build(deploy): switch hosting to Podman Quadlet
This commit is contained in:
1 parent
10aa0304e4
commit
019b15041a
15 files changed
+312
-134
No files matched your search
File renamed without changes.
@@ -21,6 +21,8 @@ server/.idea/
|
||||
!.env.example
|
||||
|
||||
# Editor and OS files
|
||||
.idea/
|
||||
.playwright-mcp/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -45,7 +45,7 @@ steps:
|
||||
- image="$ZOT_REGISTRY/justme/webdrop-api:dev-$sha"
|
||||
- dev_image="$ZOT_REGISTRY/justme/webdrop-api:dev"
|
||||
- build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
- buildah bud --layers --network host --format docker --platform linux/amd64 --build-arg "WEBDROP_FRONTEND_COMMIT=$CI_COMMIT_SHA" --build-arg "WEBDROP_FRONTEND_TAG=$build_tag" --build-arg "WEBDROP_BACKEND_COMMIT=$CI_COMMIT_SHA" --build-arg "WEBDROP_BACKEND_TAG=$build_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Dockerfile .
|
||||
- buildah bud --layers --network host --format oci --platform linux/amd64 --build-arg "WEBDROP_FRONTEND_COMMIT=$CI_COMMIT_SHA" --build-arg "WEBDROP_FRONTEND_TAG=$build_tag" --build-arg "WEBDROP_BACKEND_COMMIT=$CI_COMMIT_SHA" --build-arg "WEBDROP_BACKEND_TAG=$build_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Containerfile .
|
||||
- buildah tag "$image" "$dev_image"
|
||||
- buildah push "$image" "docker://$image"
|
||||
- buildah push "$dev_image" "docker://$dev_image"
|
||||
@@ -1,65 +1,65 @@
|
||||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/v*
|
||||
- event: manual
|
||||
|
||||
labels:
|
||||
ci: domain-linux-native
|
||||
platform: linux/amd64
|
||||
|
||||
clone:
|
||||
- name: git
|
||||
image: woodpeckerci/plugin-git:2.9.3
|
||||
settings:
|
||||
remote:
|
||||
from_secret: gitea_clone_webdrop
|
||||
lfs: false
|
||||
|
||||
steps:
|
||||
checkout-release-source:
|
||||
image: bash
|
||||
commands:
|
||||
- set -euo pipefail
|
||||
- release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}"
|
||||
- test -n "$release_tag"
|
||||
- node scripts/check-api-contract.mjs "$release_tag"
|
||||
- git fetch --force origin "refs/tags/$release_tag:refs/tags/$release_tag"
|
||||
- git checkout --detach "refs/tags/$release_tag"
|
||||
|
||||
release-checks:
|
||||
image: bash
|
||||
commands:
|
||||
- release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}"
|
||||
- test -n "$release_tag"
|
||||
- node scripts/check-api-contract.mjs "$release_tag"
|
||||
- bash scripts/ci-frontend.sh
|
||||
- mkdir -p "$HOME/.cache/woodpecker/webdrop/nuget"
|
||||
- NUGET_PACKAGES="$HOME/.cache/woodpecker/webdrop/nuget" dotnet restore server/WebDrop.slnx
|
||||
- NUGET_PACKAGES="$HOME/.cache/woodpecker/webdrop/nuget" dotnet test server/WebDrop.slnx --configuration Release --verbosity minimal --no-restore
|
||||
|
||||
publish-backend:
|
||||
image: bash
|
||||
environment:
|
||||
ZOT_USERNAME: justme
|
||||
ZOT_SECRET:
|
||||
from_secret: zot_secret
|
||||
ZOT_REGISTRY: registry.oeo.one
|
||||
commands:
|
||||
- set -euo pipefail
|
||||
- release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}"
|
||||
- test -n "$release_tag"
|
||||
- ': "${WOODPECKER_BACKEND_LOCAL_TEMP_DIR:=$HOME/woodpecker/work}"'
|
||||
- buildah_store="$(dirname "$WOODPECKER_BACKEND_LOCAL_TEMP_DIR")/buildah-cache/webdrop"
|
||||
- mkdir -p "$buildah_store/storage" "$buildah_store/run"
|
||||
- 'buildah() { command buildah --root "$buildah_store/storage" --runroot "$buildah_store/run" "$@"; }'
|
||||
- printf '%s' "$ZOT_SECRET" | buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$ZOT_REGISTRY"
|
||||
- trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT
|
||||
- image="$ZOT_REGISTRY/justme/webdrop-api:$release_tag"
|
||||
- latest_image="$ZOT_REGISTRY/justme/webdrop-api:latest"
|
||||
- build_commit="$(git rev-parse HEAD)"
|
||||
- build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
- buildah bud --layers --network host --format docker --platform linux/amd64 --build-arg "WEBDROP_FRONTEND_COMMIT=$build_commit" --build-arg "WEBDROP_FRONTEND_TAG=$release_tag" --build-arg "WEBDROP_BACKEND_COMMIT=$build_commit" --build-arg "WEBDROP_BACKEND_TAG=$release_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Dockerfile .
|
||||
- buildah tag "$image" "$latest_image"
|
||||
- buildah push "$image" "docker://$image"
|
||||
- buildah push "$latest_image" "docker://$latest_image"
|
||||
|
||||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/v*
|
||||
- event: manual
|
||||
|
||||
labels:
|
||||
ci: domain-linux-native
|
||||
platform: linux/amd64
|
||||
|
||||
clone:
|
||||
- name: git
|
||||
image: woodpeckerci/plugin-git:2.9.3
|
||||
settings:
|
||||
remote:
|
||||
from_secret: gitea_clone_webdrop
|
||||
lfs: false
|
||||
|
||||
steps:
|
||||
checkout-release-source:
|
||||
image: bash
|
||||
commands:
|
||||
- set -euo pipefail
|
||||
- release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}"
|
||||
- test -n "$release_tag"
|
||||
- node scripts/check-api-contract.mjs "$release_tag"
|
||||
- git fetch --force origin "refs/tags/$release_tag:refs/tags/$release_tag"
|
||||
- git checkout --detach "refs/tags/$release_tag"
|
||||
|
||||
release-checks:
|
||||
image: bash
|
||||
commands:
|
||||
- release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}"
|
||||
- test -n "$release_tag"
|
||||
- node scripts/check-api-contract.mjs "$release_tag"
|
||||
- bash scripts/ci-frontend.sh
|
||||
- mkdir -p "$HOME/.cache/woodpecker/webdrop/nuget"
|
||||
- NUGET_PACKAGES="$HOME/.cache/woodpecker/webdrop/nuget" dotnet restore server/WebDrop.slnx
|
||||
- NUGET_PACKAGES="$HOME/.cache/woodpecker/webdrop/nuget" dotnet test server/WebDrop.slnx --configuration Release --verbosity minimal --no-restore
|
||||
|
||||
publish-backend:
|
||||
image: bash
|
||||
environment:
|
||||
ZOT_USERNAME: justme
|
||||
ZOT_SECRET:
|
||||
from_secret: zot_secret
|
||||
ZOT_REGISTRY: registry.oeo.one
|
||||
commands:
|
||||
- set -euo pipefail
|
||||
- release_tag="${CI_COMMIT_TAG:-${RELEASE_TAG:-}}"
|
||||
- test -n "$release_tag"
|
||||
- ': "${WOODPECKER_BACKEND_LOCAL_TEMP_DIR:=$HOME/woodpecker/work}"'
|
||||
- buildah_store="$(dirname "$WOODPECKER_BACKEND_LOCAL_TEMP_DIR")/buildah-cache/webdrop"
|
||||
- mkdir -p "$buildah_store/storage" "$buildah_store/run"
|
||||
- 'buildah() { command buildah --root "$buildah_store/storage" --runroot "$buildah_store/run" "$@"; }'
|
||||
- printf '%s' "$ZOT_SECRET" | buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$ZOT_REGISTRY"
|
||||
- trap 'buildah logout "$ZOT_REGISTRY" >/dev/null 2>&1 || true' EXIT
|
||||
- image="$ZOT_REGISTRY/justme/webdrop-api:$release_tag"
|
||||
- latest_image="$ZOT_REGISTRY/justme/webdrop-api:latest"
|
||||
- build_commit="$(git rev-parse HEAD)"
|
||||
- build_time="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
- buildah bud --layers --network host --format oci --platform linux/amd64 --build-arg "WEBDROP_FRONTEND_COMMIT=$build_commit" --build-arg "WEBDROP_FRONTEND_TAG=$release_tag" --build-arg "WEBDROP_BACKEND_COMMIT=$build_commit" --build-arg "WEBDROP_BACKEND_TAG=$release_tag" --build-arg "WEBDROP_BUILD_TIME=$build_time" --tag "$image" --file server/Containerfile .
|
||||
- buildah tag "$image" "$latest_image"
|
||||
- buildah push "$image" "docker://$image"
|
||||
- buildah push "$latest_image" "docker://$latest_image"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|---|---|
|
||||
| 协议、API、威胁模型 | 已定稿(`docs/`) |
|
||||
| `test-vectors/` | 已完成。由 `node:crypto` 独立生成,服务端(.NET)和 `@webdrop/core`(WebCrypto)各自复现 |
|
||||
| `server/` | 频道与便捷分享 API 已实现;含独立的可选 `WebDrop.Frontend` 静态资源模块;NativeAOT 发布 |
|
||||
| `server/` | 频道与便捷分享 API 已实现;含独立的可选 `WebDrop.Frontend` 静态资源模块;NativeAOT 发布为 OCI 镜像 |
|
||||
| `packages/core/` | 已完成并验证。168 个测试,含对着真实服务端的端到端 |
|
||||
| `web/` | 前端,开发中 |
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
| [web/AGENTS.md](web/AGENTS.md) | 前端契约:权威顺序、范围、必须遵守与禁止项 |
|
||||
| [packages/core/README.md](packages/core/README.md) | `@webdrop/core` 的接口、前端要做和不用做的事、必须注意的几点 |
|
||||
| [server/README.md](server/README.md) | 服务端的结构、配置、AOT 发布、验证码(Cap)接入 |
|
||||
| [docs/deployment.md](docs/deployment.md) | Docker Compose 自托管部署与 Woodpecker CI |
|
||||
| [docs/deployment.md](docs/deployment.md) | Podman Quadlet 自托管部署与 Woodpecker CI |
|
||||
| [test-vectors/README.md](test-vectors/README.md) | 协议测试向量的生成与使用 |
|
||||
|
||||
## 核心概念
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: server/Dockerfile
|
||||
image: ${WEBDROP_IMAGE_REPO:-webdrop-api}:${WEBDROP_IMAGE_TAG:-local}
|
||||
platform: linux/amd64
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,noexec,nosuid,size=16m
|
||||
cap_drop:
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
ASPNETCORE_URLS: http://+:5000
|
||||
WebDrop__DataDir: /data
|
||||
WebDrop__RequestsPerMinute: ${WEBDROP_REQUESTS_PER_MINUTE:-600}
|
||||
WebDrop__ChannelCreatesPerMinute: ${WEBDROP_CREATES_PER_MINUTE:-30}
|
||||
WebDrop__SweepIntervalSeconds: ${WEBDROP_SWEEP_INTERVAL_SECONDS:-30}
|
||||
WebDrop__TrustForwardedHeaders: ${WEBDROP_TRUST_FORWARDED_HEADERS:-false}
|
||||
WebDrop__Captcha__Provider: ${CAPTCHA_PROVIDER:-none}
|
||||
WebDrop__Captcha__ApiEndpoint: ${CAPTCHA_API_ENDPOINT:-}
|
||||
WebDrop__Captcha__SiteverifyUrl: ${CAPTCHA_SITEVERIFY_URL:-}
|
||||
WebDrop__Captcha__Secret: ${CAPTCHA_SECRET:-}
|
||||
WebDrop__Captcha__TimeoutSeconds: ${CAPTCHA_TIMEOUT_SECONDS:-5}
|
||||
ports:
|
||||
- "${API_BIND_ADDRESS:-127.0.0.1}:${API_PORT:-5000}:5000"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
@@ -0,0 +1,4 @@
|
||||
[Timer]
|
||||
OnCalendar=
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=5min
|
||||
@@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=WebDrop development environment from registry
|
||||
|
||||
[Container]
|
||||
ContainerName=webdrop-dev
|
||||
Image=registry.oeo.one/justme/webdrop-api:dev
|
||||
Pull=missing
|
||||
AutoUpdate=registry
|
||||
PublishPort=127.0.0.1:5001:5000
|
||||
Environment=ASPNETCORE_URLS=http://+:5000
|
||||
Environment=WebDrop__DataDir=/data
|
||||
Environment=DOTNET_EnableDiagnostics=0
|
||||
Volume=%h/podman/webdrop-dev/data:/data:Z,U
|
||||
ReadOnly=true
|
||||
ReadOnlyTmpfs=false
|
||||
Tmpfs=/tmp:rw,noexec,nosuid,size=16m
|
||||
DropCapability=ALL
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=120
|
||||
TimeoutStopSec=60
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=WebDrop API with embedded frontend
|
||||
|
||||
[Container]
|
||||
ContainerName=webdrop
|
||||
Image=localhost/webdrop:local
|
||||
Pull=never
|
||||
PublishPort=127.0.0.1:5000:5000
|
||||
Environment=ASPNETCORE_URLS=http://+:5000
|
||||
Environment=WebDrop__DataDir=/data
|
||||
Environment=DOTNET_EnableDiagnostics=0
|
||||
Volume=/home/justme/podman/webdrop/data:/data:Z,U
|
||||
ReadOnly=true
|
||||
ReadOnlyTmpfs=false
|
||||
Tmpfs=/tmp:rw,noexec,nosuid,size=16m
|
||||
DropCapability=ALL
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=120
|
||||
TimeoutStopSec=60
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
+77
-32
@@ -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 频道按设计保存明文负载。
|
||||
@@ -0,0 +1,39 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function ConvertTo-WslPath([string]$Path) {
|
||||
$fullPath = [IO.Path]::GetFullPath($Path)
|
||||
if ($fullPath -notmatch '^([A-Za-z]):\\(.*)$') { throw "Workspace must be on a Windows drive: $fullPath" }
|
||||
$drive = $Matches[1].ToLowerInvariant()
|
||||
$relative = $Matches[2].Replace('\', '/')
|
||||
return "/mnt/$drive/$relative"
|
||||
}
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
$repoWsl = ConvertTo-WslPath $repoRoot
|
||||
|
||||
$unitPath = (Resolve-Path (Join-Path $PSScriptRoot '..\deploy\webdrop.container')).Path
|
||||
$unitWsl = ConvertTo-WslPath $unitPath
|
||||
|
||||
$updatePath = (Resolve-Path (Join-Path $PSScriptRoot 'wsl-podman-update.sh')).Path
|
||||
$updateWsl = ConvertTo-WslPath $updatePath
|
||||
|
||||
$commit = (& git -C $repoRoot rev-parse --short=12 HEAD).Trim()
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not read the current Git commit.' }
|
||||
$tagOutput = & git -C $repoRoot describe --tags --exact-match HEAD 2>$null
|
||||
$tag = if ($tagOutput) { "$tagOutput".Trim() } else { 'dev' }
|
||||
$buildTime = [TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([DateTimeOffset]::UtcNow, 'China Standard Time').ToString('yyyy-MM-dd HH:mm')
|
||||
|
||||
$deployHome = '/home/justme/podman/webdrop'
|
||||
$configDir = '/home/justme/.config/containers/systemd'
|
||||
& wsl.exe -d Debian -- mkdir -p $deployHome $configDir
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not create the WSL deployment directories.' }
|
||||
|
||||
& wsl.exe -d Debian -- install -D -m 755 $updateWsl "$deployHome/update.sh"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not install the WSL update script.' }
|
||||
& wsl.exe -d Debian -- install -D -m 644 $unitWsl "$deployHome/webdrop.container"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not install the WebDrop Quadlet.' }
|
||||
& wsl.exe -d Debian -- ln -sfn "$deployHome/webdrop.container" "$configDir/webdrop.container"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not link the Quadlet into the user systemd directory.' }
|
||||
|
||||
& wsl.exe -d Debian -- bash "$deployHome/update.sh" $repoWsl $commit $tag $buildTime
|
||||
if ($LASTEXITCODE -ne 0) { throw 'The WSL Podman build or service update failed.' }
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
source_dir="${1:?Pass the workspace path in WSL format}"
|
||||
frontend_commit="${2:-unknown}"
|
||||
frontend_tag="${3:-dev}"
|
||||
build_time="${4:-unknown}"
|
||||
deploy_dir="$HOME/podman/webdrop"
|
||||
source_dir="$(realpath "$source_dir")"
|
||||
|
||||
# The WSL proxy exports lowercase names; containers/image reads the uppercase names.
|
||||
export HTTP_PROXY="${HTTP_PROXY:-${http_proxy:-}}"
|
||||
export HTTPS_PROXY="${HTTPS_PROXY:-${https_proxy:-}}"
|
||||
export NO_PROXY="${NO_PROXY:-${no_proxy:-}}"
|
||||
|
||||
if [[ ! -f "$source_dir/server/Containerfile" ]]; then
|
||||
echo "Not a WebDrop source tree: $source_dir" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "$deploy_dir/src" "$deploy_dir/data"
|
||||
|
||||
# Seed the WSL data directory once; later updates leave runtime data untouched.
|
||||
if [[ -d "$source_dir/data" ]] && [[ -z "$(find "$deploy_dir/data" -mindepth 1 -print -quit)" ]]; then
|
||||
rsync -a "$source_dir/data/" "$deploy_dir/data/"
|
||||
fi
|
||||
|
||||
# Keep build inputs on WSL's Linux filesystem, not the slower /mnt/c mount.
|
||||
rsync -a --delete \
|
||||
--exclude='/.git/' \
|
||||
--exclude='/.env' --exclude='/.env.*' \
|
||||
--exclude='/data/' \
|
||||
--exclude='**/node_modules/' --exclude='**/dist/' \
|
||||
--exclude='**/bin/' --exclude='**/obj/' \
|
||||
--exclude='**/*.tsbuildinfo' \
|
||||
"$source_dir/" "$deploy_dir/src/"
|
||||
|
||||
podman build \
|
||||
--layers \
|
||||
--platform linux/amd64 \
|
||||
--format oci \
|
||||
--file "$deploy_dir/src/server/Containerfile" \
|
||||
--tag localhost/webdrop:local \
|
||||
--build-arg "WEBDROP_FRONTEND_COMMIT=$frontend_commit" \
|
||||
--build-arg "WEBDROP_FRONTEND_TAG=$frontend_tag" \
|
||||
--build-arg "WEBDROP_BACKEND_COMMIT=$frontend_commit" \
|
||||
--build-arg "WEBDROP_BACKEND_TAG=$frontend_tag" \
|
||||
--build-arg "WEBDROP_BUILD_TIME=$build_time" \
|
||||
"$deploy_dir/src"
|
||||
|
||||
systemctl --user daemon-reload
|
||||
if systemctl --user is-active --quiet webdrop.service; then
|
||||
systemctl --user restart webdrop.service
|
||||
else
|
||||
systemctl --user start webdrop.service
|
||||
fi
|
||||
|
||||
for attempt in {1..30}; do
|
||||
if curl --fail --silent http://127.0.0.1:5000/api/v1/info >/dev/null; then
|
||||
echo "WebDrop updated and responding at http://127.0.0.1:5000"
|
||||
podman ps --filter name=webdrop --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
systemctl --user status webdrop.service --no-pager || true
|
||||
journalctl --user -u webdrop.service -n 80 --no-pager || true
|
||||
exit 1
|
||||
File renamed without changes.
+1
-1
@@ -49,7 +49,7 @@ python3 scripts/smoke.py out/linux-x64/webdrop # 跨平台版本,只用
|
||||
- 单实例部署:订阅枢纽在进程内,多实例需要换成外部发布订阅。
|
||||
- 日志不记录请求头、请求体、完整 URL;Hosting 诊断日志已关闭,避免频道 id 进入日志。
|
||||
- NativeAOT:不用 SignalR、EF Core、MVC、反射式 JSON;所有 AOT / 裁剪警告视为错误。
|
||||
- `WebDrop.Frontend` 只负责嵌入静态资源与 SPA fallback。Dockerfile 自动构建 `web/`;单独发布 API 时,只有 `web/dist` 存在或指定 `WebDropFrontendDist` 后才包含前端。
|
||||
- `WebDrop.Frontend` 只负责嵌入静态资源与 SPA fallback。`Containerfile` 自动构建 `web/`;单独发布 API 时,只有 `web/dist` 存在或指定 `WebDropFrontendDist` 后才包含前端。
|
||||
|
||||
## 验证码(Cap)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
dotnet publish ..\WebDrop.Api\WebDrop.Api.csproj -p:WebDropFrontendDist=C:\build\web-dist
|
||||
```
|
||||
|
||||
Dockerfile 会构建 `web/`,再把 dist 传给 .NET 发布阶段。`EmbeddedFrontend.Map` 只映射页面与静态文件,不依赖 `WebDrop.Core` 或存储层。
|
||||
Containerfile 会构建 `web/`,再把 dist 传给 .NET 发布阶段。`EmbeddedFrontend.Map` 只映射页面与静态文件,不依赖 `WebDrop.Core` 或存储层。
|
||||
|
||||
开发时可以不构建 dist,此时模块不映射页面路由;使用 `npm run dev` 配合 Vite API 代理。
|
||||
|
||||
|
||||
Reference in new issue
Block a user