QuanCard Server · 安装文档
部署你自己的全卡卡同步服务
QuanCard Server 是一个容器镜像,同时提供 API 与网页保险库。按本文操作,大约 15 分钟即可上线,并与 iPhone 配对。命令行内容与 GitHub 仓库中的部署指南保持一致。
准备工作
- 一台 Linux 主机,安装 Docker Engine 24+ 与 Docker Compose v2。1 核 CPU、512 MB 内存足够一个家庭使用:Argon2id 在浏览器中运行,不占服务器资源。
- 一个域名,DNS A/AAAA 记录指向这台主机,并开放 80 与 443 端口(用于 Let's Encrypt 验证与 HTTP 跳转 HTTPS)。
- 80/443 端口上没有其他服务;如果已有,请改用自己的反向代理。
HTTPS 不是可选项。除本机开发模式外,服务器会以 426 Upgrade Required 拒绝明文 HTTP 请求。iPhone 也只会连接 HTTPS 服务器。
快速安装(推荐:内置 Caddy)
git clone https://github.com/zoolapp/quancard-server.git && cd quancard-server
./scripts/init-env.sh vault.example.com # 生成 .env 与随机密钥(不会覆盖已有文件)
docker compose up -d --wait # Caddy 自动申请并续期证书打开 https://vault.example.com,输入 init-env.sh 打印的初始化令牌(也保存在 .env 中),创建所有者账户。默认的 compose.yaml 已经做了这些加固:
| 设置 | 作用 |
|---|---|
| 应用容器不对外发布端口,只在内部网络中 | 只有 Caddy 能访问它,X-Forwarded-Proto 无法被伪造 |
只读文件系统、tmpfs /tmp、移除全部 capabilities、no-new-privileges、uid 10001 | 即使出现漏洞,影响范围也被限制 |
命名卷 quancard-data | 存放 SQLite 数据库(只有密文与校验值) |
| Caddy 日志过滤 | 丢弃查询字符串与请求头,并对客户端 IP 打码 |
一步安装脚本
scripts/install.sh 会把指定的发布版本克隆到 ~/quancard,生成配置并固定镜像版本;拉不到发布镜像时,改为从同一版本的源码构建,然后启动服务。运行前请先阅读脚本内容。脚本安装的是最新正式版;想体验开发中的新功能(例如冲突中心),请使用上面的 git clone 方式。
curl -fsSL https://raw.githubusercontent.com/zoolapp/quancard-server/v0.1.1/scripts/install.sh -o install.sh
less install.sh
sh install.sh vault.example.com使用自己的反向代理
已经有 nginx、Traefik 或 Cloudflare Tunnel?只运行 quancard 服务,并只监听本机回环地址:
services:
quancard:
image: ghcr.io/zoolapp/quancard-server:latest
environment:
QC_SECRET: ${QC_SECRET}
QC_SETUP_TOKEN: ${QC_SETUP_TOKEN}
QC_PUBLIC_ORIGIN: https://vault.example.com
QC_TRUST_PROXY: "1"
ports: ["127.0.0.1:8080:8080"]
volumes: [quancard-data:/data]
read_only: true
tmpfs: ["/tmp:size=16m"]
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
volumes: { quancard-data: {} }你的代理需要终结 TLS,原样转发 Host,设置 X-Forwarded-Proto: https 与 X-Forwarded-For,并允许最大 17 MB 的请求体。QC_TRUST_PROXY=1 只信任一层代理:开启后绝不要把 8080 端口直接暴露到公网。nginx 示例:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size 17m;
}Cloudflare Tunnel 的用法相同(service: http://localhost:8080)。此时由 Cloudflare 终结 TLS,它能看到流量元数据,但保险库内容依然端到端加密。
本机试用
./scripts/init-env.sh localhost
docker compose -f compose.local.yaml up -d
open http://localhost:8080只绑定 127.0.0.1。请使用 Chrome、Edge 或 Firefox 试用:会话 Cookie 始终带 Secure 标记,Safari 可能在明文 http://localhost 下拒绝它。不要在共享电脑上用本机模式保存真实资料。
配置项
| 变量 | 是否必需 | 说明 |
|---|---|---|
QC_DOMAIN | compose 使用 | Caddy 使用的域名,同时决定 QC_PUBLIC_ORIGIN |
QC_SECRET | 必填 | 至少 32 字节随机数(hex)。用于两步验证密钥的静态加密与 HMAC。务必备份:丢失后所有人的两步验证都会失效 |
QC_SETUP_TOKEN | 首次运行 | 一次性所有者初始化令牌(≥ 24 字符)。不设置则无法初始化 |
QC_PUBLIC_ORIGIN | 必填* | 用户访问的 https:// 地址(*本机开发可改用 QC_ALLOW_INSECURE_LOCALHOST=1) |
QC_TRUST_PROXY | 使用代理时 | 设为 1 时信任一层代理的 X-Forwarded-* 头 |
QC_ALLOW_INSECURE_LOCALHOST | 仅开发 | 设为 1 时允许 localhost / 127.0.0.1 使用明文 HTTP |
QC_DATA_DIR · QC_PORT · QC_HOST · QC_WEB_ROOT | 可选 | 镜像内默认值分别为 /data、8080、0.0.0.0、/app/web |
QC_IMAGE_TAG | 可选 | compose 使用的镜像标签;建议固定版本后按计划升级 |
首次设置
- 创建所有者账户:粘贴初始化令牌,设置用户名与至少 15 个字符的口令。没有密码重置:服务器从不知道你的密码,也就无法帮你找回。
- 创建保险库:新建保险库会附带一张示例卡;「载入示例数据」会补齐示例目录,连同欢迎卡共 15 张卡片和 6 个账户(公开测试号码),在 设置 → 示例数据 中一键移除。已有保险库则用
QC1.…恢复码导入。 - 完成后初始化令牌立即作废,之后的访问者只会看到登录页。
配对 iPhone
- 网页端:设置 → 设备 → 配对 iPhone,再次输入密码。屏幕上出现一次性二维码,10 分钟内有效。
- iPhone:设置 → 同步 → 自建服务同步 → 扫描配对二维码(也可以粘贴配对链接)。核对目标地址后点「连接并同步」。
- 两端播放同一段连接动画,并依次显示 iPhone 已连接 → 正在同步 → 同步完成。此后任一端的修改都会出现在另一端。
二维码里包含保险库密钥,只给自己的手机看,用完就关掉。服务器永远拿不到这个密钥。重新配对:在 iPhone 上 管理同步 → 断开这台设备(本机条目保留),在网页撤销旧设备,然后重新扫码。两边内容一样的条目会被识别为同一条,不会出现冲突。
家庭成员与安全
- 邀请成员:设置 → 成员 → 创建邀请链接(一次性,7 天有效)。每位成员拥有独立账户和单独加密的保险库,所有者也无法读取。
- 两步验证:设置 → 安全 → 两步验证。使用验证器 App 扫码,并把恢复码离线保存。
- 自动锁定:闲置 1、5、15 或 30 分钟后自动锁定;标签页切到后台 60 秒后也会锁定。
- 会话与活动:可以查看所有已登录的浏览器并一键退出其他会话;活动记录只能追加,不能修改。
版本冲突
全卡卡从不让设备时钟决定哪个版本胜出。两台设备离线时修改了同一条目,就会出现冲突提示:
- 内容完全相同的副本(例如重新配对时)会自动合并显示,不算冲突。
- 冲突中心逐字段标出差异,敏感字段只显示"不同"。可以逐条选择,也可以批量处理;每次批量操作前都会列出完整清单。
备份、恢复与升级
./scripts/backup.sh # 在线一致性快照 → backups/quancard-<UTC>.sqlite
./scripts/restore.sh backups/quancard-….sqlite # 校验、停服、替换、重启
docker compose pull && docker compose up -d --wait # 升级(之前先备份)备份中只有密文与校验值,但仍应妥善保管,并把 .env(含 QC_SECRET)与备份放在一起。建议用 cron 每晚备份,例如 0 3 * * * cd /opt/quancard && ./scripts/backup.sh;异地存放时用 restic、age 等工具再加密一层,并定期演练恢复。
数据库迁移在启动时于事务中自动完成。服务器拒绝在由更新版本写入的数据库上启动,以避免损坏数据;需要回退时,请恢复与旧版本对应的备份。健康检查:GET /healthz 返回 {"ok":true};GET /api/v1/status 返回版本与支持的能力。
安全检查清单
- DNS 指向主机,
https://证书有效,http://自动跳转。 - 8080 端口无法从公网访问。
.env权限为 600,已备份,没有提交到任何地方。- 所有者使用长口令并开启两步验证。
- 已安排定期备份,并演练过一次恢复。
- 主机系统与 Docker 持续接收安全更新,镜像定期升级。
常见问题排查
| 现象 | 原因与处理 |
|---|---|
| 没有初始化页面,只有登录页 | 所有者已经创建过。请直接登录,或请所有者发送邀请链接。 |
| 提示初始化令牌无效 | 从 .env 里原样复制 QC_SETUP_TOKEN;每个令牌只能用于一次初始化。 |
| iPhone 连接失败 | 服务器必须能通过 HTTPS 访问且证书有效。用手机浏览器打开 https://你的域名/healthz 检查。 |
| 浏览器提示需要 HTTPS | 请访问 QC_PUBLIC_ORIGIN 指定的地址;在反向代理后面时,设置 QC_TRUST_PROXY=1。 |
| 配对二维码已过期 | 二维码 10 分钟有效。关闭弹窗,重新生成一个。 |
| 忘记密码 | 无法重置。已配对的 iPhone 仍保留本机副本。 |
| 验证器 App 丢失 | 登录时改用恢复码,然后先关闭再重新开启两步验证。 |
更多资料
- GitHub 源码仓库 · 上手指南 · 部署指南(英文)
- 威胁模型 · 漏洞报告 · 更新日志
- 协议规范:auth v1 · pairing v1 · sync v1 · OpenAPI
QuanCard Server 是开发预览版,尚未经过独立安全审计,部署与维护由你负责。存放真实资料前,请先阅读威胁模型。