Files
vault-para/2025-12-29.md
T

592 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 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 不是 HTTPTraefik 在这里是 **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
```
写法 Bscp 风格,依赖 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
## Use user name and password for nghttpx
User name:
```
windy
```
Password:
```
meeQuan4jeinging
```
```
htpasswd -c ./passwords windy
```
Passwords
```
windy:$apr1$5usfjVkQ$Zsd27eX..5sZjFtRVTVjn1
```
Squid. Conf
```
http_port 3128
# Define the authentication program
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwords
auth_param basic realm windy private https proxy
auth_param basic children 5
auth_param basic credentialsttl 2 hours
# Define the ACL (Access Control List) for authenticated users
acl authenticated_users proxy_auth REQUIRED
# Allow access only to authenticated users
http_access allow authenticated_users
# 关闭 Via 头 (隐私保护)
via off
forwarded_for off
# 日志设置
access_log stdio:/var/log/squid/access.log combined
```
Compose
```
squid:
image: ubuntu/squid:latest
container_name: squid-backend
restart: always
volumes:
- ./squid.conf:/etc/squid/squid.conf
- ./passwords:/etc/squid/passwords:ro
- squid_cache:/var/spool/squid
command: ["squid", "-N", "-d", "1"]
networks:
- internal-net
```
You are absolutely correct to verify the documentation. I apologize for the previous error—**`nghttpx` does NOT support native Basic Authentication (username/password) for frontend clients.** The option `frontend-http-user` (and `basic-auth-file`) does not exist in the official `nghttpx` documentation.
Since your architecture is **Client -> nghttpx (TLS) -> Squid (Proxy)**, the correct place to handle authentication is in **Squid**, not `nghttpx`.
Here is the corrected, documentation-verified setup.
### 1. The Strategy
We will configure **Squid** to handle the username/password verification. `nghttpx` will simply pass the credentials (the `Proxy-Authorization` header) from the user to Squid.
### 2. Create the Password File
You need a tool like `htpasswd` (from `apache2-utils`) to create a hashed password file. Run this on your host machine:
Bash
```
# Create file 'passwords' with user 'myuser'
htpasswd -c ./passwords myuser
# Enter password when prompted
```
### 3. Update `docker-compose.yml`
You need to mount this password file into the **Squid** service (not nghttpx). You also need to mount a custom `squid.conf`.
YAML
```
services:
# ... nghttpx service remains the same ...
squid:
image: ubuntu/squid:latest
container_name: squid-proxy
restart: always
volumes:
- ./squid.conf:/etc/squid/squid.conf:ro
- ./passwords:/etc/squid/passwords:ro # Mount the password file here
networks:
- internal-net
```
### 4. Create `squid.conf`
Create a `squid.conf` file in the same directory. This configuration enables Basic Authentication using the file we just created.
Code snippet
```
# Define the authentication program
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwords
auth_param basic realm My Private Proxy
auth_param basic children 5
auth_param basic credentialsttl 2 hours
# Define the ACL (Access Control List) for authenticated users
acl authenticated_users proxy_auth REQUIRED
# Allow access only to authenticated users
http_access allow authenticated_users
# Deny everything else
http_access deny all
# Listen on port 3128
http_port 3128
```
_(Note: The path `/usr/lib/squid/basic_ncsa_auth` is standard for Ubuntu/Debian based Squid images. If you use Alpine, it might be `/usr/lib/squid/basic_ncsa_auth` or similar, check `apk info -L squid` if it fails)._
### 5. Clean up `nghttpx.conf`
Revert `nghttpx.conf` to its clean state (remove the invalid auth lines). `nghttpx` must be configured to pass the traffic transparently.
Code snippet
```
# nghttpx.conf
# Frontend: Listen on 3000, no TLS (Traefik handles TLS)
frontend=0.0.0.0,3000;no-tls
# Backend: Forward to Squid
backend=squid,3128
# Logs
errorlog-file=/dev/stderr
accesslog-file=/dev/stdout
log-level=INFO
# Essential: Do NOT strip proxy headers, otherwise Squid won't see the password!
strip-incoming-x-forwarded-for=no
```
### 6. Verify and Apply
1. **Restart:** `docker-compose up -d --force-recreate`
2. **Test:**
Bash
```
curl -v -x https://us2.wsvc.info:443 -U myuser:mypassword http://google.com
```
**Why this works:**
- The client sends a request with a `Proxy-Authorization` header.
- Traefik decrypts TLS and passes it to `nghttpx`.
- `nghttpx` forwards the request (including the header) to `squid`.
- `squid` checks the header against the `/etc/squid/passwords` file.
- If valid, `squid` allows the traffic.
Would you like me to generate the `squid.conf` specifically for an **Alpine** based Squid image if you are using one to keep the image size small?