Compare commits

...

10 Commits

Author SHA1 Message Date
b8c904bbf3 Refactor issue certs 2020-08-02 00:38:56 -04:00
7348048ddd Remove redundant info 2020-08-01 22:30:19 -04:00
35c706eceb Update e-mail function 2020-08-01 22:29:42 -04:00
a51c60370e Update README 2020-08-01 21:44:36 -04:00
e92c6c2fef Add interactive dns 2020-08-01 21:40:01 -04:00
b93f43f6fe Add plugin req 2020-08-01 20:52:33 -04:00
28e61360ae Update README 2020-08-01 20:47:10 -04:00
6e0b7cfd62 Fix derp 2020-08-01 20:46:00 -04:00
2598532761 Add it for real 2020-08-01 20:44:52 -04:00
c7eb44ae5f Combine scripts 2020-08-01 20:44:22 -04:00
4 changed files with 244 additions and 315 deletions

View File

@@ -1,16 +1,12 @@
# WIP
This project contains two files:
`acme-cpanel.sh` reads in a list of domains from one or more files. These files may only contain domains and empty lines (see `domains.txt` for example format).
`acme-cpanel-webroot.sh` (for webroot challenges)
`acme-cpanel-dns.sh` (for dns challenges, legacy script)
Both of these scripts read in a list of domains from one or more files. These files may only contain domains and empty lines (see `domains.txt`).
"*www.*" subdomains will be added automatically (do not add them to the domains file list).
## Notes
`acme-cpanel-webroot.sh` may require the following additions to .htaccess so that challenges are not automatically redirected to https:
The `--method webroot` may require the following additions to .htaccess so that challenges are not automatically redirected to https:
```text
RewriteCond %{REQUEST_FILENAME} !-f
@@ -22,33 +18,34 @@ RewriteRule ^\.well-known/.+ - [END]
Command-line (Linux):
* Move script to user home directory on the server: `scp ./* username@ip:port:~`
* Login to server: `ssh user@ip -p port`
* Make script executable: `chmod +x $HOME/acme-cpanel-webroot.s`
* Run script (ex. `$HOME/acme-cpanel-webroot.sh -s multisites`)
* Log in to server: `ssh user@ip -p port`
* Make script executable: `chmod +x $HOME/acme-cpanel.sh`
* Run script (ex. `$HOME/acme-cpanel.sh -s multisites`)
* Follow prompts to enter credentials, issue certificates, and deploy them
* Double-check that the acme cron job is enabled: `crontab -l`
cPanel:
* Use File Manager to upload files to the home directory (/home/username/)
* You may need to make file executable in Terminal: `chmod +x $HOME/acme-cpanel-webroot.sh`
* Use Terminal to run the script (ex. `$HOME/acme-cpanel-webroot.sh -s multisites`)
* You may need to make file executable in Terminal: `chmod +x $HOME/acme-cpanel.sh`
* Use Terminal to run the script (ex. `$HOME/acme-cpanel.sh -s multisites`)
* Follow prompts to enter credentials, issue certificates, and deploy them
* Use Cron Jobs app to double-check that the acme cron job is present
## Usage
### `./acme-cpanel-webroot.sh [OPTIONS] [FILES...]`
#### `./acme-cpanel.sh [OPTIONS] [FILES...]`
#### Options
```text
--method, -m dns,webroot
Choose the authentication method (default: dns)
--email, -e EMAIL
E-mail not be notified of certificate renewal failures
--keep-grouping, -k
Issue multidomain certificates based on grouping by input file
E-mail to be notified of certificate renewal failures
--group-by-file, -g
Issue multidomain certificates for all domains with the same webroot, grouped by input file
The first domain in each file will be used to determine the shared webroot
Default: issue certificates by independent domain
--sites-dir, -s DIR
Load domain list files from this directory
--force, -f
@@ -59,18 +56,14 @@ cPanel:
#### Examples
`./acme-cpanel-webroot.sh --force`
`./acme-cpanel.sh --force`
Load sites from domains.txt, issue and deploy certificates
Load sites from domains.txt, issue and deploy certificates using the webroot method
`./acme-cpanel-webroot.sh --force -s multisites`
`./acme-cpanel.sh --method dns --force -s multisites`
Load sites from multisites directory, issue and deploy certificates
Load sites from multisites directory, issue and deploy certificates using the dns method
`./acme-cpanel-webroot.sh --force -k multisites/flatwhitedesign.pw multisites/greengingerdesign.pw`
`./acme-cpanel.sh --force -g multisites/flatwhitedesign.pw multisites/greengingerdesign.pw`
Load sites from multisites directory, issue and deploy multidomain certificates based on the grouping in the file.
### `./acme_cpanel_dns.sh`
This is a legacy script that takes no arguments. By default it will read all domain lists in a top-level "multisites" directory.
Load sites from multisites directory, issue and deploy multidomain certificates with same webroot based on the grouping in the file using the webroot method

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env bash
# This program will install and configure acme, request SSL certificates from Let's Encrypt, and enable them using the cPanel API
# Comment the following line to skip issuing a test certificate
test="true"
unset err
get_acme() {
curl https://get.acme.sh | sh
curl -o "$HOME/.acme.sh/dnsapi/dns_cpaneldns.sh" https://raw.githubusercontent.com/cryobry/dns_cpaneldns/master/dns_cpaneldns.sh
"$HOME/.acme.sh/acme.sh" --upgrade --auto-upgrade
}
run_config() {
if [[ -f "$HOME/.acme.sh/account.conf" ]]; then
if grep -q "CPANELDNS_AUTH_PASSWORD" "$HOME/.acme.sh/account.conf"; then
echo "cPanel credentials already present, skipping configuration..."
echo "To rerun the configuration, first run 'rm \$HOME/.acme.sh/account.conf'"
return 0
else
# Set contact e-mail for ACME failure
read -rp 'Enter the e-mail address to contact in case of acme failure: ' EMAIL
echo
"$HOME/.acme.sh/acme.sh" --update-account --accountemail "$EMAIL"
# Read in Namecheap API variables from user for acme
read -rp 'Enter your cPanel username: ' CPANELDNS_AUTH_ID
echo
export CPANELDNS_AUTH_ID
read -rp 'Enter your cPanel password: ' CPANELDNS_AUTH_PASSWORD
echo
export CPANELDNS_AUTH_PASSWORD
read -rp 'Enter your cPanel address and port number (example: "https://www.example.com:2083/"): ' CPANELDNS_API
echo
export CPANELDNS_API
fi
else
touch "$HOME/.acme.sh/account.conf"
run_config
fi
}
# Issue certificates
issue_cert() {
local multisite_file
for multisite_file in ./multisites/*; do
echo "Attempting to issue certificates for ${multisite_file##*/} and its multisites..."
unset sites issue_cmd deploy_cmd
declare -al sites issue_cmd deploy_cmd
readarray -t sites < "${multisite_file}"
issue_cmd=("$HOME/.acme.sh/acme.sh" "--issue" "--dns" "dns_cpaneldns")
deploy_cmd=("$HOME/.acme.sh/acme.sh" "--deploy" "--deploy-hook" "cpanel_uapi")
for site in "${sites[@]}"; do
[[ "$site" != "" ]] && issue_cmd+=("-d" "$site")
done
# if test enabled, issue test certificate first
if [[ "${test:-x}" == "true" ]]; then
"${issue_cmd[@]}" --staging
read -rp -n 1 "Was the certificate correctly issued without errors? [y/N]: "
echo
[[ ! "$REPLY" =~ ^[Yy]$ ]] && err=1 && return 1
fi
echo "Running:" "${issue_cmd[@]}"
if "${issue_cmd[@]}" --force; then
echo "Running:" "${deploy_cmd[@]}"
! "${deploy_cmd[@]}" && \
echo "Could not deploy" && \
err=1
else
echo "Could not issue"
err=1
fi
done
}
main() {
get_acme
run_config
issue_cert
}
main "$@"
exit "${err:-0}"

