acme-cpanel-webroot.sh 6.6 KB

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