acme-cpanel.sh 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #!/usr/bin/env bash
  2. # This script uses acme.sh to issue and deploy SSL certificates from Let's Encrypt for a list of domains
  3. # using the webroot or dns methods
  4. #
  5. # See README.md for more details
  6. #
  7. # Copyright 2020 Bryan Roessler <bryanroessler@gmail.com>
  8. unset SITES_DIR USEREMAIL DOMAIN_FILES DOMAIN_GROUPS DEPLOY_CMD_PREFIX ISSUE_CMD_PREFIX DEBUG GROUP
  9. DEBUG="true" # quote this line to stop DEBUG mode and issue certificates for real, or use --force in user options
  10. METHOD="dns" # set the default method
  11. CONF="$HOME/.acme.sh/account.conf"
  12. ACME_SH="$HOME/.acme.sh/acme.sh"
  13. parse_input() {
  14. local input
  15. declare -g USEREMAIL
  16. declare -ag DOMAIN_FILES
  17. if input=$(getopt -o +m:e:fgs:d -l method:,email:,force,group-by-file,sites-dir:,debug -- "$@"); then
  18. eval set -- "$input"
  19. while true; do
  20. case "$1" in
  21. --method|-m)
  22. shift
  23. METHOD="${1,,}"
  24. ;;
  25. --force|-f)
  26. unset DEBUG
  27. ;;
  28. --group-by-file|-g)
  29. GROUP="true"
  30. ;;
  31. --sites-dir|-s)
  32. shift
  33. SITES_DIR="$1"
  34. ;;
  35. --debug|-d)
  36. DEBUG="true"
  37. ;;
  38. --)
  39. shift
  40. break
  41. ;;
  42. esac
  43. shift
  44. done
  45. else
  46. echo "Incorrect options provided"
  47. exit 1
  48. fi
  49. # Load domain files from remaining arguments
  50. if [[ $# -lt 1 ]]; then
  51. [[ -v SITES_DIR && -d "$SITES_DIR" ]] && return 0
  52. if [[ -f "domains.txt" ]]; then
  53. echo "You have not supplied any domain files, using domains.txt by default"
  54. DOMAIN_FILES=("domains.txt")
  55. else
  56. echo "You must specify a domain list or use domains.txt by default"
  57. exit 1
  58. fi
  59. else
  60. DOMAIN_FILES=("$@")
  61. fi
  62. }
  63. interactive_dns() {
  64. if [[ -f "$CONF" ]] && grep -q "CPANELDNS_AUTH_PASSWORD" "$CONF"; then
  65. echo "cPanel credentials already present, skipping configuration..."
  66. echo "To rerun the configuration, first run 'rm $CONF'"
  67. else
  68. read -rp 'Enter your cPanel username: ' CPANELDNS_AUTH_ID
  69. echo
  70. export CPANELDNS_AUTH_ID
  71. read -rp 'Enter your cPanel password: ' CPANELDNS_AUTH_PASSWORD
  72. echo
  73. export CPANELDNS_AUTH_PASSWORD
  74. read -rp 'Enter your cPanel address and port number (example: "https://www.example.com:2083/"): ' CPANELDNS_API
  75. echo
  76. export CPANELDNS_API
  77. fi
  78. }
  79. get_acme() {
  80. curl https://get.acme.sh | sh
  81. # shellcheck disable=SC1090
  82. source "$HOME/.bashrc"
  83. "$ACME_SH" --upgrade --auto-upgrade
  84. [[ "$METHOD" == "dns" ]] && \
  85. curl -o "$HOME/.acme.sh/dnsapi/dns_cpaneldns.sh" https://raw.githubusercontent.com/cryobry/dns_cpaneldns/master/dns_cpaneldns.sh
  86. }
  87. update_email() {
  88. if [[ ! -v USEREMAIL ]]; then
  89. if [[ -f "$CONF" ]] && line=$(grep -q "ACCOUNT_EMAIL" "$CONF"); then
  90. echo "Reusing existing contact e-mail: ${line#ACCOUNT_EMAIL=}"
  91. return 0
  92. fi
  93. read -rp 'Enter your contact e-mail (in case of renewal failures): ' USEREMAIL
  94. fi
  95. "$ACME_SH" --update-account --accountemail "${USEREMAIL}"
  96. }
  97. command_prefixes() {
  98. declare -ag ISSUE_CMD_PREFIX DEPLOY_CMD_PREFIX
  99. ISSUE_CMD_PREFIX=("$ACME_SH" "--issue")
  100. [[ "$METHOD" == "dns" ]] && ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--dns" "dns_cpaneldns")
  101. [[ -v DEBUG ]] && ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--staging") || ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--force")
  102. DEPLOY_CMD_PREFIX=("$ACME_SH" "--deploy" "--deploy-hook" "cpanel_uapi")
  103. }
  104. get_webroot() {
  105. local webroot
  106. if ! webroot=$(uapi DomainInfo single_domain_data domain="$1" | grep documentroot); then
  107. echo "UAPI call failed" >&2
  108. fi
  109. if [[ ! -v webroot || "$webroot" == "" ]]; then
  110. if [[ -v DEBUG ]]; then
  111. webroot="/tmp" # set missing webroot in DEBUG mode for testing
  112. else
  113. echo "Could not find $1's webroot" >&2
  114. exit 1
  115. fi
  116. fi
  117. echo "$webroot"
  118. }
  119. # 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
  120. load_domains() {
  121. local domain_file
  122. declare -ag DOMAIN_GROUPS=()
  123. if [[ -v SITES_DIR ]]; then
  124. for domain_file in "$SITES_DIR"/*; do
  125. DOMAIN_GROUPS+=("$(<"$domain_file")")
  126. done
  127. fi
  128. for domain_file in "${DOMAIN_FILES[@]}"; do
  129. # Load list of domains as space-delimited strings in elements of the DOMAINS array
  130. # We can keep these separate or combine them later
  131. DOMAIN_GROUPS+=("$(<"$domain_file")")
  132. done
  133. }
  134. issue_and_deploy_certs() {
  135. local group_root domain_root domain domain_group
  136. for domain_group in "${DOMAIN_GROUPS[@]}"; do
  137. local -a issue_cmd=("${ISSUE_CMD_PREFIX[@]}")
  138. local -a deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}")
  139. local i="set"
  140. # Issue certificates
  141. for domain in $domain_group; do # we want to split on whitespace
  142. [[ "$domain" == "" ]] && continue
  143. if [[ -v GROUP ]]; then
  144. if [[ "$METHOD" == "webroot" && -v i ]]; then
  145. group_root=$(get_webroot "$domain")
  146. issue_cmd+=("-w" "$group_root")
  147. unset i
  148. fi
  149. # Append domains to issue command that we will call after the loop
  150. issue_cmd+=("-d" "$domain" "-d" "www.$domain")
  151. # Issue certificate for single domain
  152. else
  153. local -a issue_cmd=("${ISSUE_CMD_PREFIX[@]}")
  154. domain_root=$(get_webroot "$domain")
  155. issue_cmd+=("-d" "$domain" "-d" "www.$domain")
  156. [[ "$METHOD" == "webroot" ]] && issue_cmd+=("-w" "$domain_root")
  157. echo "Running:" "${issue_cmd[@]}"
  158. if ! "${issue_cmd[@]}"; then
  159. echo "Failed to issue certificate for domain: $domain"
  160. err=1
  161. fi
  162. fi
  163. done
  164. # Issue certificate for group of domains
  165. if [[ -v GROUP ]]; then
  166. echo "Running:" "${issue_cmd[@]}"
  167. if ! "${issue_cmd[@]}"; then
  168. echo "Failed to issue certificate for domain group: $domain_group"
  169. err=1
  170. fi
  171. fi
  172. # Deploy certificates one domain at a time
  173. for domain in $domain_group; do
  174. deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}" "-d" "$domain") # I think we only need to deploy to the domain, not subdomains (e.g. www.)
  175. echo "Running:" "${deploy_cmd[@]}"
  176. if ! "${deploy_cmd[@]}"; then
  177. echo "Failed to deploy certificate for $domain"
  178. err=1
  179. fi
  180. done
  181. done
  182. }
  183. main() {
  184. parse_input "$@"
  185. get_acme
  186. update_email
  187. command_prefixes
  188. load_domains
  189. [[ "$METHOD" == "dns" ]] && interactive_dns
  190. sanity_check
  191. issue_and_deploy_certs
  192. }
  193. main "$@"
  194. exit "${err:-0}"