Browse Source

Initial commit

bryan 2 years ago
commit
d3860e9a29

+ 76 - 0
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.

+ 87 - 0
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}"

+ 201 - 0
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 <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}"

+ 46 - 0
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

+ 28 - 0
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

+ 1 - 0
multisites/greengingerdesign.pw

@@ -0,0 +1 @@
+greengingerdesign.pw

+ 2 - 0
multisites/greengingerdesign.space

@@ -0,0 +1,2 @@
+'*.greengingerdesign.space'
+greengingerdesign.space

+ 16 - 0
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