build(deploy): switch hosting to Podman Quadlet

This commit is contained in:
justme committed 2026-10-13 01:38:18 +08:00
1 parent 10aa0304e4
commit 019b15041a
15 files changed
+312 -134

No files matched your search

+77 -32
View File
@@ -1,53 +1,98 @@
# 本地后端 Docker Compose
# Podman Quadlet 部署
Compose 当前只启动一个 WebDrop API 容器;构建镜像时会先构建 `web/`,再由独立的 `WebDrop.Frontend` 模块将静态资源嵌入后端。访问 `http://127.0.0.1:5000/` 打开前端,API 使用同源 `/api/v1`。API 默认只绑定 loopback,SQLite 数据保存在项目的 `./data` 目录。
WebDrop 使用 rootless Podman 运行单个 API 容器。前端先构建成静态文件,再嵌入 NativeAOT 后端;容器镜像使用 OCI 格式。仓库不再维护 Compose 配置。
## 启动
## WSL 本机部署
在 Windows 工作区运行:
```powershell
Copy-Item .env.example .env
docker compose config
docker compose up -d --build
docker compose ps
.\scripts\update-wsl-podman.ps1
```
API 地址为 `http://127.0.0.1:5000`。停止服务使用 `docker compose down`;数据库和服务器存储的帧会保留在 `./data`。删除该目录会永久删除这些数据。
本机开发直接从当前 Windows 工作区调用 WSL Podman。脚本会把源码增量同步到 WSL 的 `~/podman/webdrop/src`,在 WSL Linux 文件系统里构建 OCI 镜像,构建成功后重启 Quadlet 服务使用新镜像。构建失败时不会更新正在运行的容器;本机流程不经过镜像仓库。
## 前端连接
部署目录:
本机开发仍可使用 `cd web; npm run dev`,Vite 会代理 `/api/v1` 到本地 API。独立托管前端时,可单独构建 `web/` 并在前端托管层反向代理 `/api/v1`;跨域直连则配置明确的 `WebDrop__CorsOrigins`。
- `~/podman/webdrop/src`:从当前 Windows 工作区同步的源码副本。
- `~/podman/webdrop/data`:SQLite 数据和服务端帧文件。首次运行时从仓库 `data/` 拷贝一次,后续更新不会覆盖它。
- `~/podman/webdrop/webdrop.container`:Quadlet 单元源文件,链接到 `~/.config/containers/systemd/webdrop.container`。
公网部署时,在 API 前配置 HTTPS 反向代理,并让代理连接 API 的 loopback 端口。不要把未加密 HTTP 直接暴露到公网。只有当该 API 端口仅能由可信代理访问时,才将 `WEBDROP_TRUST_FORWARDED_HEADERS` 设为 `true`。
服务地址为 `http://127.0.0.1:5000`。管理服务:
嵌入模式减少自托管组件,但前端代码与 API 来自同一个部署信任域。若要让 API 节点被攻破时仍不能替换前端,应使用独立域名托管前端。
## 验证码
默认 `CAPTCHA_PROVIDER=none`。启用 Cap 时,在 `.env` 设置:
```dotenv
CAPTCHA_PROVIDER=cap
CAPTCHA_API_ENDPOINT=https://captcha.example.com/<site-key>/
CAPTCHA_SITEVERIFY_URL=https://captcha.example.com/<site-key>/siteverify
CAPTCHA_SECRET=<server-side-secret>
```bash
systemctl --user status webdrop.service
systemctl --user stop webdrop.service
systemctl --user start webdrop.service
```
`CAPTCHA_API_ENDPOINT` 会提供给浏览器;`CAPTCHA_SITEVERIFY_URL` 和 `CAPTCHA_SECRET` 仅供 API 容器使用。不要提交 `.env`。
本机开发前端仍可运行 `cd web && npm run dev`,Vite 会代理 `/api/v1` 到本地 API。公网部署时,在 API 前配置 HTTPS 反向代理;不要把未加密 HTTP 直接暴露到公网。只有当 API 端口仅能由可信代理访问时,才信任 forwarded headers。
## 其他 Linux 主机
构建 OCI 镜像:
```bash
podman build --layers --format oci --platform linux/amd64 \
--file server/Containerfile --tag localhost/webdrop:local .
```
将 `deploy/webdrop.container` 安装到 `~/.config/containers/systemd/` 前,把数据卷路径改为该主机专用目录。然后执行:
```bash
systemctl --user daemon-reload
systemctl --user start webdrop.service
```
WSL 更新脚本在每次构建成功后直接重启 Quadlet 服务。这里不使用 registry auto-update,因为新镜像已经在本机刚刚构建完成。
## NAS 开发与生产
NAS 开发环境使用 `deploy/webdrop-dev.container`,镜像固定跟踪 `registry.oeo.one/justme/webdrop-api:dev`;开发 CI 成功后会推送该标签。此单元设置 `AutoUpdate=registry`,并将用户级 timer 调整为启动后一分钟检查、之后每五分钟检查一次,发现新镜像就重启 Quadlet 服务;也可以手动执行 `podman auto-update` 立即检查。它使用独立的 `webdrop-dev` 容器、5001 本机端口和 `~/podman/webdrop-dev/data`,与 WSL 本机开发及生产数据隔离。
在 NAS 安装该单元前,先以 `justme` 登录 registry 并把凭据保存在用户级 auth file:
```bash
mkdir -p ~/.config/containers
printf '%s' "$ZOT_SECRET" | podman login --authfile ~/.config/containers/auth.json \
--username justme --password-stdin registry.oeo.one
chmod 600 ~/.config/containers/auth.json
mkdir -p ~/.config/containers/systemd ~/podman/webdrop-dev/data
cp deploy/webdrop-dev.container ~/.config/containers/systemd/
mkdir -p ~/.config/systemd/user/podman-auto-update.timer.d
cp deploy/podman-auto-update.timer.d/override.conf \
~/.config/systemd/user/podman-auto-update.timer.d/override.conf
systemctl --user daemon-reload
systemctl --user start webdrop-dev.service
systemctl --user enable --now podman-auto-update.timer
systemctl --user restart podman-auto-update.timer
```
为确保 NAS 注销 SSH 会话后 user service 仍持续运行,管理员需要为该账号启用 lingering:`sudo loginctl enable-linger justme`。
不要把 `ZOT_SECRET` 写入仓库或 Quadlet 文件。生产使用 release workflow 发布的 `vMAJOR.MINOR.PATCH` 标签,应固定到明确版本,不要跟踪 `dev`。
验证码配置写在 Quadlet 的 `[Container]` 段,例如:
```ini
Environment=WebDrop__Captcha__Provider=cap
Environment=WebDrop__Captcha__ApiEndpoint=https://captcha.example.com/<site-key>/
Environment=WebDrop__Captcha__SiteverifyUrl=https://captcha.example.com/<site-key>/siteverify
Environment=WebDrop__Captcha__Secret=<server-side-secret>
```
`SiteverifyUrl` 和 `Secret` 仅供 API 容器使用。修改后运行 `systemctl --user daemon-reload` 并重启服务。
## Woodpecker CI
`.woodpecker/ci.yml` 在默认分支 push、pull request 和手动运行时执行 API 契约与服务端检查;`.woodpecker/web-build.yml` 同时执行 Core 测试向量、类型检查、Web CSS lint、测试和构建。两个 workflow 独立运行,让前后端验证并行。默认分支当前为 `master`,配置也兼容迁移后的 `main`。
`.woodpecker/ci.yml` 在默认分支 push、pull request 和手动运行时执行 API 契约与服务端检查;`.woodpecker/web-build.yml` 同时执行 Core 测试向量、类型检查、Web CSS lint、测试和构建。两个 workflow 独立运行,让前后端验证并行。默认分支当前为 `master`,配置也兼容迁移后的 `main`。
默认分支每次 push 和 PR 都会跑常规检查。提交信息含 `[SKIP TESTS]` 时只跳过服务端测试;API 契约检查和前端/Core 检查照常运行。提交信息含 `[CI]` 时,`.woodpecker/dev-image.yml` 会等 `ci` 和 `web-build` 都通过后,构建并推送带提交 SHA 的开发镜像以及可变 `dev` 标签。开发镜像不会覆盖 release tag 或 `latest`。发布目标为 `registry.oeo.one/justme/webdrop-api`;Buildah 使用 Woodpecker secret `zot_secret` 作为 Zot API key 的密码字段,用户名为 `justme`,并启用 TLS 校验。Gitea clone 凭据保存在 `gitea_clone_webdrop` secret。
默认分支每次 push 和 PR 都会跑常规检查。提交信息含 `[SKIP TESTS]` 时只跳过服务端测试;API 契约检查和前端/Core 检查照常运行。提交信息含 `[CI]` 时,`.woodpecker/dev-image.yml` 会等 `ci` 和 `web-build` 都通过后,构建并推送带提交 SHA 的开发镜像以及可变 `dev` 标签。开发镜像不会覆盖 release tag 或 `latest`。发布目标为 `registry.oeo.one/justme/webdrop-api`;Buildah 使用 Woodpecker secret `zot_secret` 作为 Zot API key 的密码字段,用户名为 `justme`,并启用 TLS 校验。Gitea clone 凭据保存在 `gitea_clone_webdrop` secret。
`.woodpecker/release-backend.yml` 响应 `vMAJOR.MINOR.PATCH` SemVer tag;也可手动运行并传入 `RELEASE_TAG`(例如 `v0.1.0`)。发布 workflow 会重新执行 Core、Web 和服务端完整检查,全部通过后才用 Linux native agent 的 Buildah 构建并推送后端镜像 tag 与 `latest`。所有 workflow 都通过 Woodpecker secret `gitea_clone_webdrop` 从 `https://git.oeo.one` 克隆公开仓库。
`.woodpecker/release-backend.yml` 响应 `vMAJOR.MINOR.PATCH` SemVer tag;也可手动运行并传入 `RELEASE_TAG`。发布 workflow 会重新执行 Core、Web 和服务端完整检查,全部通过后才用 Linux native agent 的 Buildah 构建 OCI 镜像并推送后端镜像 tag 与 `latest`。所有 workflow 都通过 Woodpecker secret `gitea_clone_webdrop` 从 `https://git.oeo.one` 克隆公开仓库。
两条流水线固定到 `ci: domain-linux-native` 的 `linux/amd64` Agent;该主机需要 Node 24、.NET 10 和 Buildah。release job 在 `WOODPECKER_BACKEND_LOCAL_TEMP_DIR` 对应的持久目录缓存 Buildah 层;Dockerfile 使用带 NativeAOT 前置工具的 `10.0-noble-aot` SDK 镜像,不再运行 apt 安装 clang/zlib;NuGet restore 层也由 Buildah 缓存。Gitea webhook 走同 Docker 网络中的 `woodpecker-server:8000`,代码 clone 走公网 Git 域名。
后端镜像发布到 `registry.oeo.one/justme/webdrop-api`。Woodpecker Linux agent 通过 TLS 连接 registry;发布需要 Buildah 与 push/tag/manual 事件可用的 `zot_secret` secret。使用已发布镜像时,在 `.env` 中设置 `WEBDROP_IMAGE_TAG`,仓库名已填入 `.env.example`。
API 契约版本以 `contracts/api-contract.json` 为准,并同步到 OpenAPI 与后端 `/api/v1/info`。加字段等向后兼容更新递增 minor,兼容性修复递增 patch,破坏性更新递增 major。前端声明支持的契约主版本;后端主版本变化时,前端 CI 契约检查和运行时连接检查都会失败,直到前端同步支持该主版本。
两条流水线固定到 `ci: domain-linux-native` 的 `linux/amd64` Agent;该主机需要 Node 24、.NET 10 和 Buildah。release job 在 `WOODPECKER_BACKEND_LOCAL_TEMP_DIR` 对应的持久目录缓存 Buildah 层;`server/Containerfile` 使用带 NativeAOT 前置工具的 `10.0-noble-aot` SDK 镜像,不再运行 apt 安装 clang/zlib;NuGet restore 层也由 Buildah 缓存。Gitea webhook 使用 Woodpecker server 所在网络,代码 clone 走公网 Git 域名。
## 数据备份
停止服务或使用一致性快照后备份 `./data`。目录里保存 SQLite 元数据和服务端收到的帧字节;加密频道/分享的内容帧是密文。备份仍需按敏感数据保护,因为 raw 频道按设计保存明文负载。
停止服务或使用一致性快照后备份 `~/podman/webdrop/data`。目录里保存 SQLite 元数据和服务端收到的帧字节;加密频道/分享的内容帧是密文。备份仍需按敏感数据保护,因为 raw 频道按设计保存明文负载。