Files
server-deploy/docs/2026-09-24-deployment-architecture-review.md
T

26 KiB
Raw Blame History

部署架构审查与管理工具接入建议

日期:2026-09-24。状态:历史审查与讨论记录,尚未实施。用户已明确以全新服务器为目标,不需要渐进改造或存量兼容;本文的源码发现仍供参考,涉及保留旧结构、接管与过渡层的建议已撤回。当前候选架构以 全新部署架构 为准。

1. 范围与结论

本次检查本仓库全部顶层目录、7 份 Compose、共享基础脚本、各工具部署/备份/卸载入口、Gitea/Vaultwarden 升级逻辑、Nginx 模板与已有测试目录。参考项目为 G:/Works/YouleGames/tools/docker-ops-panel,检查其文档、连接与身份核验、任务执行、服务清单、会话校验和打包结构。

这是本地源码与架构审查,没有连接生产服务器,没有执行部署、恢复、清理、安装或迁移;不把历史会话的线上结果当作当前生产状态。没有读取实际 .env 内容;在普通源码中发现硬编码凭据,本报告不收录其值。

结论:目前应用运行单元已基本分离,主要问题是部署生命周期仍耦合主机环境,缺少稳定的实例身份、操作协议和恢复边界。推荐保留“一工具一独立部署单元”,先规范脚本,再建立可选的本地 SSH 管理面板。无需合并成一份总 Compose,也无需为此引入 Kubernetes。

2. 当前目录与运行单元

  • base/:Docker、Nginx、Certbot、防火墙、系统初始化及共享 Shell 函数;同时承担安装程序和公共库两种职责。
  • gitea/:独立 Compose,应用 + 私有 MySQL 8.4;含部署、备份、升级、卸载及 Nginx 模板。
  • joplin/:独立 Compose,应用 + 私有 PostgreSQL;含部署、备份、卸载及 Nginx 模板。
  • vaultwarden/:独立 Compose,应用持久数据;含部署、备份、升级、卸载及 Nginx 模板。
  • certd/、siyuan/、portainer/:各自独立 Compose、数据目录和运维脚本。
  • rustdesk/:独立 Compose,hbbs + hbbr 组成一个工具,共享该工具的数据目录,包含公网 TCP/UDP 和回环 WebSocket 入口。
  • vps-xray/:独立 systemd 部署,不使用 Compose;另含内核网络参数、防火墙、定时重启及客户端配置生成。应支持不同主机部署。
  • claude/:开发机 CLI 配置脚本,不是 Linux 服务器应用栈。
  • claude-dev-stack/:用户已明确废弃,目录与其中唯一的 .env 已从工作区删除,不纳入工具目录、管理界面、适配器或部署计划;历史版本仍保留该文件。
  • docs/:已有 Xray 设计与测试计划。
  • .claude/:本地辅助目录,不作为发布单元。

当前 Web 路径为:宿主 Nginx → 各应用的回环映射端口。MySQL/PostgreSQL 没有发布宿主端口。应用之间未发现业务依赖。Gitea SSH、RustDesk TCP/UDP 为公网直达例外。Certd 是独立证书管理应用,当前脚本中的站点证书仍由 Certbot 处理,不能认为 Certd 已接管所有证书。

3. 主要发现及证据

P0:发布内容中存在凭据边界问题

初次审查时 git ls-files 确认 claude-dev-stack/.env 已被跟踪,随后按用户要求从工作区删除并排除管理范围;claude/setup-claude-deepseek.sh:14 和 PowerShell 对应脚本存在硬编码 API 凭据。根 .gitignore 中 Xray 生成配置的忽略规则被注释,多个节点信息文件也被跟踪。

新增面板的上传/打包必须使用发布文件白名单,不能递归上传整个工作区。需要另行确认并轮换已进入版本历史的有效凭据,将实际环境文件移出跟踪;仅添加 ignore 不会移除已经跟踪的文件。当前仅按用户要求删除废弃的 claude-dev-stack,不重写历史或轮换密钥。

P1:应用部署会影响整台主机

certd/deploy.sh:145 等应用入口会调用 init_system;base/setup.sh:72 执行整机 apt-get upgrade。应用配置校验在这些步骤之后。Gitea 在自己的脚本里重复了同一套初始化逻辑。

