226 lines
7.3 KiB
Bash
Executable File
226 lines
7.3 KiB
Bash
Executable File
#!/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 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[@]}"
|
|
if ! "${issue_cmd[@]}"; then
|
|
echo "Failed to issue certificate"
|
|
fi
|
|
# 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
|
|
issue_cmd=("${ISSUE_CMD_PREFIX[@]}" "-d" "$domain" "-d" "www.$domain")
|
|
[[ "$METHOD" == "webroot" ]] && domain_root=$(get_webroot "$domain") && issue_cmd=("${issue_cmd[@]}" "-w" "$domain_root")
|
|
deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}" "-d" "$domain") # I think we only need to deploy to the domain, not subdomains
|
|
[[ "$METHOD" == "webroot" ]] && deploy_cmd=("${deploy_cmd[@]}" "-w" "$domain_root")
|
|
echo "Running:" "${issue_cmd[@]}"
|
|
if ! "${issue_cmd[@]}"; then
|
|
echo "Failed to issue certificate for $domain"
|
|
err=1
|
|
fi
|
|
echo "Running:" "${deploy_cmd[@]}"
|
|
if ! "${deploy_cmd[@]}"; then
|
|
echo "Failed to deploy certificate for $domain"
|
|
err=1
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
}
|
|
|
|
|
|
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}" |