From 25985327612e67341e7ac740fd75d913b619aadf Mon Sep 17 00:00:00 2001 From: bryan Date: Sat, 1 Aug 2020 20:44:52 -0400 Subject: [PATCH] Add it for real --- acme-cpanel.sh | 212 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100755 acme-cpanel.sh diff --git a/acme-cpanel.sh b/acme-cpanel.sh new file mode 100755 index 0000000..e63f5ad --- /dev/null +++ b/acme-cpanel.sh @@ -0,0 +1,212 @@ +#!/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 +# +# USAGE +# ./acme-cpanel.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) + +source functions.sh + +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 + +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,,}" + ;; + --email|-e) + shift + USEREMAIL="$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 +} + + +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") + [[ "$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=("$HOME/.acme.sh/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" + # 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 + issue_and_deploy_certs +} + + +main "$@" +exit "${err:-0}" \ No newline at end of file