自托管服务器
bssh Teams 同步服务器是一个由 PostgreSQL 支持的单容器镜像。托管服务运行同一个镜像,因此自托管服务器的行为完全相同,只是数据位于你自己的基础设施上。未配置 Stripe 时没有计费:每个团队的席位数固定。
服务器只保存密文和元数据。它从不持有主机名、备注、私钥或录制内容,也从不位于 SSH 数据路径中。Teams 安全 说明了信任模型。
- 一台装有 Docker 和 Compose 插件的 Linux 主机。
- PostgreSQL,例如下方 Compose 文件中的
postgres:17。 - 一个 DNS 名称及其 TLS 证书。除回环地址外,客户端拒绝明文 HTTP,并且要求 TLS 1.3。
- 团队机器能够访问该名称。
镜像以 Docker 镜像归档的形式分发,文件名为 bssh-cloud-api_<version>.tar,例如 bssh-cloud-api_0.2.0.tar。请通过 GitHub issue 申请访问权限。
在服务器上加载该归档:
docker load -i bssh-cloud-api_0.2.0.tar加载后的镜像带有版本标签,例如 bssh-cloud-api:0.2.0。
所有设置都在运行时生效;镜像中不包含任何与环境相关的值。
| 变量 | 默认值 | 说明 |
|---|---|---|
DATABASE_URL / DATABASE_URL_FILE |
必填 | PostgreSQL URL,或保存它的文件。要使用 TLS,添加 ?sslmode=verify-full&sslrootcert=/path/ca.crt。 |
BIND_ADDR |
127.0.0.1:8080(镜像中为 0.0.0.0:8080) |
监听地址。 |
TLS_CERT_FILE, TLS_KEY_FILE |
未设置 | PEM 证书链和私钥:由服务器自行提供 HTTPS。两个都设置,或都不设置;不设置时以明文 HTTP 提供服务,由前面的 TLS 代理终止。 |
SIGNUP_MODE |
invite |
invite:只有被邀请的邮箱和 SIGNUP_BOOTSTRAP_EMAILS 中的邮箱可以创建账户。open 接受任何人,仅用于开发。 |
SIGNUP_BOOTSTRAP_EMAILS / SIGNUP_BOOTSTRAP_EMAILS_FILE |
空 | 无需邀请即可创建账户的邮箱:由运维方手动加入的团队创建者。以逗号或换行分隔;两个来源会合并。 |
TRUST_PROXY_HEADERS |
关闭 | 从 X-Forwarded-For / X-Real-IP 记录审计日志中的客户端 IP。仅当前置代理会覆写这些头且是唯一入口时使用。 |
DEFAULT_SEATS |
5 |
计费关闭时新团队的席位数。 |
MIN_CLIENT_VERSION |
0.0.0 |
接受的最旧客户端版本。 |
RUST_LOG |
未设置 | 日志过滤,例如 info。 |
_FILE 变体指向一个文件,其内容(去掉首尾空白)会替代该变量。用它们保存机密,这样它们不会出现在 docker inspect 和环境变量中。
创建机密文件
Section titled “创建机密文件”-
创建机密文件并写入数据库密码:
mkdir -p secretschmod 700 secretsprintf '%s' 'a-long-random-password' > secrets/postgres_passwordprintf '%s' 'postgres://bssh:a-long-random-password@postgres:5432/bssh' > secrets/database_urlprintf '%s\n' 'ana@example.com' > secrets/bootstrap_emailschmod 600 secrets/* -
database_url中的密码必须与postgres_password中的一致;如果包含保留字符,请进行 URL 编码。若要使用 Compose 之外的 PostgreSQL 服务器,请修改database_url中的主机并添加 TLS 参数。
API 在启动时读取这些文件;修改后请重启。
使用 Docker Compose 运行
Section titled “使用 Docker Compose 运行”services: postgres: image: postgres:17 environment: POSTGRES_USER: bssh POSTGRES_DB: bssh POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password volumes: - pgdata:/var/lib/postgresql/data - ./secrets/postgres_password:/run/secrets/postgres_password:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U bssh -d bssh"] interval: 5s timeout: 5s retries: 10 start_period: 5s restart: unless-stopped
api: image: bssh-cloud-api:0.2.0 depends_on: postgres: condition: service_healthy environment: DATABASE_URL_FILE: /run/secrets/database_url BIND_ADDR: 0.0.0.0:8080 SIGNUP_BOOTSTRAP_EMAILS_FILE: /run/secrets/bootstrap_emails TRUST_PROXY_HEADERS: "true" DEFAULT_SEATS: "5" RUST_LOG: info volumes: - ./secrets/database_url:/run/secrets/database_url:ro - ./secrets/bootstrap_emails:/run/secrets/bootstrap_emails:ro ports: - "127.0.0.1:8080:8080" restart: unless-stopped
volumes: pgdata:docker compose up -ddocker compose logs -f apiAPI 在启动时、开始接受连接之前执行数据库迁移,并记录版本,例如 bssh-cloud 0.2.0。如果迁移失败,进程会退出,日志会说明原因。
API 只发布在回环地址上,因为 TLS 在它前面终止。curl http://127.0.0.1:8080/healthz 返回 ok;可用于监控。
在前面配置 TLS
Section titled “在前面配置 TLS”要么挂载证书链和私钥并设置 TLS_CERT_FILE 和 TLS_KEY_FILE,让服务器自行提供 TLS;要么两者都不设置,在同一主机上的反向代理中终止 TLS。无论哪种方式,API 都不能从外部访问,证书必须对团队使用的名称有效。
客户端要求 TLS 1.3,因此代理必须支持它。以下 nginx 配置块是最小示例:
server { listen 443 ssl; server_name teams.example.com;
ssl_certificate /etc/letsencrypt/live/teams.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/teams.example.com/privkey.pem; ssl_protocols TLSv1.3;
client_max_body_size 8m; # 一次保险库推送最多 4 MiB
location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; }}仅当代理是唯一入口并覆写这两个头时,才设置 TRUST_PROXY_HEADERS: "true"。服务器会把第一个 X-Forwarded-For 地址写入审计日志;如果代理在客户端提供的头上追加,客户端就能选择自己被记录的地址。
服务器不发送邮件:管理员负责转交每份邀请。第一个人是团队创建者,不需要邀请,因为其邮箱位于 SIGNUP_BOOTSTRAP_EMAILS 中。
-
将创建者的邮箱加入
secrets/bootstrap_emails。 -
重启 API 使其重新读取文件:
docker compose restart api
在创建者的机器上创建账户和团队:
bssh teams init --server https://teams.example.com --email ana@example.combssh teams create platform打开 Teams,填写 Sync server(https://teams.example.com)、Your email、Device name 和 Passphrase,然后选择 Set up this device 和 Create team。
然后从管理员的机器邀请其他人:
bssh teams invite platform sam@example.com邀请中包含服务器 URL 和一次性令牌,令牌只显示一次。被邀请者用该令牌和管理员设备的指纹加入,随后管理员授予团队密钥。成员与设备 详述了这一过程。添加 --admin 可将被邀请者设为管理员。
在没有计费的服务器上,新团队获得 DEFAULT_SEATS 个席位,并与团队一起保存。修改该变量只影响之后创建的团队。
成员和待处理的邀请都计入上限。团队满员时,新建邀请会失败。接受邀请时若没有空缺席位,邀请会保持待处理,并在有席位空出后生效。通过移除成员或撤销待处理的邀请来释放席位:
bssh teams uninvite platform ana@example.combssh teams remove platform ana@example.com在没有计费的服务器上,团队永远不会变为只读,席位数也不会自行变化。
在数据库上启用 PostgreSQL 时间点恢复,或使用你的服务商的等效功能,并定期做逻辑转储作为第二道防线:
docker compose exec -T postgres pg_dump -U bssh -d bssh > bssh-$(date +%F).sql备份包含所有团队的密文和元数据。请存放在只有运维方可以读取的位置,并在静态时加密。
要实际测试恢复,而不是想当然。将转储恢复到一个临时数据库,对其启动第二个 API 实例,确认 /healthz 有响应且客户端可以同步:
docker compose exec -T postgres createdb -U bssh bssh_restoredocker compose exec -T postgres psql -U bssh -d bssh_restore < bssh-2026-10-23.sql-
备份数据库。
-
加载新归档并检查其标签:
docker load -i bssh-cloud-api_<new-version>.tar -
在 Compose 文件中更新镜像标签,然后:
docker compose up -d
迁移在启动时自动运行,API 始终位于 /v1 下,因此升级服务器不需要客户端做任何改动。低于 MIN_CLIENT_VERSION 的客户端会收到 HTTP 426,bssh teams 会显示 the server needs a newer bssh; please upgrade。
镜像只包含向前迁移,因此回退到旧镜像意味着恢复升级前所做的数据库备份。
服务器保存什么
Section titled “服务器保存什么”服务器保存它无法解密的密文,以及实现服务端审计所需的元数据:
- 团队 id、成员和受邀者的邮箱、角色和席位数、设备名称和公钥
- 不透明的条目 id、修订号和密文大小
- 审计事件:成员 M 在时间 T 从 IP A 使用了条目 E
主机名、用户名、端口、标签、备注和跳板机设置都在客户端加密。私钥不会上传:共享连接不包含密钥路径,每位成员自己的连接把密钥文件留在自己的机器上。密码同样不会被保存,提示发生在你的终端里。即便如此,仍应将数据库及其备份视为敏感数据:没有成员的设备密钥,它们不会泄露保险库内容,但元数据是完整的。