base/setup.sh:157 在已有 Docker 配置缺少 registry-mirrors 字段时覆盖整个 daemon.json,随后重启 Docker。应用部署不应隐式执行这些操作。

建议拆分主机初始化、主机维护和应用生命周期;应用只检查基础能力。公共库被 source 时不得隐式安装或修改系统。

P1:脚本位置依赖阻碍单独分发

Certd、Joplin、Vaultwarden、SiYuan、Portainer、RustDesk 的 deploy.sh 均要求 ../base/setup.sh 存在,即使服务器已安装所需环境也无法跳过。它们运行时独立,部署包却不独立。

共享源代码可以保留,但单工具发布包应带版本固定的最小运维库,或使用显式安装并验证版本的公共运行器;不能依赖相邻仓库目录。建议先采用发布时打包最小库,减少服务端额外依赖。

P1:同主机多实例隔离不完整

7 份 Compose 都使用固定 container_name;大部分宿主端口和 Nginx 上游固定。Vaultwarden 虽支持 VAULTWARDEN_PORT,Nginx 模板仍固定 8080。Gitea 升级检测支持 GITEA_HTTP_PORT,但 Compose 和 Nginx 仍固定 3000。

仅设置 Compose 项目名还不够,容器名、端口、数据路径、Nginx 站点名称和备份范围必须同时隔离。项目身份不能只靠当前文件夹名推断。

现有实例应读取 Docker Compose 标签与挂载建立绑定,不应直接删除 container_name 或改项目名后重建;这可能创建第二套容器或误连数据。

P1:共享入口和证书缺少事务与归属管理

base/setup.sh:342 起先把站点替换成临时 HTTP 配置,再申请证书;失败后没有恢复旧站点。deploy_nginx_conf 覆盖目标文件后才测试,失败时磁盘上可能留着无效配置。

各工具卸载入口还会删除证书并可能移除共享 Certbot cron。应由独立入口模块持有站点和证书引用,应用卸载只释放自己的绑定;续期调度由环境模块负责。

建议保持宿主 Nginx,使用主机级锁,备份旧配置、生成候选、验证、切换、reload、探测,失败时还原。每张证书只有一个明确的管理者,Certbot 与 Certd 不同时自动修改同一证书。

P1:备份/恢复契约不足以支撑通用面板

certd/backup.sh、portainer/backup.sh、joplin/backup.sh 等把配置打包错误通过 || true 忽略,随后仍输出成功。多个脚本仅按目录年龄清理备份,缺少成功清单、引用保护与明确的自有文件边界。运行中的数据库/文件目录归档也不能统一视为一致快照。

Gitea 的备份有单独 flock,但 upgrade.sh 没有使用同一把实例锁,不能阻止备份和升级交叠。统一面板锁也必须覆盖 CLI 入口。

恢复应声明类型:配置恢复、镜像回退、数据库恢复、完整快照恢复。数据库迁移不能套用参考面板的通用镜像回退。备份目录需要 manifest、完成标记、文件摘要、版本、数据位置及一致性模式;被回滚点引用的备份禁止自动清理。

P1:现有升级脚本不应直接视为经过全面验证的执行后端

gitea/upgrade.sh:617 附近的 wait_healthy 仍接受任意非 000 HTTP 响应,包括 500。自动和手动回滚忽略 stop 失败后继续恢复数据库,存在写入方未停就恢复数据的风险。自动回滚还可能在服务未就绪后输出“已回滚”。

恢复文件为合并解压,数据库恢复保留新版本额外表;这不等于精确还原。全量备份回灌也会覆盖备份内的 Git 引用文件,不能声称升级后的新提交天然保留。

此前“跨小版本必有 schema 版本增长”“表行数不减少即数据完好”“遗留表普遍无害”都不宜作为所有版本的通用保证。应绑定目标容器、实际数据库、目标版本兼容要求和具体恢复演练;通过有限场景只能证明对应场景。

P2:非 Docker 工具及端口资源需要单独建模

Xray 默认 XRAY_PORT=443(vps-xray/deploy.sh:411),与同一地址上的 Nginx TLS 入口存在绑定冲突的可能;它还执行 sysctl 和防火墙修改。不能把所有工具都抽象成 docker compose up。

