install_caddy.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/usr/bin/env bash
  2. # =============================================================================
  3. # FranklinWH Toolbox - Caddy 安装与站点配置脚本
  4. # =============================================================================
  5. # 作用:部署自编译 Caddy(含 spaceship DNS 插件)并配置本项目站点:
  6. # 1. 拷贝项目内自编译二进制 deploy/bin/caddy -> /usr/bin/caddy
  7. # 2. 安装/更新 systemd 服务(参考 caddy-new.service:注入 spaceship DNS token)
  8. # 3. 生成站点 Caddyfile(TLS 走 spaceship DNS-01,风格同线上 002-*.caddy):
  9. # - 前端静态:/fwh-toolbox/* -> localhost:${CADDY_FRONTEND_PORT}(8300)
  10. # - 后端 API :/fwh-toolbox/api/* -> localhost:${CADDY_BACKEND_PORT}(8301)
  11. # - (可选)Casdoor:/casdoor/* -> localhost:${CADDY_CASDOOR_PORT}(8010)
  12. # - (可选)MinIO 控制台:/fwh-minio/* -> localhost:${CADDY_MINIO_CONSOLE_PORT}(9199)
  13. #
  14. # 站点文件默认写入 /etc/caddy/sites/,并确保主 /etc/caddy/Caddyfile 里存在
  15. # `import sites/*.caddy`(若缺失则自动追加),最后 validate + reload。
  16. #
  17. # 前置条件:
  18. # - 项目内已放置自编译 caddy 二进制:deploy/bin/caddy(内置 dns.providers.spaceship)
  19. # - DNS 托管在 spaceship,并已申请 API key/secret
  20. #
  21. # 环境变量:
  22. # CADDY_DOMAIN 对外域名(必填,如 lmt.zenpower.cc)
  23. # CADDY_BIN_SRC 自编译二进制来源路径 (默认 <脚本目录>/../bin/caddy)
  24. # CADDY_BIN_DST 目标机二进制安装路径 (默认 /usr/bin/caddy)
  25. # CADDY_SERVICE_FILE systemd 服务文件路径 (默认 /lib/systemd/system/caddy.service)
  26. # LIBDNS_SPACESHIP_APIKEY spaceship DNS API Key(必填,写入 systemd 服务)
  27. # LIBDNS_SPACESHIP_APISECRET spaceship DNS API Secret(必填,写入 systemd 服务)
  28. # CADDY_DNS_RESOLVER DNS 传播检查解析器 (默认 8.8.8.8)
  29. # CADDY_SITES_DIR 站点片段目录 (默认 /etc/caddy/sites)
  30. # CADDY_MAIN_FILE 主配置文件 (默认 /etc/caddy/Caddyfile)
  31. # CADDY_FRONTEND_PORT 前端静态端口 (默认 8300)
  32. # CADDY_BACKEND_PORT 后端 API 端口 (默认 8301)
  33. # CADDY_INCLUDE_CASDOOR 是否加入 /casdoor 反代 (y/n) (默认 n)
  34. # CADDY_CASDOOR_PORT Casdoor 端口 (默认 8010)
  35. # CADDY_INCLUDE_MINIO 是否加入 /fwh-minio 反代 (y/n) (默认 n)
  36. # CADDY_MINIO_CONSOLE_PORT MinIO 控制台端口 (默认 9199)
  37. # CADDY_SERVE_STATIC 由 Caddy 直接 file_server 托管前端 (y/n) (默认 n)
  38. # CADDY_STATIC_ROOT file_server 的静态根目录 (默认 /var/www/fwh-toolbox/fwh-toolbox)
  39. # =============================================================================
  40. set -euo pipefail
  41. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  42. CADDY_DOMAIN="${CADDY_DOMAIN:-}"
  43. CADDY_BIN_SRC="${CADDY_BIN_SRC:-${SCRIPT_DIR}/../bin/caddy}"
  44. CADDY_BIN_DST="${CADDY_BIN_DST:-/usr/bin/caddy}"
  45. CADDY_SERVICE_FILE="${CADDY_SERVICE_FILE:-/lib/systemd/system/caddy.service}"
  46. LIBDNS_SPACESHIP_APIKEY="${LIBDNS_SPACESHIP_APIKEY:-}"
  47. LIBDNS_SPACESHIP_APISECRET="${LIBDNS_SPACESHIP_APISECRET:-}"
  48. CADDY_DNS_RESOLVER="${CADDY_DNS_RESOLVER:-8.8.8.8}"
  49. CADDY_SITES_DIR="${CADDY_SITES_DIR:-/etc/caddy/sites}"
  50. CADDY_MAIN_FILE="${CADDY_MAIN_FILE:-/etc/caddy/Caddyfile}"
  51. CADDY_FRONTEND_PORT="${CADDY_FRONTEND_PORT:-8300}"
  52. CADDY_BACKEND_PORT="${CADDY_BACKEND_PORT:-8301}"
  53. CADDY_INCLUDE_CASDOOR="${CADDY_INCLUDE_CASDOOR:-n}"
  54. CADDY_CASDOOR_PORT="${CADDY_CASDOOR_PORT:-8010}"
  55. CADDY_INCLUDE_MINIO="${CADDY_INCLUDE_MINIO:-n}"
  56. CADDY_MINIO_CONSOLE_PORT="${CADDY_MINIO_CONSOLE_PORT:-9199}"
  57. CADDY_SERVE_STATIC="${CADDY_SERVE_STATIC:-n}"
  58. CADDY_STATIC_ROOT="${CADDY_STATIC_ROOT:-/var/www/fwh-toolbox/fwh-toolbox}"
  59. c_reset='\033[0m'; c_blue='\033[1;34m'; c_green='\033[1;32m'; c_yellow='\033[1;33m'; c_red='\033[1;31m'
  60. info() { echo -e "${c_blue}[INFO]${c_reset} $*"; }
  61. ok() { echo -e "${c_green}[ OK ]${c_reset} $*"; }
  62. warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; }
  63. err() { echo -e "${c_red}[FAIL]${c_reset} $*" >&2; }
  64. has() { command -v "$1" >/dev/null 2>&1; }
  65. # ---------------------------------------------------------------------------
  66. # 1. 部署自编译 Caddy 二进制(deploy/bin/caddy -> /usr/bin/caddy)
  67. # ---------------------------------------------------------------------------
  68. ensure_caddy() {
  69. if [[ ! -f "${CADDY_BIN_SRC}" ]]; then
  70. err "未找到自编译二进制:${CADDY_BIN_SRC}"
  71. err "请将含 spaceship 插件的 caddy 放到该路径(见 deploy/bin/caddy)。"
  72. return 1
  73. fi
  74. info "部署自编译 Caddy:${CADDY_BIN_SRC} -> ${CADDY_BIN_DST}"
  75. # 若正在运行,先停以便覆盖二进制
  76. sudo systemctl stop caddy >/dev/null 2>&1 || true
  77. sudo install -m 0755 "${CADDY_BIN_SRC}" "${CADDY_BIN_DST}"
  78. # 校验必需的 spaceship DNS 模块存在
  79. if ! "${CADDY_BIN_DST}" list-modules 2>/dev/null | grep -q '^dns.providers.spaceship$'; then
  80. err "已安装的 caddy 缺少 dns.providers.spaceship 模块,无法用 spaceship DNS 签证书。"
  81. return 1
  82. fi
  83. ok "Caddy 就绪:$(${CADDY_BIN_DST} version 2>/dev/null | head -n1)"
  84. }
  85. # ---------------------------------------------------------------------------
  86. # 1b. 安装/更新 systemd 服务(注入 spaceship DNS token)
  87. # 参考 caddy-new.service:User=caddy,run --environ,Environment 注入 token
  88. # ---------------------------------------------------------------------------
  89. install_service() {
  90. [[ -n "${LIBDNS_SPACESHIP_APIKEY}" ]] || { err "缺少 LIBDNS_SPACESHIP_APIKEY"; return 1; }
  91. [[ -n "${LIBDNS_SPACESHIP_APISECRET}" ]] || { err "缺少 LIBDNS_SPACESHIP_APISECRET"; return 1; }
  92. # 确保 caddy 用户存在
  93. id caddy >/dev/null 2>&1 || sudo useradd --system --home /var/lib/caddy --shell /usr/sbin/nologin caddy || true
  94. sudo mkdir -p /var/log/caddy /var/lib/caddy
  95. sudo chown -R caddy:caddy /var/log/caddy /var/lib/caddy 2>/dev/null || true
  96. info "写入 systemd 服务:${CADDY_SERVICE_FILE}"
  97. sudo tee "${CADDY_SERVICE_FILE}" >/dev/null <<EOF
  98. # caddy.service —— 由 install_caddy.sh 生成(自编译 caddy + spaceship DNS)
  99. [Unit]
  100. Description=Caddy
  101. Documentation=https://caddyserver.com/docs/
  102. After=network.target network-online.target
  103. Requires=network-online.target
  104. [Service]
  105. Environment="LIBDNS_SPACESHIP_APIKEY=${LIBDNS_SPACESHIP_APIKEY}"
  106. Environment="LIBDNS_SPACESHIP_APISECRET=${LIBDNS_SPACESHIP_APISECRET}"
  107. Type=notify
  108. User=caddy
  109. Group=caddy
  110. ExecStart=${CADDY_BIN_DST} run --environ --config ${CADDY_MAIN_FILE}
  111. ExecReload=${CADDY_BIN_DST} reload --config ${CADDY_MAIN_FILE} --force
  112. TimeoutStopSec=5s
  113. LimitNOFILE=1048576
  114. PrivateTmp=true
  115. ProtectSystem=full
  116. AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
  117. [Install]
  118. WantedBy=multi-user.target
  119. EOF
  120. # 服务文件含明文 token,收紧权限
  121. sudo chmod 600 "${CADDY_SERVICE_FILE}"
  122. sudo systemctl daemon-reload
  123. sudo systemctl enable caddy >/dev/null 2>&1 || true
  124. ok "systemd 服务已安装(token 已注入,文件权限 600)"
  125. }
  126. # ---------------------------------------------------------------------------
  127. # 2. 组装 tls 指令块(spaceship DNS-01,风格同线上 002-*.caddy)
  128. # token 通过 systemd Environment 注入,此处用 {env.XXX} 引用,不写明文。
  129. # ---------------------------------------------------------------------------
  130. _tls_block() {
  131. cat <<EOF
  132. tls {
  133. dns spaceship {
  134. api_key {env.LIBDNS_SPACESHIP_APIKEY}
  135. api_secret {env.LIBDNS_SPACESHIP_APISECRET}
  136. }
  137. propagation_timeout 10m
  138. propagation_delay 5m
  139. resolvers ${CADDY_DNS_RESOLVER}
  140. }
  141. EOF
  142. }
  143. # ---------------------------------------------------------------------------
  144. # 3. 生成站点 Caddyfile
  145. # ---------------------------------------------------------------------------
  146. write_site() {
  147. [[ -n "${CADDY_DOMAIN}" ]] || { err "CADDY_DOMAIN 不能为空"; return 1; }
  148. sudo mkdir -p "${CADDY_SITES_DIR}"
  149. local site_file="${CADDY_SITES_DIR}/fwh-toolbox.caddy"
  150. local tls_block; tls_block="$(_tls_block)" || return 1
  151. info "生成站点配置:${site_file}"
  152. # 前端处理块:file_server 直托管 或 反代到静态端口
  153. local frontend_block
  154. if [[ "${CADDY_SERVE_STATIC}" == "y" ]]; then
  155. frontend_block=$(cat <<EOF
  156. # 前端静态:由 Caddy 直接托管
  157. reverse_proxy /fwh-toolbox/api/* localhost:${CADDY_BACKEND_PORT}
  158. handle_path /fwh-toolbox/* {
  159. root * ${CADDY_STATIC_ROOT}
  160. try_files {path} /index.html
  161. file_server
  162. }
  163. EOF
  164. )
  165. else
  166. frontend_block=$(cat <<EOF
  167. # fwh-toolbox 前端 + API
  168. handle /fwh-toolbox/* {
  169. # API:/fwh-toolbox/api/* -> 后端 ${CADDY_BACKEND_PORT}(去掉前缀)
  170. handle_path /fwh-toolbox/api/* {
  171. reverse_proxy localhost:${CADDY_BACKEND_PORT}
  172. }
  173. # 其余:前端静态 ${CADDY_FRONTEND_PORT}
  174. reverse_proxy localhost:${CADDY_FRONTEND_PORT}
  175. }
  176. EOF
  177. )
  178. fi
  179. # 可选:Casdoor
  180. local casdoor_block=""
  181. if [[ "${CADDY_INCLUDE_CASDOOR}" == "y" ]]; then
  182. casdoor_block=$(cat <<EOF
  183. # casdoor login
  184. redir /casdoor /casdoor/ 301
  185. handle_path /casdoor/* {
  186. reverse_proxy localhost:${CADDY_CASDOOR_PORT}
  187. }
  188. EOF
  189. )
  190. fi
  191. # 可选:MinIO 控制台
  192. local minio_block=""
  193. if [[ "${CADDY_INCLUDE_MINIO}" == "y" ]]; then
  194. minio_block=$(cat <<EOF
  195. # minio 控制台
  196. redir /fwh-minio /fwh-minio/ 301
  197. handle_path /fwh-minio/* {
  198. reverse_proxy localhost:${CADDY_MINIO_CONSOLE_PORT}
  199. }
  200. EOF
  201. )
  202. fi
  203. # 写入站点文件(用 sudo tee,内容经 heredoc 组装)
  204. sudo tee "${site_file}" >/dev/null <<EOF
  205. ${CADDY_DOMAIN} {
  206. ${tls_block} encode zstd gzip
  207. # 前端 redirect(补斜杠)
  208. redir /fwh-toolbox /fwh-toolbox/ 301
  209. ${frontend_block}
  210. ${casdoor_block}
  211. ${minio_block}
  212. }
  213. EOF
  214. ok "已生成站点配置:${site_file}"
  215. }
  216. # ---------------------------------------------------------------------------
  217. # 4. 确保主 Caddyfile 引入 sites/*.caddy
  218. # ---------------------------------------------------------------------------
  219. ensure_import() {
  220. local import_line="import sites/*.caddy"
  221. if [[ ! -f "${CADDY_MAIN_FILE}" ]]; then
  222. info "主配置 ${CADDY_MAIN_FILE} 不存在,创建并写入 import 指令"
  223. sudo mkdir -p "$(dirname "${CADDY_MAIN_FILE}")"
  224. echo "${import_line}" | sudo tee "${CADDY_MAIN_FILE}" >/dev/null
  225. return 0
  226. fi
  227. if sudo grep -qE '^\s*import\s+sites/\*\.caddy' "${CADDY_MAIN_FILE}"; then
  228. ok "主配置已包含 import sites/*.caddy"
  229. else
  230. info "在主配置追加:${import_line}"
  231. echo "" | sudo tee -a "${CADDY_MAIN_FILE}" >/dev/null
  232. echo "${import_line}" | sudo tee -a "${CADDY_MAIN_FILE}" >/dev/null
  233. fi
  234. }
  235. # ---------------------------------------------------------------------------
  236. # 5. 校验并重载
  237. # ---------------------------------------------------------------------------
  238. validate_reload() {
  239. info "校验 Caddy 配置 ..."
  240. if sudo "${CADDY_BIN_DST}" validate --config "${CADDY_MAIN_FILE}" >/dev/null 2>&1; then
  241. ok "配置校验通过"
  242. else
  243. warn "配置校验未通过,输出详细信息:"
  244. sudo "${CADDY_BIN_DST}" validate --config "${CADDY_MAIN_FILE}" || true
  245. err "请检查后手动 reload:sudo systemctl reload caddy"
  246. return 1
  247. fi
  248. info "重载 Caddy ..."
  249. if sudo systemctl reload caddy >/dev/null 2>&1; then
  250. ok "Caddy 已重载"
  251. else
  252. warn "reload 失败,尝试 restart ..."
  253. sudo systemctl restart caddy && ok "Caddy 已重启" || { err "Caddy 启动失败,请查看 journalctl -u caddy"; return 1; }
  254. fi
  255. }
  256. main() {
  257. info "== 部署自编译 Caddy 反向代理 =="
  258. [[ -n "${CADDY_DOMAIN}" ]] || { err "必须提供 CADDY_DOMAIN(对外域名)"; exit 1; }
  259. ensure_caddy
  260. install_service
  261. write_site
  262. ensure_import
  263. validate_reload
  264. cat <<EOF
  265. ${c_green}Caddy 反向代理配置完成${c_reset}
  266. 域名 : ${CADDY_DOMAIN}
  267. 二进制 : ${CADDY_BIN_DST}(自编译,含 spaceship DNS 插件)
  268. systemd 服务 : ${CADDY_SERVICE_FILE}
  269. TLS : spaceship DNS-01(token 经 systemd Environment 注入)
  270. 站点文件 : ${CADDY_SITES_DIR}/fwh-toolbox.caddy
  271. 前端入口 : https://${CADDY_DOMAIN}/fwh-toolbox/
  272. 后端 API : https://${CADDY_DOMAIN}/fwh-toolbox/api/ (-> localhost:${CADDY_BACKEND_PORT})
  273. $( [[ "${CADDY_INCLUDE_CASDOOR}" == "y" ]] && echo " Casdoor : https://${CADDY_DOMAIN}/casdoor/ (-> localhost:${CADDY_CASDOOR_PORT})" )
  274. $( [[ "${CADDY_INCLUDE_MINIO}" == "y" ]] && echo " MinIO 控制台 : https://${CADDY_DOMAIN}/fwh-minio/ (-> localhost:${CADDY_MINIO_CONSOLE_PORT})" )
  275. 提示 : systemd 服务含明文 DNS token,已设权限 600;勿提交到仓库。
  276. 详情参考 : deploy/DEPLOYMENT.md 第 9 章
  277. EOF
  278. }
  279. main "$@"