From d6747028b40bd8dfb070cd491da066be845e8539 Mon Sep 17 00:00:00 2001 From: windyboy Date: Sun, 30 Aug 2026 13:19:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(soft-serve):=20=E9=95=9C=E5=83=8F=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=20v0.12.2=20+=20=E5=A4=87=E4=BB=BD=20sidecar=20+=20?= =?UTF-8?q?=E9=9D=9E=20root=20=E8=BF=90=E8=A1=8C=20(W1N-244..248)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 镜像 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/(服务器文件为准) --- AGENTS.md | 2 +- compose/README.md | 1 + compose/soft-serve/.env.example | 2 + compose/soft-serve/Dockerfile.backup | 3 ++ compose/soft-serve/compose.yml | 59 ++++++++++++++++++++++++++ compose/soft-serve/scripts/backup.sh | 15 +++++++ compose/soft-serve/scripts/crontab.txt | 4 ++ compose/soft-serve/scripts/prune.sh | 5 +++ hosts/us2.wsvc.info.md | 11 ++++- 9 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 compose/soft-serve/.env.example create mode 100644 compose/soft-serve/Dockerfile.backup create mode 100644 compose/soft-serve/compose.yml create mode 100644 compose/soft-serve/scripts/backup.sh create mode 100644 compose/soft-serve/scripts/crontab.txt create mode 100644 compose/soft-serve/scripts/prune.sh diff --git a/AGENTS.md b/AGENTS.md index 676c7ec..a84f947 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 | diff --git a/compose/README.md b/compose/README.md index 78c4433..3754eba 100644 --- a/compose/README.md +++ b/compose/README.md @@ -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) | diff --git a/compose/soft-serve/.env.example b/compose/soft-serve/.env.example new file mode 100644 index 0000000..61b7947 --- /dev/null +++ b/compose/soft-serve/.env.example @@ -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 diff --git a/compose/soft-serve/Dockerfile.backup b/compose/soft-serve/Dockerfile.backup new file mode 100644 index 0000000..4062c96 --- /dev/null +++ b/compose/soft-serve/Dockerfile.backup @@ -0,0 +1,3 @@ +FROM alpine:3.20 +RUN apk add --no-cache sqlite tzdata +WORKDIR /scripts diff --git a/compose/soft-serve/compose.yml b/compose/soft-serve/compose.yml new file mode 100644 index 0000000..19aaff9 --- /dev/null +++ b/compose/soft-serve/compose.yml @@ -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 diff --git a/compose/soft-serve/scripts/backup.sh b/compose/soft-serve/scripts/backup.sh new file mode 100644 index 0000000..35db21a --- /dev/null +++ b/compose/soft-serve/scripts/backup.sh @@ -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)" diff --git a/compose/soft-serve/scripts/crontab.txt b/compose/soft-serve/scripts/crontab.txt new file mode 100644 index 0000000..a4f2ee5 --- /dev/null +++ b/compose/soft-serve/scripts/crontab.txt @@ -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 diff --git a/compose/soft-serve/scripts/prune.sh b/compose/soft-serve/scripts/prune.sh new file mode 100644 index 0000000..147e382 --- /dev/null +++ b/compose/soft-serve/scripts/prune.sh @@ -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)" diff --git a/hosts/us2.wsvc.info.md b/hosts/us2.wsvc.info.md index 0ee6dd5..2458417 100644 --- a/hosts/us2.wsvc.info.md +++ b/hosts/us2.wsvc.info.md @@ -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_/{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 |