acme-cpanel.sh 7.3 KB

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