面板应支持 Compose 与 systemd 两类执行器;Xray 先支持状态及明确的维护动作。主机网络调优必须作为单独环境计划展示,安装前检查真实监听地址、协议和端口。

P2:版本、能力、文档存在漂移

多个镜像使用 latest/lts/小版本浮动标签;仓库 Gitea 默认仍为 1.25。根 README 未覆盖 Xray、CLI 工具和残留目录,列出不存在的 gitea/migrate.sh,并把所有端口描述为回环,遗漏实际公网例外。

只有 Gitea/Vaultwarden 有专门 upgrade.sh,其余工具不能仅因存在 deploy.sh 就在面板上显示“安全升级/回滚”。面板应按适配器实际能力显示操作,解析并记录具体镜像 digest。

4. 推荐目标架构

采用三层结构,管理界面是可选控制入口:

  1. 主机环境:Docker/Compose、宿主 Nginx、证书续期、防火墙。单独检查、安装、维护,主机级变更说明影响范围。
  2. 独立应用实例:每实例独立 Compose 项目/或 systemd 单元、配置、数据、备份与任务记录。应用私有数据库留在实例内。Gitea 不依赖 Joplin、Certd 或 Portainer。
  3. 本地管理面板与 CLI:共用操作协议和执行器。面板停止后,应用继续工作,已提交的远程任务可以继续并被重新核对。

应用实例目标由 hostId + appId + instanceId + 规范化部署路径 + 实际项目/单元身份组成。所有写计划绑定目标、配置/编排摘要、明确版本、影响资源和恢复能力;执行前在锁内复核。

独立部署应同时满足:只选择一个工具即可准备它自己的依赖;不要求安装其他应用或面板;部署/升级不修改其他应用;独立停启、备份、恢复、卸载;同一台主机可部署第二实例;环境能力缺失时给出独立环境准备计划。

数据库可以在高级配置中外置,但默认不合并共享 MySQL/PostgreSQL;避免将升级、故障和恢复范围扩大到多个工具。

5. 管理工具参考项目的取舍

可借鉴的机制:

  • React/Vite + Node 本地服务、Windows/macOS 便携启动与打包。
  • SSH 私钥/Agent、SHA256 指纹确认,传输层明确区分断线和任务失败。
  • 本地 Host/Origin/会话/CSRF 校验。
  • 目标绑定与过期预览、配置漂移检查、受控参数、秘密遮蔽。
  • 远端任务目录、退出码、日志、进程状态、断线后的状态核对。
  • 主机环境操作与应用生命周期分离。

需要重建的业务层:

  • src/contracts.ts:1 固定了 game-docker 的服务列表。
  • src/connections.ts:388 会拒绝不存在 game-docker 服务的目录,不能直接连接本仓库的 Gitea/Joplin 部署。
  • 部署目录、环境字段、微信/QQ、发布文件清单与业务站点路由均为专用逻辑。
  • 现有档案偏向“一个连接目标绑定一个项目”;这里需要“一台主机登记多个独立实例”。

因此建议在本仓库新增 tools/ops-panel/,复用经抽取和测试的机制,应用适配器独立定义。不要直接搬整个参考项目再堆条件判断。参考项目自己也将真实 Linux Docker 验收列为未完成,不能把其本地测试当作生产保证。

Portainer 保持可选:它提供容器层管理,本工具负责仓库应用生命周期、备份与恢复计划。直接在 Portainer 修改编排可能造成漂移,面板须检测并要求重新识别。docker.sock 的 :ro 挂载不能当作 Docker API 只读权限。

6. 候选目录与交付方式

第一阶段保留现有应用目录和线上路径,新增职责明确的目录:

server-deploy/
  base/                  # 兼容旧入口;逐步成为显式主机环境命令
  lib/                   # 可版本化、无隐式副作用的通用运维函数
  contracts/             # 应用清单、计划、实例、任务、备份 schema
  tools/ops-panel/        # 可独立启动的本地管理工具
  gitea/                 # 原路径保留,逐个添加应用清单与适配入口
  joplin/
  vaultwarden/
  certd/ siyuan/ portainer/ rustdesk/
  vps-xray/              # systemd 适配器
  claude/                # 开发机配置工具,不默认纳入服务器部署
  tests/                 # 协议、脚本故障、真实隔离验收
  docs/

