install_caddy.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #!/usr/bin/env bash
  2. # =============================================================================
  3. # FranklinWH Toolbox - Caddy 安装与站点配置脚本
  4. # =============================================================================
  5. # 作用:安装 Caddy 2(缺失才装),并为本项目生成/部署一个站点 Caddyfile:
  6. # 1. 前端静态:/fwh-toolbox/* -> localhost:${CADDY_FRONTEND_PORT}(8300)
  7. # 2. 后端 API :/fwh-toolbox/api/* -> localhost:${CADDY_BACKEND_PORT}(8301)
  8. # 3. (可选)Casdoor:/casdoor/* -> localhost:${CADDY_CASDOOR_PORT}(8010)
  9. # 4. (可选)MinIO 控制台:/fwh-minio/* -> localhost:${CADDY_MINIO_CONSOLE_PORT}(9199)
  10. # 生成的片段风格参考线上 /etc/caddy/sites/002-lmt.zenpower.cc.caddy。
  11. #
  12. # 站点文件默认写入 /etc/caddy/sites/,并确保主 /etc/caddy/Caddyfile 里存在
  13. # `import sites/*.caddy`(若缺失则自动追加),最后 validate + reload。
  14. #
  15. # 前置条件:
  16. # - Ubuntu/Debian(apt)。其他发行版请参考 DEPLOYMENT.md 第 9 章手动安装。
  17. #
  18. # 环境变量:
  19. # CADDY_DOMAIN 对外域名(必填,如 lmt.zenpower.cc)
  20. # CADDY_TLS_MODE TLS 模式:internal | acme | manual (默认 internal)
  21. # CADDY_ACME_EMAIL acme 模式下的邮箱 (默认空)
  22. # CADDY_TLS_CERT manual 模式下证书路径 (默认空)
  23. # CADDY_TLS_KEY manual 模式下私钥路径 (默认空)
  24. # CADDY_SITES_DIR 站点片段目录 (默认 /etc/caddy/sites)
  25. # CADDY_MAIN_FILE 主配置文件 (默认 /etc/caddy/Caddyfile)
  26. # CADDY_FRONTEND_PORT 前端静态端口 (默认 8300)
  27. # CADDY_BACKEND_PORT 后端 API 端口 (默认 8301)
  28. # CADDY_INCLUDE_CASDOOR 是否加入 /casdoor 反代 (y/n) (默认 n)
  29. # CADDY_CASDOOR_PORT Casdoor 端口 (默认 8010)
  30. # CADDY_INCLUDE_MINIO 是否加入 /fwh-minio 反代 (y/n) (默认 n)
  31. # CADDY_MINIO_CONSOLE_PORT MinIO 控制台端口 (默认 9199)
  32. # CADDY_SERVE_STATIC 由 Caddy 直接 file_server 托管前端 (y/n) (默认 n)
  33. # CADDY_STATIC_ROOT file_server 的静态根目录 (默认 /var/www/fwh-toolbox/fwh-toolbox)
  34. # =============================================================================
  35. set -euo pipefail
  36. CADDY_DOMAIN="${CADDY_DOMAIN:-}"
  37. CADDY_TLS_MODE="${CADDY_TLS_MODE:-internal}"
  38. CADDY_ACME_EMAIL="${CADDY_ACME_EMAIL:-}"
  39. CADDY_TLS_CERT="${CADDY_TLS_CERT:-}"
  40. CADDY_TLS_KEY="${CADDY_TLS_KEY:-}"
  41. CADDY_SITES_DIR="${CADDY_SITES_DIR:-/etc/caddy/sites}"
  42. CADDY_MAIN_FILE="${CADDY_MAIN_FILE:-/etc/caddy/Caddyfile}"
  43. CADDY_FRONTEND_PORT="${CADDY_FRONTEND_PORT:-8300}"
  44. CADDY_BACKEND_PORT="${CADDY_BACKEND_PORT:-8301}"
  45. CADDY_INCLUDE_CASDOOR="${CADDY_INCLUDE_CASDOOR:-n}"
  46. CADDY_CASDOOR_PORT="${CADDY_CASDOOR_PORT:-8010}"
  47. CADDY_INCLUDE_MINIO="${CADDY_INCLUDE_MINIO:-n}"
  48. CADDY_MINIO_CONSOLE_PORT="${CADDY_MINIO_CONSOLE_PORT:-9199}"
  49. CADDY_SERVE_STATIC="${CADDY_SERVE_STATIC:-n}"
  50. CADDY_STATIC_ROOT="${CADDY_STATIC_ROOT:-/var/www/fwh-toolbox/fwh-toolbox}"
  51. c_reset='\033[0m'; c_blue='\033[1;34m'; c_green='\033[1;32m'; c_yellow='\033[1;33m'; c_red='\033[1;31m'
  52. info() { echo -e "${c_blue}[INFO]${c_reset} $*"; }
  53. ok() { echo -e "${c_green}[ OK ]${c_reset} $*"; }
  54. warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; }
  55. err() { echo -e "${c_red}[FAIL]${c_reset} $*" >&2; }
  56. has() { command -v "$1" >/dev/null 2>&1; }
  57. # ---------------------------------------------------------------------------
  58. # 1. 安装 Caddy(缺失才装)
  59. # ---------------------------------------------------------------------------
  60. ensure_caddy() {
  61. if has caddy; then
  62. ok "Caddy 已安装:$(caddy version 2>/dev/null | head -n1)"
  63. return 0
  64. fi
  65. info "安装 Caddy 2 ..."
  66. if ! has apt-get; then
  67. err "当前系统未提供 apt-get,请参考 DEPLOYMENT.md 第 9 章手动安装 Caddy。"
  68. return 1
  69. fi
  70. sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl gnupg
  71. curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  72. | sudo gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
  73. curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  74. | sudo tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null
  75. sudo apt-get update -y
  76. sudo apt-get install -y caddy
  77. sudo systemctl enable caddy >/dev/null 2>&1 || true
  78. ok "Caddy 安装完成:$(caddy version 2>/dev/null | head -n1)"
  79. }
  80. # ---------------------------------------------------------------------------
  81. # 2. 组装 tls 指令块
  82. # ---------------------------------------------------------------------------
  83. _tls_block() {
  84. case "${CADDY_TLS_MODE}" in
  85. internal)
  86. printf '%s' "tls internal"
  87. ;;
  88. acme)
  89. if [[ -n "${CADDY_ACME_EMAIL}" ]]; then
  90. printf '%s' "tls ${CADDY_ACME_EMAIL}"
  91. else
  92. # 走全局/默认 ACME,无需站点级 tls 指令
  93. printf ''
  94. fi
  95. ;;
  96. manual)
  97. if [[ -z "${CADDY_TLS_CERT}" || -z "${CADDY_TLS_KEY}" ]]; then
  98. err "TLS 模式=manual 需要同时提供 CADDY_TLS_CERT 与 CADDY_TLS_KEY"
  99. return 1
  100. fi
  101. printf '%s' "tls ${CADDY_TLS_CERT} ${CADDY_TLS_KEY}"
  102. ;;
  103. *)
  104. err "未知 CADDY_TLS_MODE:${CADDY_TLS_MODE}(可选 internal|acme|manual)"
  105. return 1
  106. ;;
  107. esac
  108. }
  109. # ---------------------------------------------------------------------------
  110. # 3. 生成站点 Caddyfile
  111. # ---------------------------------------------------------------------------
  112. write_site() {
  113. [[ -n "${CADDY_DOMAIN}" ]] || { err "CADDY_DOMAIN 不能为空"; return 1; }
  114. sudo mkdir -p "${CADDY_SITES_DIR}"
  115. local site_file="${CADDY_SITES_DIR}/fwh-toolbox.caddy"
  116. local tls_directive; tls_directive="$(_tls_block)" || return 1
  117. # 组装 tls 行(为空则不输出该行)
  118. local tls_line=""
  119. [[ -n "${tls_directive}" ]] && tls_line=$'\t'"${tls_directive}"$'\n'
  120. info "生成站点配置:${site_file}"
  121. # 前端处理块:file_server 直托管 或 反代到静态端口
  122. local frontend_block
  123. if [[ "${CADDY_SERVE_STATIC}" == "y" ]]; then
  124. frontend_block=$(cat <<EOF
  125. # 前端静态:由 Caddy 直接托管
  126. reverse_proxy /fwh-toolbox/api/* localhost:${CADDY_BACKEND_PORT}
  127. handle_path /fwh-toolbox/* {
  128. root * ${CADDY_STATIC_ROOT}
  129. try_files {path} /index.html
  130. file_server
  131. }
  132. EOF
  133. )
  134. else
  135. frontend_block=$(cat <<EOF
  136. # fwh-toolbox 前端 + API
  137. handle /fwh-toolbox/* {
  138. # API:/fwh-toolbox/api/* -> 后端 ${CADDY_BACKEND_PORT}(去掉前缀)
  139. handle_path /fwh-toolbox/api/* {
  140. reverse_proxy localhost:${CADDY_BACKEND_PORT}
  141. }
  142. # 其余:前端静态 ${CADDY_FRONTEND_PORT}
  143. reverse_proxy localhost:${CADDY_FRONTEND_PORT}
  144. }
  145. EOF
  146. )
  147. fi
  148. # 可选:Casdoor
  149. local casdoor_block=""
  150. if [[ "${CADDY_INCLUDE_CASDOOR}" == "y" ]]; then
  151. casdoor_block=$(cat <<EOF
  152. # casdoor login
  153. redir /casdoor /casdoor/ 301
  154. handle_path /casdoor/* {
  155. reverse_proxy localhost:${CADDY_CASDOOR_PORT}
  156. }
  157. EOF
  158. )
  159. fi
  160. # 可选:MinIO 控制台
  161. local minio_block=""
  162. if [[ "${CADDY_INCLUDE_MINIO}" == "y" ]]; then
  163. minio_block=$(cat <<EOF
  164. # minio 控制台
  165. redir /fwh-minio /fwh-minio/ 301
  166. handle_path /fwh-minio/* {
  167. reverse_proxy localhost:${CADDY_MINIO_CONSOLE_PORT}
  168. }
  169. EOF
  170. )
  171. fi
  172. # 写入站点文件(用 sudo tee,内容经 heredoc 组装)
  173. sudo tee "${site_file}" >/dev/null <<EOF
  174. ${CADDY_DOMAIN} {
  175. ${tls_line} encode zstd gzip
  176. # 前端 redirect(补斜杠)
  177. redir /fwh-toolbox /fwh-toolbox/ 301
  178. ${frontend_block}
  179. ${casdoor_block}
  180. ${minio_block}
  181. }
  182. EOF
  183. ok "已生成站点配置:${site_file}"
  184. }
  185. # ---------------------------------------------------------------------------
  186. # 4. 确保主 Caddyfile 引入 sites/*.caddy
  187. # ---------------------------------------------------------------------------
  188. ensure_import() {
  189. local import_line="import sites/*.caddy"
  190. if [[ ! -f "${CADDY_MAIN_FILE}" ]]; then
  191. info "主配置 ${CADDY_MAIN_FILE} 不存在,创建并写入 import 指令"
  192. sudo mkdir -p "$(dirname "${CADDY_MAIN_FILE}")"
  193. echo "${import_line}" | sudo tee "${CADDY_MAIN_FILE}" >/dev/null
  194. return 0
  195. fi
  196. if sudo grep -qE '^\s*import\s+sites/\*\.caddy' "${CADDY_MAIN_FILE}"; then
  197. ok "主配置已包含 import sites/*.caddy"
  198. else
  199. info "在主配置追加:${import_line}"
  200. echo "" | sudo tee -a "${CADDY_MAIN_FILE}" >/dev/null
  201. echo "${import_line}" | sudo tee -a "${CADDY_MAIN_FILE}" >/dev/null
  202. fi
  203. }
  204. # ---------------------------------------------------------------------------
  205. # 5. 校验并重载
  206. # ---------------------------------------------------------------------------
  207. validate_reload() {
  208. info "校验 Caddy 配置 ..."
  209. if sudo caddy validate --config "${CADDY_MAIN_FILE}" >/dev/null 2>&1; then
  210. ok "配置校验通过"
  211. else
  212. warn "配置校验未通过,输出详细信息:"
  213. sudo caddy validate --config "${CADDY_MAIN_FILE}" || true
  214. err "请检查后手动 reload:sudo systemctl reload caddy"
  215. return 1
  216. fi
  217. info "重载 Caddy ..."
  218. if sudo systemctl reload caddy >/dev/null 2>&1; then
  219. ok "Caddy 已重载"
  220. else
  221. warn "reload 失败,尝试 restart ..."
  222. sudo systemctl restart caddy && ok "Caddy 已重启" || { err "Caddy 启动失败,请查看 journalctl -u caddy"; return 1; }
  223. fi
  224. }
  225. main() {
  226. info "== 安装/配置 Caddy 反向代理 =="
  227. [[ -n "${CADDY_DOMAIN}" ]] || { err "必须提供 CADDY_DOMAIN(对外域名)"; exit 1; }
  228. ensure_caddy
  229. write_site
  230. ensure_import
  231. validate_reload
  232. cat <<EOF
  233. ${c_green}Caddy 反向代理配置完成${c_reset}
  234. 域名 : ${CADDY_DOMAIN}
  235. TLS 模式 : ${CADDY_TLS_MODE}
  236. 站点文件 : ${CADDY_SITES_DIR}/fwh-toolbox.caddy
  237. 前端入口 : https://${CADDY_DOMAIN}/fwh-toolbox/
  238. 后端 API : https://${CADDY_DOMAIN}/fwh-toolbox/api/ (-> localhost:${CADDY_BACKEND_PORT})
  239. $( [[ "${CADDY_INCLUDE_CASDOOR}" == "y" ]] && echo " Casdoor : https://${CADDY_DOMAIN}/casdoor/ (-> localhost:${CADDY_CASDOOR_PORT})" )
  240. $( [[ "${CADDY_INCLUDE_MINIO}" == "y" ]] && echo " MinIO 控制台 : https://${CADDY_DOMAIN}/fwh-minio/ (-> localhost:${CADDY_MINIO_CONSOLE_PORT})" )
  241. 详情参考 : deploy/DEPLOYMENT.md 第 9 章
  242. EOF
  243. }
  244. main "$@"