Files
my-vault/01_Projects/Infrastructure/Services/Soft Serve Git.md
T

9.2 KiB
Raw Blame History

# 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`
- 访问方式:SSHSoft Serve SSH 服务端口为容器内 `23231`
- 暴露方式(推荐):Traefik TCP entrypoint `ssh` 监听宿主机 `2222`,转发到容器 `23231`

---

## 2. 前置条件清单

### 2.1 DNSCNAME

你已设置:
- `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 networkvw-net

确保 Traefik 容器也在同一个 vw-net 网络内。


3. 准备目录与配置文件

在 us2 上:

mkdir -p /opt/soft-serve
cd /opt/soft-serve
mkdir -p data

最终结构:

/opt/soft-serve/
  compose.yml
  .env
  data/

4. 准备初始化管理员公钥(必须)

Soft Serve 首次启动会根据环境变量写入初始管理员 key。你已经验证的公钥写法如下(单行):

.env

SOFT_SERVE_INITIAL_ADMIN_KEYS=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE9irsGu03p+1xrwIfzrzjGZCcExJ/XFEgkqsgfEN70j windy@windy-mbp

注意事项:

  • 必须是 完整公钥的一整行
  • 只在 数据目录首次初始化(空目录) 时生效

5. Docker Compose(推荐:Traefik TCP 暴露 SSH

compose.yml(与你当前成功的结构一致,并保留注释):

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 不是 HTTPTraefik 在这里是 TCP 转发
  • 不要使用 HostSNI(repo.windy.me) 之类的规则来"按域名"分流 SSH(会引发 TLS/HostSNI 相关报错)
  • 最稳妥的做法就是:
    • tls=false
    • HostSNI('*')
    • 依赖端口入口(2222

6. Traefik 静态配置要求(必须有 entrypoint)

你必须在 Traefik 的静态配置中定义 ssh entrypoint,并监听对外端口(示例:2222)。

示例(只示意关键段):

entryPoints:
  ssh:
    address: ":2222"

如果缺失,会出现典型错误:

  • EntryPoint doesn't exist entryPointName=ssh

7. 首次启动与"只初始化一次"的规则

7.1 首次启动

在 /opt/soft-serve

docker compose up -d
docker compose ps

7.2 初始化只发生一次(关键规则)

如需重新初始化(比如 .env 修改后不生效),必须清空数据目录:

docker compose down
rm -rf ./data
mkdir -p ./data
docker compose up -d

8. 客户端连接与"user not found"修正方法

8.1 强制使用指定 key(排错与首次推荐)

你最终验证成功的关键点是:固定 key + IdentitiesOnly

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):

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

在本机写入:

Host repo.windy.me
  HostName repo.windy.me
  Port 2222
  User git
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes

之后即可:

ssh repo.windy.me info
ssh repo.windy.me repo list

9. 创建仓库与 Git clone/push

9.1 创建仓库

ssh repo.windy.me repo create test
ssh repo.windy.me repo list

9.2 Clone(推荐写法)

写法 A(最清晰):

git clone ssh://repo.windy.me:2222/test.git

写法 Bscp 风格,依赖 ssh config 的 Port):

git clone repo.windy.me:test.git

9.3 Push 验证

cd test
echo "# test" > README.md
git add .
git commit -m "init"
git push

10. 常见故障排查(快速定位)

10.1 连接到错误端口

现象:你以为是 23231,但实际对外是 2222(由 Traefik entrypoint 决定)。

验证(在 us2 上):

ss -lntp | grep :2222

应看到 Traefik 监听 2222。

10.2 EntryPoint doesn't exist entryPointName=ssh

原因:Traefik 静态配置未定义 entryPoints.ssh

修复:给 Traefik 增加:

entryPoints:
  ssh:
    address: ":2222"

并重启 Traefik。

10.3 Error: user not found

高概率原因:SSH 客户端用了"另一把 key"。

修复(强制固定 key):

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 上:

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

附录:生产级配置建议(可选)

A.1 生产级 composehealthcheck、日志限制、只读 filesystem、资源限制)

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:rw
      - /etc/localtime:/etc/localtime:ro

    networks:
      - traefik

    labels:
      - traefik.enable=true
      - 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

    # 健康检查
    healthcheck:
      test: ["CMD", "nc", "-z", "localhost", "23231"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

    # 资源限制
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'

    # 安全设置
    read_only: true
    tmpfs:
      - /tmp:size=100M,mode=1777

    # 日志限制
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

networks:
  traefik:
    external: true
    name: vw-net

A.2 Traefik 静态配置片段

示例 Traefik 静态配置(traefik.yml 或命令行参数):

# traefik.yml 示例
entryPoints:
  ssh:
    address: ":2222"

api:
  dashboard: true
  insecure: true

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: vw-net

或通过命令行参数:

--entrypoints.ssh.address=:2222