View File

@@ -1,201 +0,0 @@
#!/usr/bin/env bash
# This script uses acme.sh to issue and deploy SSL certificates from Let's Encrypt for a list of domains using the webroot method
# See README for more details
#
# Copyright 2020 Bryan Roessler <bryanroessler@gmail.com>
#
# USAGE
# ./acme-cpanel-webroot.sh [OPTIONS] [FILES...]
#
# EXAMPLES
# TESTING: ./acme-cpanel-webroot.sh --debug -e me@gmail.com multisites/flatwhitedesign.pw multisites/greengingermultisite.website
# PRODUCTION: ./acme-cpanel-webroot.sh --force -e me@gmail.com multisites/flatwhitedesign.pw multisites/greengingermultisite.website
#
# TESTING: ./acme-cpanel-webroot.sh --debug -s multisites
# PRODUCTION: ./acme-cpanel-webroot.sh --force -s multisites
#
# FILES is a list of files containing first-level DOMAIN names (see domains.txt) on newlines
# Certificates will automatically be issued and deployed for DOMAIN and www.DOMAIN using the webroot method
#
# NOTE: The webroot method does NOT support wildcard domains, Let's Encrypt requires wildcard domains to
# use DNS challenges, which the CPANEL uapi does not support (use dns_cpaneldns plugin instead)
unset SITES_DIR USEREMAIL DOMAIN_FILES DOMAIN_GROUPS DEPLOY_CMD_PREFIX ISSUE_CMD_PREFIX DEBUG GROUP
DEBUG="true" # quote this line to stop DEBUG mode and issue certificates for real, or use --force in user options
parse_input() {
local input
declare -g USEREMAIL
declare -ag DOMAIN_FILES
if input=$(getopt -o +e:fks:d -l email:,force,keep-grouping,sites-dir:,debug -- "$@"); then
eval set -- "$input"
while true; do
case "$1" in
--email|-e)
shift
USEREMAIL="$1"
;;
--force|-f)
unset DEBUG
;;
--keep-grouping|-k)
GROUP="true"
;;
--sites-dir|-s)
shift
SITES_DIR="$1"
;;
--debug|-d)
DEBUG="true"
;;
--)
shift
break
;;
esac
shift
done
else
echo "Incorrect options provided"
exit 1
fi
# Load domain files from remaining arguments
if [[ $# -lt 1 ]]; then
[[ -v SITES_DIR && -d "$SITES_DIR" ]] && return 0
if [[ -f "domains.txt" ]]; then
echo "You have not supplied any domain files, using domains.txt by default"
DOMAIN_FILES=("domains.txt")
else
echo "You must specify a domain list or use domains.txt by default"
exit 1
fi
else
DOMAIN_FILES=("$@")
fi
}
get_acme() {
curl https://get.acme.sh | sh
source "$HOME/.bashrc"
"$HOME/.acme.sh/acme.sh" --upgrade --auto-upgrade
}
update_email() { [[ -v USEREMAIL ]] && "$HOME/.acme.sh/acme.sh" --update-account --accountemail "${USEREMAIL}"; }
command_prefixes() {
declare -ag ISSUE_CMD_PREFIX DEPLOY_CMD_PREFIX
ISSUE_CMD_PREFIX=("$HOME/.acme.sh/acme.sh" "--issue" "--force")
[[ -v DEBUG ]] && ISSUE_CMD_PREFIX=("$HOME/.acme.sh/acme.sh" "--issue" "--staging")
DEPLOY_CMD_PREFIX=("$HOME/.acme.sh/acme.sh" "--deploy" "--deploy-hook" "cpanel_uapi")
[[ -v DEBUG ]] && DEPLOY_CMD_PREFIX=("$HOME/.acme.sh/acme.sh" "--deploy" "--deploy-hook" "cpanel_uapi")
}
# Either create a single array of all domains (DOMAINS) to issue one-by-one or create an array of array names to issue for a single webroot
load_domains() {
local domain_file
declare -ag DOMAIN_GROUPS=()
if [[ -v SITES_DIR ]]; then
for domain_file in "$SITES_DIR"/*; do
DOMAIN_GROUPS+=("$(<"$domain_file")")
done
fi
for domain_file in "${DOMAIN_FILES[@]}"; do
# Load list of domains as space-delimited strings in elements of the DOMAINS array
# We can keep these separate or combine them later
DOMAIN_GROUPS+=("$(<"$domain_file")")
done
}
get_webroot() {
local webroot
if ! webroot=$(uapi DomainInfo single_domain_data domain="$1" | grep documentroot); then
echo "UAPI call failed" >&2
fi
if [[ ! -v webroot || "$webroot" == "" ]]; then
if [[ -v DEBUG ]]; then
webroot="/tmp" # set missing webroot in DEBUG mode for testing
else
echo "Could not find $1's webroot" >&2
exit 1
fi
fi
echo "$webroot"
}
issue_and_deploy_certs() {
local domain_root domain domain_group
local -a issue_cmd=()
local -a deploy_cmd=()
if [[ -v GROUP ]]; then
for domain_group in "${DOMAIN_GROUPS[@]}"; do
unset i
for domain in $domain_group; do # we want to split on whitespace
[[ "$domain" == "" ]] && continue
# Get the webroot from the first domain
if [[ ! -v i ]]; then
local i="set"
domain_root=$(get_webroot "$domain")
issue_cmd=("${ISSUE_CMD_PREFIX[@]}" "-w" "$domain_root")
fi
issue_cmd+=("-d" "$domain" "-d" "www.$domain")
done
# Issue certificate for entire domain group
echo "Running:" "${issue_cmd[@]}"
"${issue_cmd[@]}"
# Deploy certificates one by one
for domain in $domain_group; do
deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}" "-w" "$domain_root" "-d" "$domain")
echo "Running:" "${deploy_cmd[@]}"
"${deploy_cmd[@]}"
done
done
else
for domain_group in "${DOMAIN_GROUPS[@]}"; do
# Issue and deploy certificates one by one
for domain in $domain_group; do # we want to split on whitespace
domain_root=$(get_webroot "$domain")
issue_cmd=("${ISSUE_CMD_PREFIX[@]}" "-w" "$domain_root" "-d" "$domain" "-d" "www.$domain")
deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}" "-w" "$domain_root" "-d" "$domain") # I think we only need to deploy to the domain, not subdomains
echo "Running:" "${issue_cmd[@]}"
if ! "${issue_cmd[@]}"; then
echo "Certificate issue failed for $domain"
exit_err=1
fi
echo "Running:" "${deploy_cmd[@]}"
if ! "${deploy_cmd[@]}"; then
echo "Certificate deployment failed for $domain"
exit_err=1
fi
done
done
fi
}
main() {
parse_input "$@"
get_acme
update_email
command_prefixes
load_domains
issue_and_deploy_certs
}
main "$@"
exit "${exit_err:-0}"

224
acme-cpanel.sh Executable file
View File

@@ -0,0 +1,224 @@
#!/usr/bin/env bash
# This script uses acme.sh to issue and deploy SSL certificates from Let's Encrypt for a list of domains
# using the webroot or dns methods
#
# See README.md for more details
#
# Copyright 2020 Bryan Roessler <bryanroessler@gmail.com>
unset SITES_DIR USEREMAIL DOMAIN_FILES DOMAIN_GROUPS DEPLOY_CMD_PREFIX ISSUE_CMD_PREFIX DEBUG GROUP
DEBUG="true" # quote this line to stop DEBUG mode and issue certificates for real, or use --force in user options
METHOD="dns" # set the default method
CONF="$HOME/.acme.sh/account.conf"
ACME_SH="$HOME/.acme.sh/acme.sh"
parse_input() {
local input
declare -g USEREMAIL
declare -ag DOMAIN_FILES
if input=$(getopt -o +m:e:fgs:d -l method:,email:,force,group-by-file,sites-dir:,debug -- "$@"); then
eval set -- "$input"
while true; do
case "$1" in
--method|-m)
shift
METHOD="${1,,}"
;;
--force|-f)
unset DEBUG
;;
--group-by-file|-g)
GROUP="true"
;;
--sites-dir|-s)
shift
SITES_DIR="$1"
;;
--debug|-d)
DEBUG="true"
;;
--)
shift
break
;;
esac
shift
done
else
echo "Incorrect options provided"
exit 1
fi
# Load domain files from remaining arguments
if [[ $# -lt 1 ]]; then
[[ -v SITES_DIR && -d "$SITES_DIR" ]] && return 0
if [[ -f "domains.txt" ]]; then
echo "You have not supplied any domain files, using domains.txt by default"
DOMAIN_FILES=("domains.txt")
else
echo "You must specify a domain list or use domains.txt by default"
exit 1
fi
else
DOMAIN_FILES=("$@")
fi
}
interactive_dns() {
if [[ -f "$CONF" ]] && grep -q "CPANELDNS_AUTH_PASSWORD" "$CONF"; then
echo "cPanel credentials already present, skipping configuration..."
echo "To rerun the configuration, first run 'rm $CONF'"
else
read -rp 'Enter your cPanel username: ' CPANELDNS_AUTH_ID
echo
export CPANELDNS_AUTH_ID
read -rp 'Enter your cPanel password: ' CPANELDNS_AUTH_PASSWORD
echo
export CPANELDNS_AUTH_PASSWORD
read -rp 'Enter your cPanel address and port number (example: "https://www.example.com:2083/"): ' CPANELDNS_API
echo
export CPANELDNS_API
fi
}
get_acme() {
curl https://get.acme.sh | sh
# shellcheck disable=SC1090
source "$HOME/.bashrc"
"$ACME_SH" --upgrade --auto-upgrade
[[ "$METHOD" == "dns" ]] && \
curl -o "$HOME/.acme.sh/dnsapi/dns_cpaneldns.sh" https://raw.githubusercontent.com/cryobry/dns_cpaneldns/master/dns_cpaneldns.sh
}
update_email() {
if [[ ! -v USEREMAIL ]]; then
if [[ -f "$CONF" ]] && line=$(grep -q "ACCOUNT_EMAIL" "$CONF"); then
echo "Reusing existing contact e-mail: ${line#ACCOUNT_EMAIL=}"
return 0
fi
read -rp 'Enter your contact e-mail (in case of renewal failures): ' USEREMAIL
fi
"$ACME_SH" --update-account --accountemail "${USEREMAIL}"
}
command_prefixes() {
declare -ag ISSUE_CMD_PREFIX DEPLOY_CMD_PREFIX
ISSUE_CMD_PREFIX=("$ACME_SH" "--issue")
[[ "$METHOD" == "dns" ]] && ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--dns" "dns_cpaneldns")
[[ -v DEBUG ]] && ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--staging") || ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--force")
DEPLOY_CMD_PREFIX=("$ACME_SH" "--deploy" "--deploy-hook" "cpanel_uapi")
}
get_webroot() {
local webroot
if ! webroot=$(uapi DomainInfo single_domain_data domain="$1" | grep documentroot); then
echo "UAPI call failed" >&2
fi
if [[ ! -v webroot || "$webroot" == "" ]]; then
if [[ -v DEBUG ]]; then
webroot="/tmp" # set missing webroot in DEBUG mode for testing
else
echo "Could not find $1's webroot" >&2
exit 1
fi
fi
echo "$webroot"
}
# Either create a single array of all domains (DOMAINS) to issue one-by-one or create an array of array names to issue for a single webroot
load_domains() {
local domain_file
declare -ag DOMAIN_GROUPS=()
if [[ -v SITES_DIR ]]; then
for domain_file in "$SITES_DIR"/*; do
DOMAIN_GROUPS+=("$(<"$domain_file")")
done
fi
for domain_file in "${DOMAIN_FILES[@]}"; do
# Load list of domains as space-delimited strings in elements of the DOMAINS array
# We can keep these separate or combine them later
DOMAIN_GROUPS+=("$(<"$domain_file")")
done
}
issue_and_deploy_certs() {
local group_root domain_root domain domain_group
for domain_group in "${DOMAIN_GROUPS[@]}"; do
local -a issue_cmd=("${ISSUE_CMD_PREFIX[@]}")
local -a deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}")
local i="set"
# Issue certificates
for domain in $domain_group; do # we want to split on whitespace
[[ "$domain" == "" ]] && continue
if [[ -v GROUP ]]; then
if [[ "$METHOD" == "webroot" && -v i ]]; then
group_root=$(get_webroot "$domain")
issue_cmd+=("-w" "$group_root")
unset i
fi
# Append domains to issue command that we will call after the loop
issue_cmd+=("-d" "$domain" "-d" "www.$domain")
# Issue certificate for single domain
else
local -a issue_cmd=("${ISSUE_CMD_PREFIX[@]}")
domain_root=$(get_webroot "$domain")
issue_cmd+=("-d" "$domain" "-d" "www.$domain")
[[ "$METHOD" == "webroot" ]] && issue_cmd+=("-w" "$domain_root")
echo "Running:" "${issue_cmd[@]}"
if ! "${issue_cmd[@]}"; then
echo "Failed to issue certificate for domain: $domain"
err=1
fi
fi
done
# Issue certificate for group of domains
if [[ -v GROUP ]]; then
echo "Running:" "${issue_cmd[@]}"
if ! "${issue_cmd[@]}"; then
echo "Failed to issue certificate for domain group: $domain_group"
err=1
fi
fi
# Deploy certificates one domain at a time
for domain in $domain_group; do
deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}" "-d" "$domain") # I think we only need to deploy to the domain, not subdomains (e.g. www.)
echo "Running:" "${deploy_cmd[@]}"
if ! "${deploy_cmd[@]}"; then
echo "Failed to deploy certificate for $domain"
err=1
fi
done
done
}
main() {
parse_input "$@"
get_acme
update_email
command_prefixes
load_domains
[[ "$METHOD" == "dns" ]] && interactive_dns
sanity_check
issue_and_deploy_certs
}
main "$@"
exit "${err:-0}"