# 部署与运维 ## 配置 Docker Compose 从项目根目录 `.env` 读取以下值: | 变量 | 说明 | 默认值 | | --- | --- | --- | | `FEEDBACK_DOMAIN` | Caddy 申请证书并对外服务的域名 | 必填 | | `APP_SECRET` | 会话、IP 和安装标识 digest 的主 secret,至少 32 字符 | 必填 | | `SESSION_TTL_HOURS` | 管理员会话时长 | `12` | | `PUBLIC_RATE_LIMIT_PER_HOUR` | 单 IP 每小时公开提交上限 | `20` | | `PUBLIC_INSTALLATION_LIMIT_PER_HOUR` | 单安装实例每小时公开提交上限 | `8` | | `MAX_SCREENSHOT_BYTES` | 输入及标准化后截图的字节上限 | `5242880` | `.env` 包含 secret,不得提交到 Git 或复制到工单、聊天和日志中。建议在 密码管理器中保存一份加密副本,并在主机上执行 `chmod 600 .env`。 Compose 会从 `FEEDBACK_DOMAIN` 生成 `PUBLIC_ORIGIN=https://FEEDBACK_DOMAIN`。直接运行 Node 服务时, production 环境仍必须显式提供只含 HTTPS origin 的 `PUBLIC_ORIGIN`。 ## 网络与 TLS - DNS A/AAAA 记录必须指向主机。 - 入站开放 TCP 80、TCP 443 和 UDP 443。 - 不要直接发布应用容器的 3000 端口。 - Caddy 自动申请并续期证书。 - `TRUST_PROXY=true` 仅用于当前 Caddy → app 的受控容器网络拓扑;改变 代理层级时必须重新检查真实客户端 IP 解析。 验证: ```sh docker compose ps curl --fail "https://feedback.example.com/health" curl -I "https://feedback.example.com/admin/" ``` 将示例域名替换为 `.env` 中的真实 `FEEDBACK_DOMAIN`。 ## 管理员 创建或重置管理员: ```sh read -rsp "Administrator password: " PRODUCT_ISSUE_ADMIN_PASSWORD echo export PRODUCT_ISSUE_ADMIN_PASSWORD docker compose run --rm \ -e PRODUCT_ISSUE_ADMIN_PASSWORD \ app node dist/server/server/cli/create-admin.js \ --email admin@example.com \ --name "MesaLogo Admin" unset PRODUCT_ISSUE_ADMIN_PASSWORD ``` 密码必须为 12–128 字符。相同邮箱再次执行会更新显示名和密码、重新启用 管理员,并删除该管理员的所有既有会话。 ## 备份 应用数据全部位于 `feedback-data` volume: - `product-issue.sqlite` 以及可能存在的 `-wal` / `-shm` - `attachments//screenshot.png` 为获得一致快照,备份时短暂停止 app: ```sh umask 077 STAMP="$(date -u +%Y%m%dT%H%M%SZ)" mkdir -p "backups/${STAMP}" docker compose stop app docker compose cp app:/app/data/. "backups/${STAMP}/" docker compose start app tar -C backups -czf "backups/product-issue-${STAMP}.tar.gz" "${STAMP}" ``` 同时安全备份 `.env` 中的生产配置,但应与数据归档分开加密保存。不要只 备份 SQLite 主文件而遗漏 WAL 或 attachments。 建议至少每日备份,并定期把加密副本同步到另一台主机或对象存储。定期在 隔离环境执行恢复演练,而不是只检查归档文件存在。 ## 恢复 恢复会替换当前 volume 内容。先保存当前数据并确认选中的归档: ```sh mkdir -p restore tar -C restore -xzf backups/product-issue-YYYYMMDDTHHMMSSZ.tar.gz docker compose stop app docker compose run --rm --no-deps app sh -c \ 'find /app/data -mindepth 1 -maxdepth 1 -exec rm -rf {} +' docker compose run --rm --no-deps \ -v "$PWD/restore/YYYYMMDDTHHMMSSZ:/restore:ro" \ app sh -c 'cp -R /restore/. /app/data/' docker compose start app ``` 随后检查: ```sh docker compose logs --tail=100 app curl --fail "https://feedback.example.com/health" ``` 登录后台并抽查产品、反馈、备注和截图。 ## 升级 1. 创建并验证当前备份。 2. 获取经过审核的新版本源码。 3. 运行 `docker compose build --pull`。 4. 运行 `docker compose up -d`。 5. 检查 health、容器日志、管理员登录和一条测试反馈。 数据库迁移在应用启动时执行,采用可重复的 `CREATE TABLE/INDEX IF NOT EXISTS`。未来出现结构变更时,仍应先备份再升级。 ## Secret 轮换 更新 `.env` 中的 `APP_SECRET`,然后重建 app: ```sh docker compose up -d --force-recreate app ``` 轮换会让所有管理员会话失效,并使后续 IP/安装标识使用新的 digest。 数据库中的反馈、备注和截图不依赖该 secret 解密,因此仍可读取。 ## 监控与故障排查 ```sh docker compose ps docker compose logs --tail=200 app docker compose logs --tail=200 caddy docker compose exec app node -e \ "fetch('http://127.0.0.1:3000/health').then(async r=>console.log(r.status,await r.text()))" ``` 应用关闭了常规逐请求访问日志,避免把公开提交者的原始 IP 持久写入应用 日志。拒绝和内部错误使用请求 ID 记录,不记录反馈正文、邮箱、截图内容、 Cookie、CSRF token 或 secret。 ## 数据保留 当前版本不自动删除反馈或附件。部署方应根据实际支持流程定义保留期限, 并将数据库行和对应附件视为同一条记录处理。任何直接数据库维护都必须先 停止写入、完成备份,并在隔离副本上验证。