123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- #!/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}"
|