应用清单声明:标识/版本、运行器、Compose 服务角色、环境要求、端口及入口、秘密字段、数据挂载、健康探测、升级与恢复策略、支持的动作。清单不是任意命令执行接口,执行器仍使用受控命令和参数校验。

远端先保持 /opt/gitea 等既有目录;已有数据路径按真实挂载登记。新实例可采用 /opt/selfhost/instances/<实例>/、/var/lib/selfhost/<实例>/ 和 /var/backups/selfhost/<实例>/,但不自动移动旧目录。单工具发布包只包含自身清单、Compose、脚本/模板和所需版本的通用库,避免依赖整仓库。

7. 方案比较与实施顺序

推荐:独立应用 + 共享环境 + 本地 SSH 面板。 最接近当前结构,改造可以逐工具进行,日常运维不依赖服务器额外常驻控制服务。

备选:独立应用 + 服务器 Web 面板。 适合多人协作,但增加集中凭据、用户权限、审计、HTTPS 和面板自身可用性的维护成本。

不推荐:全服务合并 Compose,通过 profiles 选择。 能集中启动,却容易重新引入共享环境、项目级操作和恢复范围耦合,不符合本次强调的独立部署目标。

建议实施顺序:

  1. 凭据与发布边界清理;明确环境与应用责任;登记所有已有实例,不重建容器。
  2. 固化实例/任务/备份协议;统一实例锁及主机环境锁,应用配置校验先于所有修改。
  3. 选择无外部数据库的 SiYuan 做独立部署试点,验证端口参数、反代、数据保留、多实例及失败恢复。
  4. 建立面板只读版:多主机、多实例、状态、日志、配置脱敏、备份目录和任务核对。
  5. 接入经过验收的部署/配置更新/备份动作;Gitea、Vaultwarden、Joplin 各自定义迁移与恢复策略。
  6. 单独接入主机环境维护、Xray;按需扩展 Certd 与 Portainer。

升级应选择明确目标版本;预览可以查询新版本,但确认后的执行不能重新解析 latest。取消选择工具只表示本次不操作,不能转换为卸载。卸载默认保留数据,销毁数据是独立动作。

8. 验收边界

  • 只拿单个工具发布包,在准备好的环境中完成部署;同机启动第二实例且端口、数据、路由不交叉。
  • 面板或 SSH 断开后,远程任务可以核对,不自动重发有副作用的步骤。
  • 部署单个工具时,其他工具容器 ID、挂载、配置与运行状态不发生非预期变化。
  • backup/upgrade/restore/uninstall 共用锁;公共环境维护与应用变更互斥。
  • 镜像拉取失败、磁盘不足、数据库未就绪、配置漂移、Nginx 校验失败,均有明确阶段和退出状态。
  • 停机失败时禁止恢复数据库;备份失败禁止继续迁移;恢复失败明确进入需人工检查,不输出成功。
  • 恢复既验证归档也实际恢复数据库/仓库,并核验应用功能;数据行数只是辅助证据。
  • 保留现有数据路径、镜像和回滚资料;接管只建立基线,执行迁移另有计划。

9. UI 方向(用户已确认)

管理工具采用 Apple 风格,参考 macOS 原生工具的信息组织与交互节奏。此项是视觉与交互要求,不意味着必须改用 Swift、Electron 或其他桌面框架,也不等同于已选择本地或服务端部署形态。

  • 整体视觉:中性背景、克制的蓝色强调、清晰字体层级、充足留白、适度圆角与轻分隔。避免大面积渐变、过度毛玻璃和满屏同质卡片。
  • 主框架:侧边栏负责导航;顶部持续显示当前服务器、环境与应用实例;内容区按概览、应用、环境、任务和备份组织。切换目标后清除旧预览,避免操作到上一台服务器。
  • 应用列表:优先用紧凑列表展示名称、版本、状态和入口;详情页提供配置、日志、备份与升级等能力,未支持的动作不伪装成可用功能。
  • 操作流程:部署按“选择工具 → 配置 → 预检与变更预览 → 执行结果”推进。配置采用分组表单,长任务使用明确的阶段和可展开日志,保留任务恢复入口。
  • 生产操作:预览明确展示目标、影响的实例、预计停机及数据恢复范围;危险动作使用独立且具体的确认。颜色辅助提示,不能代替文字说明。
  • 可访问性:键盘焦点可见、文本对比度充足、状态不只依靠颜色;以系统字体栈适配 Windows/macOS,不分发 Apple 专有字体资源。

