| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- #!/usr/bin/env bash
- # =============================================================================
- # FranklinWH Toolbox - Caddy 安装与站点配置脚本
- # =============================================================================
- # 作用:部署自编译 Caddy(含 spaceship DNS 插件)并配置本项目站点:
- # 1. 拷贝项目内自编译二进制 deploy/bin/caddy -> /usr/bin/caddy
- # 2. 安装/更新 systemd 服务(参考 caddy-new.service:注入 spaceship DNS token)
- # 3. 生成站点 Caddyfile(TLS 走 spaceship DNS-01,风格同线上 002-*.caddy):
- # - 前端静态:/fwh-toolbox/* -> localhost:${CADDY_FRONTEND_PORT}(8300)
- # - 后端 API :/fwh-toolbox/api/* -> localhost:${CADDY_BACKEND_PORT}(8301)
- # - (可选)Casdoor:/casdoor/* -> localhost:${CADDY_CASDOOR_PORT}(8010)
- # - (可选)MinIO 控制台:/fwh-minio/* -> localhost:${CADDY_MINIO_CONSOLE_PORT}(9199)
- #
- # 站点文件默认写入 /etc/caddy/sites/,并确保主 /etc/caddy/Caddyfile 里存在
- # `import sites/*.caddy`(若缺失则自动追加),最后 validate + reload。
- #
- # 前置条件:
- # - 项目内已放置自编译 caddy 二进制:deploy/bin/caddy(内置 dns.providers.spaceship)
- # - DNS 托管在 spaceship,并已申请 API key/secret
- #
- # 环境变量:
- # CADDY_DOMAIN 对外域名(必填,如 lmt.zenpower.cc)
- # CADDY_BIN_SRC 自编译二进制来源路径 (默认 <脚本目录>/../bin/caddy)
- # CADDY_BIN_DST 目标机二进制安装路径 (默认 /usr/bin/caddy)
- # CADDY_SERVICE_FILE systemd 服务文件路径 (默认 /lib/systemd/system/caddy.service)
- # LIBDNS_SPACESHIP_APIKEY spaceship DNS API Key(必填,写入 systemd 服务)
- # LIBDNS_SPACESHIP_APISECRET spaceship DNS API Secret(必填,写入 systemd 服务)
- # CADDY_DNS_RESOLVER DNS 传播检查解析器 (默认 8.8.8.8)
- # CADDY_SITES_DIR 站点片段目录 (默认 /etc/caddy/sites)
- # CADDY_MAIN_FILE 主配置文件 (默认 /etc/caddy/Caddyfile)
- # CADDY_FRONTEND_PORT 前端静态端口 (默认 8300)
- # CADDY_BACKEND_PORT 后端 API 端口 (默认 8301)
- # CADDY_INCLUDE_CASDOOR 是否加入 /casdoor 反代 (y/n) (默认 n)
- # CADDY_CASDOOR_PORT Casdoor 端口 (默认 8010)
- # CADDY_INCLUDE_MINIO 是否加入 /fwh-minio 反代 (y/n) (默认 n)
- # CADDY_MINIO_CONSOLE_PORT MinIO 控制台端口 (默认 9199)
- # CADDY_SERVE_STATIC 由 Caddy 直接 file_server 托管前端 (y/n) (默认 n)
- # CADDY_STATIC_ROOT file_server 的静态根目录 (默认 /var/www/fwh-toolbox/fwh-toolbox)
- # =============================================================================
- set -euo pipefail
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- CADDY_DOMAIN="${CADDY_DOMAIN:-}"
- CADDY_BIN_SRC="${CADDY_BIN_SRC:-${SCRIPT_DIR}/../bin/caddy}"
- CADDY_BIN_DST="${CADDY_BIN_DST:-/usr/bin/caddy}"
- CADDY_SERVICE_FILE="${CADDY_SERVICE_FILE:-/lib/systemd/system/caddy.service}"
- LIBDNS_SPACESHIP_APIKEY="${LIBDNS_SPACESHIP_APIKEY:-}"
- LIBDNS_SPACESHIP_APISECRET="${LIBDNS_SPACESHIP_APISECRET:-}"
- CADDY_DNS_RESOLVER="${CADDY_DNS_RESOLVER:-8.8.8.8}"
- CADDY_SITES_DIR="${CADDY_SITES_DIR:-/etc/caddy/sites}"
- CADDY_MAIN_FILE="${CADDY_MAIN_FILE:-/etc/caddy/Caddyfile}"
- CADDY_FRONTEND_PORT="${CADDY_FRONTEND_PORT:-8300}"
- CADDY_BACKEND_PORT="${CADDY_BACKEND_PORT:-8301}"
- CADDY_INCLUDE_CASDOOR="${CADDY_INCLUDE_CASDOOR:-n}"
- CADDY_CASDOOR_PORT="${CADDY_CASDOOR_PORT:-8010}"
- CADDY_INCLUDE_MINIO="${CADDY_INCLUDE_MINIO:-n}"
- CADDY_MINIO_CONSOLE_PORT="${CADDY_MINIO_CONSOLE_PORT:-9199}"
- CADDY_SERVE_STATIC="${CADDY_SERVE_STATIC:-n}"
- CADDY_STATIC_ROOT="${CADDY_STATIC_ROOT:-/var/www/fwh-toolbox/fwh-toolbox}"
- c_reset='\033[0m'; c_blue='\033[1;34m'; c_green='\033[1;32m'; c_yellow='\033[1;33m'; c_red='\033[1;31m'
- info() { echo -e "${c_blue}[INFO]${c_reset} $*"; }
- ok() { echo -e "${c_green}[ OK ]${c_reset} $*"; }
- warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; }
- err() { echo -e "${c_red}[FAIL]${c_reset} $*" >&2; }
- has() { command -v "$1" >/dev/null 2>&1; }
- # ---------------------------------------------------------------------------
- # 1. 部署自编译 Caddy 二进制(deploy/bin/caddy -> /usr/bin/caddy)
- # ---------------------------------------------------------------------------
- ensure_caddy() {
- if [[ ! -f "${CADDY_BIN_SRC}" ]]; then
- err "未找到自编译二进制:${CADDY_BIN_SRC}"
- err "请将含 spaceship 插件的 caddy 放到该路径(见 deploy/bin/caddy)。"
- return 1
- fi
- info "部署自编译 Caddy:${CADDY_BIN_SRC} -> ${CADDY_BIN_DST}"
- # 若正在运行,先停以便覆盖二进制
- sudo systemctl stop caddy >/dev/null 2>&1 || true
- sudo install -m 0755 "${CADDY_BIN_SRC}" "${CADDY_BIN_DST}"
- # 校验必需的 spaceship DNS 模块存在
- if ! "${CADDY_BIN_DST}" list-modules 2>/dev/null | grep -q '^dns.providers.spaceship$'; then
- err "已安装的 caddy 缺少 dns.providers.spaceship 模块,无法用 spaceship DNS 签证书。"
- return 1
- fi
- ok "Caddy 就绪:$(${CADDY_BIN_DST} version 2>/dev/null | head -n1)"
- }
- # ---------------------------------------------------------------------------
- # 1b. 安装/更新 systemd 服务(注入 spaceship DNS token)
- # 参考 caddy-new.service:User=caddy,run --environ,Environment 注入 token
- # ---------------------------------------------------------------------------
- install_service() {
- [[ -n "${LIBDNS_SPACESHIP_APIKEY}" ]] || { err "缺少 LIBDNS_SPACESHIP_APIKEY"; return 1; }
- [[ -n "${LIBDNS_SPACESHIP_APISECRET}" ]] || { err "缺少 LIBDNS_SPACESHIP_APISECRET"; return 1; }
- # 确保 caddy 用户存在
- id caddy >/dev/null 2>&1 || sudo useradd --system --home /var/lib/caddy --shell /usr/sbin/nologin caddy || true
- sudo mkdir -p /var/log/caddy /var/lib/caddy
- sudo chown -R caddy:caddy /var/log/caddy /var/lib/caddy 2>/dev/null || true
- info "写入 systemd 服务:${CADDY_SERVICE_FILE}"
- sudo tee "${CADDY_SERVICE_FILE}" >/dev/null <<EOF
- # caddy.service —— 由 install_caddy.sh 生成(自编译 caddy + spaceship DNS)
- [Unit]
- Description=Caddy
- Documentation=https://caddyserver.com/docs/
- After=network.target network-online.target
- Requires=network-online.target
- [Service]
- Environment="LIBDNS_SPACESHIP_APIKEY=${LIBDNS_SPACESHIP_APIKEY}"
- Environment="LIBDNS_SPACESHIP_APISECRET=${LIBDNS_SPACESHIP_APISECRET}"
- Type=notify
- User=caddy
- Group=caddy
- ExecStart=${CADDY_BIN_DST} run --environ --config ${CADDY_MAIN_FILE}
- ExecReload=${CADDY_BIN_DST} reload --config ${CADDY_MAIN_FILE} --force
- TimeoutStopSec=5s
- LimitNOFILE=1048576
- PrivateTmp=true
- ProtectSystem=full
- AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
- [Install]
- WantedBy=multi-user.target
- EOF
- # 服务文件含明文 token,收紧权限
- sudo chmod 600 "${CADDY_SERVICE_FILE}"
- sudo systemctl daemon-reload
- sudo systemctl enable caddy >/dev/null 2>&1 || true
- ok "systemd 服务已安装(token 已注入,文件权限 600)"
- }
- # ---------------------------------------------------------------------------
- # 2. 组装 tls 指令块(spaceship DNS-01,风格同线上 002-*.caddy)
- # token 通过 systemd Environment 注入,此处用 {env.XXX} 引用,不写明文。
- # ---------------------------------------------------------------------------
- _tls_block() {
- cat <<EOF
- tls {
- dns spaceship {
- api_key {env.LIBDNS_SPACESHIP_APIKEY}
- api_secret {env.LIBDNS_SPACESHIP_APISECRET}
- }
- propagation_timeout 10m
- propagation_delay 5m
- resolvers ${CADDY_DNS_RESOLVER}
- }
- EOF
- }
- # ---------------------------------------------------------------------------
- # 3. 生成站点 Caddyfile
- # ---------------------------------------------------------------------------
- write_site() {
- [[ -n "${CADDY_DOMAIN}" ]] || { err "CADDY_DOMAIN 不能为空"; return 1; }
- sudo mkdir -p "${CADDY_SITES_DIR}"
- local site_file="${CADDY_SITES_DIR}/fwh-toolbox.caddy"
- local tls_block; tls_block="$(_tls_block)" || return 1
- info "生成站点配置:${site_file}"
- # 前端处理块:file_server 直托管 或 反代到静态端口
- local frontend_block
- if [[ "${CADDY_SERVE_STATIC}" == "y" ]]; then
- frontend_block=$(cat <<EOF
- # 前端静态:由 Caddy 直接托管
- reverse_proxy /fwh-toolbox/api/* localhost:${CADDY_BACKEND_PORT}
- handle_path /fwh-toolbox/* {
- root * ${CADDY_STATIC_ROOT}
- try_files {path} /index.html
- file_server
- }
- EOF
- )
- else
- frontend_block=$(cat <<EOF
- # fwh-toolbox 前端 + API
- handle /fwh-toolbox/* {
- # API:/fwh-toolbox/api/* -> 后端 ${CADDY_BACKEND_PORT}(去掉前缀)
- handle_path /fwh-toolbox/api/* {
- reverse_proxy localhost:${CADDY_BACKEND_PORT}
- }
- # 其余:前端静态 ${CADDY_FRONTEND_PORT}
- reverse_proxy localhost:${CADDY_FRONTEND_PORT}
- }
- EOF
- )
- fi
- # 可选:Casdoor
- local casdoor_block=""
- if [[ "${CADDY_INCLUDE_CASDOOR}" == "y" ]]; then
- casdoor_block=$(cat <<EOF
- # casdoor login
- redir /casdoor /casdoor/ 301
- handle_path /casdoor/* {
- reverse_proxy localhost:${CADDY_CASDOOR_PORT}
- }
- EOF
- )
- fi
- # 可选:MinIO 控制台
- local minio_block=""
- if [[ "${CADDY_INCLUDE_MINIO}" == "y" ]]; then
- minio_block=$(cat <<EOF
- # minio 控制台
- redir /fwh-minio /fwh-minio/ 301
- handle_path /fwh-minio/* {
- reverse_proxy localhost:${CADDY_MINIO_CONSOLE_PORT}
- }
- EOF
- )
- fi
- # 写入站点文件(用 sudo tee,内容经 heredoc 组装)
- sudo tee "${site_file}" >/dev/null <<EOF
- ${CADDY_DOMAIN} {
- ${tls_block} encode zstd gzip
- # 前端 redirect(补斜杠)
- redir /fwh-toolbox /fwh-toolbox/ 301
- ${frontend_block}
- ${casdoor_block}
- ${minio_block}
- }
- EOF
- ok "已生成站点配置:${site_file}"
- }
- # ---------------------------------------------------------------------------
- # 4. 确保主 Caddyfile 引入 sites/*.caddy
- # ---------------------------------------------------------------------------
- ensure_import() {
- local import_line="import sites/*.caddy"
- if [[ ! -f "${CADDY_MAIN_FILE}" ]]; then
- info "主配置 ${CADDY_MAIN_FILE} 不存在,创建并写入 import 指令"
- sudo mkdir -p "$(dirname "${CADDY_MAIN_FILE}")"
- echo "${import_line}" | sudo tee "${CADDY_MAIN_FILE}" >/dev/null
- return 0
- fi
- if sudo grep -qE '^\s*import\s+sites/\*\.caddy' "${CADDY_MAIN_FILE}"; then
- ok "主配置已包含 import sites/*.caddy"
- else
- info "在主配置追加:${import_line}"
- echo "" | sudo tee -a "${CADDY_MAIN_FILE}" >/dev/null
- echo "${import_line}" | sudo tee -a "${CADDY_MAIN_FILE}" >/dev/null
- fi
- }
- # ---------------------------------------------------------------------------
- # 5. 校验并重载
- # ---------------------------------------------------------------------------
- validate_reload() {
- info "校验 Caddy 配置 ..."
- if sudo "${CADDY_BIN_DST}" validate --config "${CADDY_MAIN_FILE}" >/dev/null 2>&1; then
- ok "配置校验通过"
- else
- warn "配置校验未通过,输出详细信息:"
- sudo "${CADDY_BIN_DST}" validate --config "${CADDY_MAIN_FILE}" || true
- err "请检查后手动 reload:sudo systemctl reload caddy"
- return 1
- fi
- info "重载 Caddy ..."
- if sudo systemctl reload caddy >/dev/null 2>&1; then
- ok "Caddy 已重载"
- else
- warn "reload 失败,尝试 restart ..."
- sudo systemctl restart caddy && ok "Caddy 已重启" || { err "Caddy 启动失败,请查看 journalctl -u caddy"; return 1; }
- fi
- }
- main() {
- info "== 部署自编译 Caddy 反向代理 =="
- [[ -n "${CADDY_DOMAIN}" ]] || { err "必须提供 CADDY_DOMAIN(对外域名)"; exit 1; }
- ensure_caddy
- install_service
- write_site
- ensure_import
- validate_reload
- cat <<EOF
- ${c_green}Caddy 反向代理配置完成${c_reset}
- 域名 : ${CADDY_DOMAIN}
- 二进制 : ${CADDY_BIN_DST}(自编译,含 spaceship DNS 插件)
- systemd 服务 : ${CADDY_SERVICE_FILE}
- TLS : spaceship DNS-01(token 经 systemd Environment 注入)
- 站点文件 : ${CADDY_SITES_DIR}/fwh-toolbox.caddy
- 前端入口 : https://${CADDY_DOMAIN}/fwh-toolbox/
- 后端 API : https://${CADDY_DOMAIN}/fwh-toolbox/api/ (-> localhost:${CADDY_BACKEND_PORT})
- $( [[ "${CADDY_INCLUDE_CASDOOR}" == "y" ]] && echo " Casdoor : https://${CADDY_DOMAIN}/casdoor/ (-> localhost:${CADDY_CASDOOR_PORT})" )
- $( [[ "${CADDY_INCLUDE_MINIO}" == "y" ]] && echo " MinIO 控制台 : https://${CADDY_DOMAIN}/fwh-minio/ (-> localhost:${CADDY_MINIO_CONSOLE_PORT})" )
- 提示 : systemd 服务含明文 DNS token,已设权限 600;勿提交到仓库。
- 详情参考 : deploy/DEPLOYMENT.md 第 9 章
- EOF
- }
- main "$@"
|