# Soft Serve 安装指南(Docker Compose + Traefik TCP + CNAME) ## 1. 目标与最终形态 - 域名:`repo.windy.me` - DNS:`repo.windy.me` **CNAME → `us2.wsvc.info`** - 部署主机:`us2.wsvc.info` 对应的 VPS(本文称 “us2”) - Soft Serve 镜像:`ghcr.io/charmbracelet/soft-serve:latest` - 数据持久化:宿主机 `./data` → 容器 `/var/lib/soft-serve` - 访问方式:SSH(Soft Serve SSH 服务端口为容器内 `23231`) - 暴露方式(推荐):Traefik TCP entrypoint `ssh` 监听宿主机 `2222`,转发到容器 `23231` --- ## 2. 前置条件清单 ### 2.1 DNS(CNAME) 你已设置: - `repo.windy.me` CNAME → `us2.wsvc.info` 关键含义: - 用户访问 `repo.windy.me` 时,最终会解析到 **us2 的公网 IP** - 只要 us2 上对外开放 SSH 入口端口(示例:2222),访问就成立 建议验证(任意机器): ```bash dig +short repo.windy.me CNAME dig +short repo.windy.me A ``` --- ### 2.2 网络与防火墙 在 us2 上确保对外放行你用于 Soft Serve SSH 的端口(示例 2222): - 入站允许:TCP 2222 --- ### 2.3 Traefik 已存在并使用外部网络 你当前 compose 使用: - external network:`vw-net` 确保 Traefik 容器也在同一个 `vw-net` 网络内。 --- ## 3. 准备目录与配置文件 在 us2 上: ```bash mkdir -p /opt/soft-serve cd /opt/soft-serve mkdir -p data ``` 最终结构: ``` /opt/soft-serve/ compose.yml .env data/ ``` --- ## 4. 准备初始化管理员公钥(必须) Soft Serve 首次启动会根据环境变量写入初始管理员 key。你已经验证的公钥写法如下(单行): `.env`: ```env SOFT_SERVE_INITIAL_ADMIN_KEYS=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE9irsGu03p+1xrwIfzrzjGZCcExJ/XFEgkqsgfEN70j windy@windy-mbp ``` 注意事项: - 必须是 **完整公钥的一整行** - 只在 **数据目录首次初始化(空目录)** 时生效 --- ## 5. Docker Compose(推荐:Traefik TCP 暴露 SSH) `compose.yml`(与你当前成功的结构一致,并保留注释): ```yaml services: soft-serve: image: ghcr.io/charmbracelet/soft-serve:latest container_name: soft-serve restart: unless-stopped 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 # 方案B:直连端口映射(不走 Traefik) # ports: # - "2222:23231" 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 networks: traefik: external: true name: vw-net ``` ### 关于 “SSH 不能走 Traefik 代理域名分流”的结论 - SSH 不是 HTTP;Traefik 在这里是 **TCP 转发** - 不要使用 `HostSNI(repo.windy.me)` 之类的规则来“按域名”分流 SSH(会引发 TLS/HostSNI 相关报错) - 最稳妥的做法就是: - `tls=false` - `HostSNI('*')` - 依赖端口入口(2222) --- ## 6. Traefik 静态配置要求(必须有 entrypoint) 你必须在 Traefik 的静态配置中定义 `ssh` entrypoint,并监听对外端口(示例:2222)。 示例(只示意关键段): ```yaml entryPoints: ssh: address: ":2222" ``` 如果缺失,会出现典型错误: - `EntryPoint doesn't exist entryPointName=ssh` --- ## 7. 首次启动与“只初始化一次”的规则 ### 7.1 首次启动 在 `/opt/soft-serve`: ```bash docker compose up -d docker compose ps ``` ### 7.2 初始化只发生一次(关键规则) 如需重新初始化(比如 `.env` 修改后不生效),必须清空数据目录: ```bash docker compose down rm -rf ./data mkdir -p ./data docker compose up -d ``` --- ## 8. 客户端连接与“user not found”修正方法 ### 8.1 强制使用指定 key(排错与首次推荐) 你最终验证成功的关键点是:**固定 key + IdentitiesOnly**。 ```bash ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 -p 2222 repo.windy.me info ``` 若成功会输出类似: ``` Username: admin (或 windy) Admin: true Public keys: ... ``` ### 8.2 把默认用户名从 `admin` 改成 `windy` 你已成功的改名命令(注意同样要固定 key): ```bash ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 -p 2222 repo.windy.me set-username windy ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 -p 2222 repo.windy.me info ``` **解释:**“user not found” 的真实根因通常不是 Soft Serve 没用户,而是 SSH 客户端未固定 key 时选用了另一把 key,导致 Soft Serve 无法把该连接映射到已存在的用户。 ### 8.3 永久固化:写 `~/.ssh/config` 在本机写入: ```sshconfig Host repo.windy.me HostName repo.windy.me Port 2222 User git IdentityFile ~/.ssh/id_ed25519 IdentitiesOnly yes ``` 之后即可: ```bash ssh repo.windy.me info ssh repo.windy.me repo list ``` --- ## 9. 创建仓库与 Git clone/push ### 9.1 创建仓库 ```bash ssh repo.windy.me repo create test ssh repo.windy.me repo list ``` ### 9.2 Clone(推荐写法) 写法 A(最清晰): ```bash git clone ssh://repo.windy.me:2222/test.git ``` 写法 B(scp 风格,依赖 ssh config 的 Port): ```bash git clone repo.windy.me:test.git ``` ### 9.3 Push 验证 ```bash cd test echo "# test" > README.md git add . git commit -m "init" git push ``` --- ## 10. 常见故障排查(快速定位) ### 10.1 连接到错误端口 现象:你以为是 23231,但实际对外是 2222(由 Traefik entrypoint 决定)。 验证(在 us2 上): ```bash ss -lntp | grep :2222 ``` 应看到 Traefik 监听 2222。 --- ### 10.2 `EntryPoint doesn't exist entryPointName=ssh` 原因:Traefik 静态配置未定义 `entryPoints.ssh`。 修复:给 Traefik 增加: ```yaml entryPoints: ssh: address: ":2222" ``` 并重启 Traefik。 --- ### 10.3 `Error: user not found` 高概率原因:SSH 客户端用了“另一把 key”。 修复(强制固定 key): ```bash ssh -vvv -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 -p 2222 repo.windy.me info ``` 观察日志中是否出现: - `Offering public key: ... id_ed25519` - `Server accepts key: ... id_ed25519` --- ## 11. 备份与恢复(生产建议) ### 11.1 需要备份的内容 Soft Serve 核心数据都在宿主机 `./data`(映射自 `/var/lib/soft-serve`): - `soft-serve.db`(用户/设置) - `repos/`(仓库数据,如存在) - `ssh/`(host keys 等) ### 11.2 最简单备份命令 在 us2 上: ```bash cd /opt/soft-serve tar -czf soft-serve-backup-$(date +%F).tar.gz ./data ``` 恢复流程: 1. `docker compose down` 2. 解压覆盖 `./data` 3. `docker compose up -d` --- ## 12. 推荐的“最终检查清单” - `repo.windy.me` CNAME 指向 `us2.wsvc.info`,并能解析到 us2 IP - us2 对外开放 TCP 2222 - Traefik 静态配置存在 `entryPoints.ssh=:2222` - Soft Serve 数据目录持久化:`./data:/var/lib/soft-serve` - 客户端 `~/.ssh/config` 固定 `IdentityFile` + `IdentitiesOnly yes` - `ssh repo.windy.me info` 输出 `Username: windy` 且 `Admin: true` --- 如果你希望我把这份指南进一步“落地化”,我可以再给你两个附录(可选): 1. **一份生产级 compose**(healthcheck、日志限制、只读 filesystem、资源限制) 2. **一份 Traefik 静态配置片段**(与你当前 Traefik 容器的真实配置方式对齐:file provider / command args / labels)