docs(soft-serve): 镜像固定 v0.12.2 + 备份 sidecar + 非 root 运行 (W1N-244..248)

- 镜像 pinned charmcli/soft-serve:v0.12.2(GHCR 为 dev/nightly 源,无 v0.12.x tag)
- soft-serve-backup sidecar:每日 02:00 sqlite .backup + repos-config 打包,03:00 prune 保留 14 份
- 非 root 运行(user 1000:1000),data chown;ssh.public_url 修复
- compose 源码参考:compose/soft-serve/(服务器文件为准)
This commit is contained in:
windyboy
2026-08-30 13:19:05 +08:00
parent f174aa1219
commit d6747028b4
9 changed files with 100 additions and 2 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ not maintain a second copy of the machine table here.
| SMTP `mx2.windy.me:587` (STARTTLS) or `:465` | mx2 | client submission; full email + mailbox password — [runbook](runbooks/mailcow-smtp-client.md) |
| IMAP `mx2.windy.me:993` | mx2 | same mailbox credentials |
| https://auth.wsvc.info | us2 (`/opt/vaultwarden`) | Vaultwarden (Postgres, **operational**) — client Server URL |
| `repo.windy.me:2222` | us2 (`/opt/soft-serve`) | Soft Serve (stub details) |
| `repo.windy.me:2222` | us2 (`/opt/soft-serve`) | Soft Serve git — v0.12.2 pinned, backup sidecar; details in [hosts/us2.wsvc.info.md](hosts/us2.wsvc.info.md) |
| DNS `ns1.wsvc.info:53` | hk2 (`/opt/pdns`, Auth **5.0.6**) | PowerDNS auth — zones `windy.me`, `wsvc.info`, `chans.xyz` |
| https://pdns.wsvc.info | hk2 (`poweradmin`) | Poweradmin UI |
| https://pgweb.wsvc.info | hk2 (`pgweb`) | PowerDNS Postgres browser |
+1
View File
@@ -11,6 +11,7 @@ files: every secret is a `${VAR}` reference resolved from the **server-local
| `vaultwarden` | us2 (`/opt/vaultwarden`) | `compose/vaultwarden/compose.yml` | static file + `compose-deploy.yml` |
| `pdns` | hk2 (`/opt/pdns`) | `compose/pdns/compose.yml` | static file + `compose-deploy.yml` |
| `pgdb` | pgdb (`/opt/database`, 无 ansible) | `compose/pgdb/compose.yml` | static file(手动部署:scp → `docker compose config -q``up -d`;服务器文件名 `docker-compose.yml` |
| `soft-serve` | us2 (`/opt/soft-serve`) | `compose/soft-serve/compose.yml` (+ `Dockerfile.backup`, `scripts/`) | static file(参考镜像, 未接入 compose-deploy; 服务器文件为准) |
| `adguardhome` | dns.windy.lan (`/opt/adguardhome`) | — (待从 LAN 提取) | static file (pending) |
| `unifi` | ubnt (`/home/windy/unifi-9`) | — (待从 LAN 提取) | static file (pending) |
| `wireguard` | us4 (`/opt/wireguard`) | `ansible/templates/wireguard-compose.yml.j2` | role-rendered (inventory vars) |
+2
View File
@@ -0,0 +1,2 @@
# Soft Serve initial admin public key (used only on first boot)
SOFT_SERVE_INITIAL_ADMIN_KEYS=ssh-ed25519 AAAA... # replace with admin public key
+3
View File
@@ -0,0 +1,3 @@
FROM alpine:3.20
RUN apk add --no-cache sqlite tzdata
WORKDIR /scripts
+59
View File
@@ -0,0 +1,59 @@
services:
soft-serve:
image: charmcli/soft-serve:v0.12.2
container_name: soft-serve
restart: unless-stopped
# non-root (uid 1000 = windy; 与 backup sidecar BACKUP_UID 一致)
user: "1000:1000"
environment:
SOFT_SERVE_DATA_PATH: /var/lib/soft-serve
SOFT_SERVE_INITIAL_ADMIN: windy
SOFT_SERVE_INITIAL_ADMIN_KEYS: ${SOFT_SERVE_INITIAL_ADMIN_KEYS}
volumes:
- ./data:/var/lib/soft-serve
- soft-serve-app:/soft-serve
networks:
- traefik
labels:
- traefik.enable=true
# SSH over TCP via Traefik (entryPoint ssh -> container port 23231)
- traefik.tcp.routers.softserve-ssh.entrypoints=ssh
- traefik.tcp.routers.softserve-ssh.rule=HostSNI(`*`)
- traefik.tcp.routers.softserve-ssh.tls=false
- traefik.tcp.services.softserve-ssh.loadbalancer.server.port=23231
soft-serve-backup:
build:
context: .
dockerfile: Dockerfile.backup
container_name: soft-serve-backup
restart: unless-stopped
volumes:
- ./data:/data:ro
- ./backups:/backup
- ./scripts:/scripts
environment:
TZ: Asia/Shanghai
BACKUP_UID: 1000
BACKUP_GID: 1000
entrypoint: >
/bin/sh -ec "
umask 077 &&
touch /backup/backup.log &&
crontab /scripts/crontab.txt &&
echo '[INFO] soft-serve backup cron installed' &&
crond -f -l 8
"
volumes:
soft-serve-app:
networks:
traefik:
external: true
name: vw-net
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
set -eu
umask 077
D() { date "+%Y-%m-%d %H:%M:%S"; }
TS=$(date +%Y%m%d_%H%M%S)
OUT="/backup/soft-serve_${TS}"
mkdir -p "$OUT"
echo "[$(D)] Starting soft-serve backup -> $OUT"
tar czf "$OUT/repos-config.tar.gz" -C /data repos hooks config.yaml ssh
sqlite3 /data/soft-serve.db ".backup '$OUT/soft-serve.db'"
chmod 600 "$OUT/repos-config.tar.gz" "$OUT/soft-serve.db"
if [ -n "${BACKUP_UID:-}" ] && [ -n "${BACKUP_GID:-}" ]; then
chown -R "$BACKUP_UID:$BACKUP_GID" "$OUT" /backup/backup.log
fi
echo "[$(D)] Backup OK: $(du -sh "$OUT" | cut -f1)"
+4
View File
@@ -0,0 +1,4 @@
# Run soft-serve backup daily at 02:00
0 2 * * * /bin/sh /scripts/backup.sh >> /backup/backup.log 2>&1
# Prune backups older than 14 days daily at 03:00
0 3 * * * /bin/sh /scripts/prune.sh >> /backup/backup.log 2>&1
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
set -eu
D() { date "+%Y-%m-%d %H:%M:%S"; }
ls -dt /backup/soft-serve_* 2>/dev/null | tail -n +15 | xargs -r rm -rf
echo "[$(D)] Pruned. Kept $(ls -d /backup/soft-serve_* 2>/dev/null | wc -l) backups (max 14)"
+10 -1
View File
@@ -80,7 +80,16 @@ ansible-playbook playbooks/compose-reconcile.yml --limit vaultwarden \
| Container | Status | Image / notes |
|-----------|--------|---------------|
| `soft-serve` | Up | `ghcr.io/charmbracelet/soft-serve:latest` (`repo.windy.me:2222`) |
| `soft-serve` | Up | `charmcli/soft-serve:v0.12.2` (`repo.windy.me:2222`) |
| `soft-serve-backup` | Up | alpine + sqlite3 sidecar (daily backup 02:00 / prune 03:00, crond) |
Soft Serve details (verified/updated 2026-08-30; 核查 [W1N-244](https://linear.app/w1ndy/issue/W1N-244), 修复 [W1N-245](https://linear.app/w1ndy/issue/W1N-245)/[W1N-246](https://linear.app/w1ndy/issue/W1N-246)/[W1N-247](https://linear.app/w1ndy/issue/W1N-247)):
- `/opt/soft-serve/compose.yml` (+ `Dockerfile.backup`, `scripts/`, `backups/`); data `/opt/soft-serve/data``/var/lib/soft-serve` (sqlite `soft-serve.db`, 12 repos + `windyboy`); env `.env` (`SOFT_SERVE_INITIAL_ADMIN_KEYS` = admin pubkey, first-boot only). 仓库镜像: `compose/soft-serve/`(参考, 服务器文件为准)
- No host ports published: Traefik TCP entrypoint `ssh` (`:2222``soft-serve:23231`, `HostSNI(*)`, `tls=false`) on `vw-net`. Container listens 23231 SSH / 23232 HTTP (git smart-http, no web UI) / 9418 git / 23233 stats (localhost only)
- **镜像已固定** `charmcli/soft-serve:v0.12.2`(digest `sha256:554cc770…`, Docker Hub 稳定源; GHCR 为 dev/nightly 源且无 v0.12.x tag); `/soft-serve` 由 named volume `soft-serve_soft-serve-app` 承载, 旧匿名卷已清理(2026-08-30)
- **非 root 运行**(2026-08-30, [W1N-248](https://linear.app/w1ndy/issue/W1N-248)): compose `user: "1000:1000"`(uid=windy, 镜像无内置用户); data 全量 `chown 1000:1000`, 容器内 `id` = uid 1000, 功能验证通过
- **`ssh.public_url` 已修复**(2026-08-30): `config.yaml``ssh://git@repo.windy.me:2222`; 失效 env `SOFT_SERVE_SSH_PUBLIC_URL` 已删(v0.12 不读取); `http`/`git` public_url 保持 `localhost`(未对外暴露)
- **备份**: sidecar `soft-serve-backup` 每日 02:00 → `backups/soft-serve_<TS>/{repos-config.tar.gz, soft-serve.db}`(db 用 `sqlite3 .backup` 在线快照), 03:00 prune 保留 14 份; 产物 chown windy:windy 600(含 `ssh/` host keys)。**恢复**: `docker compose stop soft-serve` → 解包 `repos-config.tar.gz` + 放回 `soft-serve.db``data/``docker compose up -d`。异地副本(hk2/WSL 每日拉取)= follow-up(见 W1N-247)
| `traefik` | Up | `traefik:v3.6.2` (`/opt/traefik`, public `:80`/`:443`) |
| `nghttpx-proxy` + `squid-backend` | Up | HTTP forward-proxy stack (`/opt/nghttpx`), network `nghttpx_internal-net`; details TBD |