下一步设计应先明确页面结构与核心流程,再制作少量关键页面原型;当前尚未实现 UI。

10. 官方参考

待确认的产品选择:管理工具是本地 SSH 面板还是服务器 Web 平台。本文优先推荐前者;选择后再形成实施规格与任务拆分。

11. 第二轮评估:收敛为可实施的结构

11.1 判断与纠偏

第一版对独立应用、共享环境、渐进接管的方向判断成立,但仍不足以作为直接开发的架构规格:执行责任不明确,容易让 Node、CLI、Shell 各自实现一遍操作;对多实例、外部数据库、双入口提供者等扩展的优先级偏高;UI 罗列功能而没有明确使用层级。

现代化的衡量标准是可复现、声明清楚、可观测、失败可判定、模块可独立测试,而不是增加框架、微服务或自动发现组件。本项目适合模块化单体管理工具 + 独立应用发布包 + 明确的环境模块。

11.2 独立部署的实际边界

首版必须保证每个工具能单独安装、停启、升级和备份,不依赖其他业务应用及面板在线。共享 Docker、入口和系统资源意味着仍有共同故障域,不能承诺同主机上的绝对故障隔离。

同主机多实例作为架构约束预留(独立身份、参数化资源),先在试点应用验证;不要求首版所有工具完成多实例 UI 和复杂迁移。外部数据库作为未来扩展,不在首版同时支持所有组合。

Gitea + 私有 MySQL、Joplin + 私有 PostgreSQL、RustDesk 两组件分别是一个部署单元。应用镜像更新与其数据库引擎升级是不同操作,不能因全栈 pull 顺带升级数据库。

应用包只声明所需环境与入口,不负责无条件安装 Nginx、签发证书或重启 Docker。入口模式至少在模型中区分“受管入口”“外部入口”“仅内网”,实际支持范围由适配器声明;应用自身要求公网 HTTPS 时仍需阻止不满足要求的配置。

11.3 入口方案与部署合理性

当前改造基线继续使用宿主 Nginx + Certbot:保留既有证书、站点和路径,集中到环境模块管理,避免与应用迁移同时更换网关。每台主机的监听地址和端口必须有明确归属,应用只申请自己的路由。

Caddy 的自动 HTTPS 可以减少新服务器的证书管理代码,是合理的后续选择;但它不自动解决既有 Nginx 路由、特殊长连接、证书接管和端口迁移。因此首版不同时实现两套网关,不强迫存量服务切换。入口操作接口应保持小而具体:检查、规划站点、应用站点、验证、移除本站点绑定。

容器化网关也可行,但不能把所有数据库加入公共代理网络。若未来改用容器网关,需单独设计前端网络与应用私有网络。当前回环端口 + 宿主代理在此规模下足够清楚。

11.4 管理工具采用模块化单体

候选仍为 React + TypeScript 前端及一个 Node 本地服务;技术版本在实施时锁定。首版不引入微服务、独立消息队列、常驻远端 Agent、插件市场或 Electron/Tauri 外壳。

面板内部按职责组织,而不是持续扩大一个 operations.ts:

tools/ops-panel/src/
  domain/             # Host、AppDefinition、Instance、Plan、Operation、Backup
  application/        # 发现、接管、预览、执行、核对等用例
  infrastructure/     # SSH/SFTP、存储、应用目录读取
  http/               # 本地会话、校验、API、任务事件
  web/
    app/              # 路由、当前目标、整体布局
    features/         # 服务器、应用实例、任务、设置
    ui/               # 基础组件与设计 token

依赖方向为 UI/API → 用例 → 领域模型;用例通过小接口使用 SSH 和存储实现。领域模型不依赖 React、HTTP 或 SSH。不要为每个目录强建独立 npm 包;出现第二个真实消费者时再提取公共包。

主机档案与应用实例分开:一台主机有多个实例;应用定义描述模板和能力,实例记录具体路径、配置引用和现场身份。Plan 表示有时效的目标变更;Operation 表示一次执行;Backup 表示可验证的恢复资料。不要把这些全部塞进一个不断扩展的 ServerProfile。

11.5 单一执行入口,避免三套实现

