From d3860e9a29ddc3f108611c3c65476548bab26777 Mon Sep 17 00:00:00 2001 From: bryan Date: Sat, 1 Aug 2020 19:41:58 -0400 Subject: [PATCH] Initial commit --- README.md | 76 +++++++++ acme-cpanel-dns.sh | 87 ++++++++++ acme-cpanel-webroot.sh | 201 ++++++++++++++++++++++++ domains.txt | 46 ++++++ multisites/flatwhitedesign.pw | 28 ++++ multisites/greengingerdesign.pw | 1 + multisites/greengingerdesign.space | 2 + multisites/greengingermultisite.website | 16 ++ 8 files changed, 457 insertions(+) create mode 100755 README.md create mode 100644 acme-cpanel-dns.sh create mode 100755 acme-cpanel-webroot.sh create mode 100644 domains.txt create mode 100644 multisites/flatwhitedesign.pw create mode 100644 multisites/greengingerdesign.pw create mode 100644 multisites/greengingerdesign.space create mode 100644 multisites/greengingermultisite.website diff --git a/README.md b/README.md new file mode 100755 index 0000000..23873d0 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# WIP + +This project contains two files: + +`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`). + +## Notes + +`acme-cpanel-webroot.sh` may require the following additions to .htaccess so that challenges are not automatically redirected to https: + +```text +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^\.well-known/.+ - [END] +``` + +## Installation + +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`) +* 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`) +* 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...]` + +#### Options + +```text +--email, -e EMAIL + E-mail not be notified of certificate renewal failures +--keep-grouping, -k + Issue multidomain certificates based on grouping 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 + Override default debug +--debug, -d (default) + Use --staging to issue certificates and do not deploy +``` + +#### Examples + +`./acme-cpanel-webroot.sh --force` + +Load sites from domains.txt, issue and deploy certificates + +`./acme-cpanel-webroot.sh --force -s multisites` + +Load sites from multisites directory, issue and deploy certificates + +`./acme-cpanel-webroot.sh --force -k 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. \ No newline at end of file diff --git a/acme-cpanel-dns.sh b/acme-cpanel-dns.sh new file mode 100644 index 0000000..d62d9e4 --- /dev/null +++ b/acme-cpanel-dns.sh @@ -0,0 +1,87 @@ +#!/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}" diff --git a/acme-cpanel-webroot.sh b/acme-cpanel-webroot.sh new file mode 100755 index 0000000..9eb4cf9 --- /dev/null +++ b/acme-cpanel-webroot.sh @@ -0,0 +1,201 @@ +#!/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 +# +# 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}" \ No newline at end of file diff --git a/domains.txt b/domains.txt new file mode 100644 index 0000000..ed96b3d --- /dev/null +++ b/domains.txt @@ -0,0 +1,46 @@ +flatwhitedesign.pw +amyduthie.co.uk +annajrobertson.com +bekibeer.com +believeinmeaffirmations.co.uk +bigquestionsbrightertomorrow.com +blackparrotsocial.co.uk +creativelyspotted.co.uk +curiositysocialmedia.co.uk +emmamacfarlane.co.uk +fairwayscommunications.co.uk +flatwhitewebsites.co.uk +fredthecoach.co.uk +hoopsocial.co.uk +kirklandgardenservices.com +laurahetzlernutrition.com +muchmoresocial.co.uk +residenthuman.com +snuffbottlepages.com +socialforbusiness.co.uk +socialstylepro.co.uk +socialthingslinz.com +sparrowhawkcomms.co.uk +stripedsisters.co.uk +track17london.com +upfrontandsocial.co.uk +violetdigital.co.uk +windygap.co.uk +greengingermultisite.website +austin-hope-pilkington.org.uk +avishankar.co.uk +fjblegal.com +generalcharityfund.org.uk +glovers-trust.org +greengingerdesign.biz +greengingerdesign.co.uk +londonwatersportscentre.org +louisshankar.co.uk +nelemorprojects.com +pilkingtoncharitiesfund.org.uk +radt.org.uk +raindropsocial.com +sinswitch.com +ttrc.org.uk +greengingerdesign.space +greengingerdesign.pw diff --git a/multisites/flatwhitedesign.pw b/multisites/flatwhitedesign.pw new file mode 100644 index 0000000..6624708 --- /dev/null +++ b/multisites/flatwhitedesign.pw @@ -0,0 +1,28 @@ +flatwhitedesign.pw +amyduthie.co.uk +annajrobertson.com +bekibeer.com +believeinmeaffirmations.co.uk +bigquestionsbrightertomorrow.com +blackparrotsocial.co.uk +creativelyspotted.co.uk +curiositysocialmedia.co.uk +emmamacfarlane.co.uk +fairwayscommunications.co.uk +flatwhitewebsites.co.uk +fredthecoach.co.uk +hoopsocial.co.uk +kirklandgardenservices.com +laurahetzlernutrition.com +muchmoresocial.co.uk +residenthuman.com +snuffbottlepages.com +socialforbusiness.co.uk +socialstylepro.co.uk +socialthingslinz.com +sparrowhawkcomms.co.uk +stripedsisters.co.uk +track17london.com +upfrontandsocial.co.uk +violetdigital.co.uk +windygap.co.uk diff --git a/multisites/greengingerdesign.pw b/multisites/greengingerdesign.pw new file mode 100644 index 0000000..28b6f3e --- /dev/null +++ b/multisites/greengingerdesign.pw @@ -0,0 +1 @@ +greengingerdesign.pw diff --git a/multisites/greengingerdesign.space b/multisites/greengingerdesign.space new file mode 100644 index 0000000..a3c7f7e --- /dev/null +++ b/multisites/greengingerdesign.space @@ -0,0 +1,2 @@ +'*.greengingerdesign.space' +greengingerdesign.space diff --git a/multisites/greengingermultisite.website b/multisites/greengingermultisite.website new file mode 100644 index 0000000..3a109b1 --- /dev/null +++ b/multisites/greengingermultisite.website @@ -0,0 +1,16 @@ +greengingermultisite.website +austin-hope-pilkington.org.uk +avishankar.co.uk +fjblegal.com +generalcharityfund.org.uk +glovers-trust.org +greengingerdesign.biz +greengingerdesign.co.uk +londonwatersportscentre.org +louisshankar.co.uk +nelemorprojects.com +pilkingtoncharitiesfund.org.uk +radt.org.uk +raindropsocial.com +sinswitch.com +ttrc.org.uk \ No newline at end of file