acme-cpanel.sh 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. # USAGE
  10. # ./acme-cpanel.sh [OPTIONS] [FILES...]
  11. #
  12. # EXAMPLES
  13. # TESTING: ./acme-cpanel-webroot.sh --debug -e me@gmail.com multisites/flatwhitedesign.pw multisites/greengingermultisite.website
  14. # PRODUCTION: ./acme-cpanel-webroot.sh --force -e me@gmail.com multisites/flatwhitedesign.pw multisites/greengingermultisite.website
  15. #
  16. # TESTING: ./acme-cpanel-webroot.sh --debug -s multisites
  17. # PRODUCTION: ./acme-cpanel-webroot.sh --force -s multisites
  18. #
  19. # FILES is a list of files containing first-level DOMAIN names (see domains.txt) on newlines
  20. # Certificates will automatically be issued and deployed for DOMAIN and www.DOMAIN using the webroot method
  21. #
  22. # NOTE: The webroot method does NOT support wildcard domains, Let's Encrypt requires wildcard domains to
  23. # use DNS challenges, which the CPANEL uapi does not support (use dns_cpaneldns plugin instead)
  24. source functions.sh
  25. unset SITES_DIR USEREMAIL DOMAIN_FILES DOMAIN_GROUPS DEPLOY_CMD_PREFIX ISSUE_CMD_PREFIX DEBUG GROUP
  26. DEBUG="true" # quote this line to stop DEBUG mode and issue certificates for real, or use --force in user options
  27. METHOD="dns" # set the default method
  28. parse_input() {
  29. local input
  30. declare -g USEREMAIL
  31. declare -ag DOMAIN_FILES
  32. if input=$(getopt -o +m:e:fgs:d -l method:,email:,force,group-by-file,sites-dir:,debug -- "$@"); then
  33. eval set -- "$input"
  34. while true; do
  35. case "$1" in
  36. --method|-m)
  37. shift
  38. METHOD="${1,,}"
  39. ;;
  40. --email|-e)
  41. shift
  42. USEREMAIL="$1"
  43. ;;
  44. --force|-f)
  45. unset DEBUG
  46. ;;
  47. --group-by-file|-g)
  48. GROUP="true"
  49. ;;
  50. --sites-dir|-s)
  51. shift
  52. SITES_DIR="$1"
  53. ;;
  54. --debug|-d)
  55. DEBUG="true"
  56. ;;
  57. --)
  58. shift
  59. break
  60. ;;
  61. esac
  62. shift
  63. done
  64. else
  65. echo "Incorrect options provided"
  66. exit 1
  67. fi
  68. # Load domain files from remaining arguments
  69. if [[ $# -lt 1 ]]; then
  70. [[ -v SITES_DIR && -d "$SITES_DIR" ]] && return 0
  71. if [[ -f "domains.txt" ]]; then
  72. echo "You have not supplied any domain files, using domains.txt by default"
  73. DOMAIN_FILES=("domains.txt")
  74. else
  75. echo "You must specify a domain list or use domains.txt by default"
  76. exit 1
  77. fi
  78. else
  79. DOMAIN_FILES=("$@")
  80. fi
  81. }
  82. get_acme() {
  83. curl https://get.acme.sh | sh
  84. source "$HOME/.bashrc"
  85. "$HOME/.acme.sh/acme.sh" --upgrade --auto-upgrade
  86. }
  87. update_email() { [[ -v USEREMAIL ]] && "$HOME/.acme.sh/acme.sh" --update-account --accountemail "${USEREMAIL}"; }
  88. command_prefixes() {
  89. declare -ag ISSUE_CMD_PREFIX DEPLOY_CMD_PREFIX
  90. ISSUE_CMD_PREFIX=("$HOME/.acme.sh/acme.sh" "--issue")
  91. [[ "$METHOD" == "dns" ]] && ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--dns" "dns_cpaneldns")
  92. [[ -v DEBUG ]] && ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--staging") || ISSUE_CMD_PREFIX=("${ISSUE_CMD_PREFIX[@]}" "--force")
  93. DEPLOY_CMD_PREFIX=("$HOME/.acme.sh/acme.sh" "--deploy" "--deploy-hook" "cpanel_uapi")
  94. }
  95. get_webroot() {
  96. local webroot
  97. if ! webroot=$(uapi DomainInfo single_domain_data domain="$1" | grep documentroot); then
  98. echo "UAPI call failed" >&2
  99. fi
  100. if [[ ! -v webroot || "$webroot" == "" ]]; then
  101. if [[ -v DEBUG ]]; then
  102. webroot="/tmp" # set missing webroot in DEBUG mode for testing
  103. else
  104. echo "Could not find $1's webroot" >&2
  105. exit 1
  106. fi
  107. fi
  108. echo "$webroot"
  109. }
  110. # 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
  111. load_domains() {
  112. local domain_file
  113. declare -ag DOMAIN_GROUPS=()
  114. if [[ -v SITES_DIR ]]; then
  115. for domain_file in "$SITES_DIR"/*; do
  116. DOMAIN_GROUPS+=("$(<"$domain_file")")
  117. done
  118. fi
  119. for domain_file in "${DOMAIN_FILES[@]}"; do
  120. # Load list of domains as space-delimited strings in elements of the DOMAINS array
  121. # We can keep these separate or combine them later
  122. DOMAIN_GROUPS+=("$(<"$domain_file")")
  123. done
  124. }
  125. issue_and_deploy_certs() {
  126. local domain_root domain domain_group
  127. local -a issue_cmd=()
  128. local -a deploy_cmd=()
  129. if [[ -v GROUP ]]; then
  130. for domain_group in "${DOMAIN_GROUPS[@]}"; do
  131. unset i
  132. for domain in $domain_group; do # we want to split on whitespace
  133. [[ "$domain" == "" ]] && continue
  134. # Get the webroot from the first domain
  135. if [[ ! -v i ]]; then
  136. local i="set"
  137. domain_root=$(get_webroot "$domain")
  138. issue_cmd=("${ISSUE_CMD_PREFIX[@]}" "-w" "$domain_root")
  139. fi
  140. issue_cmd+=("-d" "$domain" "-d" "www.$domain")
  141. done
  142. # Issue certificate for entire domain group
  143. echo "Running:" "${issue_cmd[@]}"
  144. if ! "${issue_cmd[@]}"; then
  145. echo "Failed to issue certificate"
  146. # Deploy certificates one by one
  147. for domain in $domain_group; do
  148. deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}" "-w" "$domain_root" "-d" "$domain")
  149. echo "Running:" "${deploy_cmd[@]}"
  150. "${deploy_cmd[@]}"
  151. done
  152. done
  153. else
  154. for domain_group in "${DOMAIN_GROUPS[@]}"; do
  155. # Issue and deploy certificates one by one
  156. for domain in $domain_group; do # we want to split on whitespace
  157. issue_cmd=("${ISSUE_CMD_PREFIX[@]}" "-d" "$domain" "-d" "www.$domain")
  158. [[ "$METHOD" == "webroot" ]] && domain_root=$(get_webroot "$domain") && issue_cmd=("${issue_cmd[@]}" "-w" "$domain_root")
  159. deploy_cmd=("${DEPLOY_CMD_PREFIX[@]}" "-d" "$domain") # I think we only need to deploy to the domain, not subdomains
  160. [[ "$METHOD" == "webroot" ]] && deploy_cmd=("${deploy_cmd[@]}" "-w" "$domain_root")
  161. echo "Running:" "${issue_cmd[@]}"
  162. if ! "${issue_cmd[@]}"; then
  163. echo "Failed to issue certificate for $domain"
  164. err=1
  165. fi
  166. echo "Running:" "${deploy_cmd[@]}"
  167. if ! "${deploy_cmd[@]}"; then
  168. echo "Failed to deploy certificate for $domain"
  169. err=1
  170. fi
  171. done
  172. done
  173. fi
  174. }
  175. main() {
  176. parse_input "$@"
  177. get_acme
  178. update_email
  179. command_prefixes
  180. load_domains
  181. issue_and_deploy_certs
  182. }
  183. main "$@"
  184. exit "${err:-0}"