前端负责输入、预览和展示;本地服务负责连接、调用用例与核对任务;远端版本化运行器负责现场检查、实例锁、执行步骤和结果记录。应用适配器提供真正有差异的备份、迁移、健康验证及恢复步骤。

GUI 与服务器上的 CLI 都调用同一个远端执行入口。旧 deploy.sh/backup.sh/upgrade.sh 在逐项验收后才改成兼容入口;不新增一套 TypeScript Shell 字符串流程与原脚本并行维护。

目标流程:发现/检查 → 生成计划 → 展示变更 → 确认 → 锁内重验 → 执行 → 验证 → 记录结果。远端运行器不成为常驻服务;每个任务保留固定版本代码、计划标识、实例身份、阶段日志和退出状态,SSH 断线后继续运行或进入明确的待核对状态。

任务结果必须区分:成功、失败且已恢复、失败需处理、结果待核对。不能将网络断开直接标记为失败或自动重试,也不能把“发出恢复命令”当作“已恢复”。中断恢复指核对现场和受控续做,不代表每个 Shell 步骤都能从中间续跑。

首版同主机写任务串行、跨主机可并行,读取不取写锁;CLI 使用相同锁约定。后续证明有必要时才细分实例并行锁与环境排他锁,避免过早设计复杂调度器。手工操作及 Portainer 不会自动遵守本工具的锁,因此执行前仍要核对配置与现场漂移。

11.6 单一事实来源与分发

应用目录内的清单是该工具字段、能力和运行入口的唯一声明来源;Compose 是容器编排来源。面板不再手写第二套服务列表或镜像默认值。旧实例以实际挂载与项目标签建立记录,不由新模板覆盖现场。

发布时产出独立、版本化的应用包,包含本应用定义、Compose/模板、适配步骤与固定版本的最小运行器。源代码共享,发布包自包含;更新管理面板不隐式更新服务器应用包。

版本需区分面板版本、应用包版本、运行器协议版本和上游镜像 digest。计划锁定具体包与镜像;包摘要只证明内容一致,若要防范供应链替换还需要可信发布来源或签名,不能将普通 checksum 当作来源认证。

本地保存主机档案和任务索引,远端任务结果是执行核对依据。首版可以用经串行化和原子写入的 JSON 文件存储小规模元数据;不为几台服务器强加本地数据库。通过 Store 接口隔离,确有查询、规模或事务需求时再换 SQLite。秘密不进入任务摘要、前端持久存储或普通日志。

11.7 Apple 风格落实到信息架构

全局导航保持简洁:服务器、任务、设置。进入服务器后显示其应用实例与独立的运行环境页;“添加应用”进入工具目录。实例详情再提供概览、配置、日志、备份与版本历史,避免把每个动作升级为全局导航项。

常用操作贴近当前对象:重启在实例工具栏,升级在版本信息附近,恢复从具体备份进入。预览使用稳定的页面或侧栏,只对真正需要确认的影响做确认,不层层叠弹窗。任务状态与阶段、日志分开,真实可计算时才展示百分比。

视觉采用中性色、统一字级和间距、小面积状态色、系统字体、清楚的选中与焦点状态;适量材质不妨碍日志阅读。Apple 风格是一致性、层级和反馈,不是简单地增加圆角或玻璃背景。离线显示最后更新时间与“状态未确认”,不把历史绿色状态当作当前健康。

11.8 缩小首版并调整交付方式

第一条闭环采用一个应用:应用包 → 独立 CLI → 面板识别 → 部署预览 → 执行 → 健康验证 → 备份/恢复演练。完成后接入第二个带数据库的应用,验证抽象是否成立,再批量扩展。

这比先重写全部脚本、再开发一个大型面板更容易发现协议设计问题。共享环境准备作为单独入口随试点一起提供,但 Docker 版本升级、证书提供者切换、所有工具多实例和外部数据库组合不同时纳入首版。

验收应覆盖:只分发单工具包、另一应用持续正常、断线后任务核对、停机失败拒绝恢复、备份失败阻止迁移、失败结果真实、已有实例原路径接管。UI 原型与领域接口可以先设计,但本节仍是评估后的建议,未开始应用代码实现。

补充官方参考:Caddy 自动 HTTPS https://caddyserver.com/docs/automatic-https 。其能力说明不构成对当前生产网关切换的建议。