server/DEPLOY_DOCKER.md
Rama main · réplica de lectura del repositorio original.
# TapLedger Server 部署教程(通用 Docker Compose)
> 把「一点账本」家庭共享后端跑在你自己的机器上(Linux / 威联通 / Windows+WSL 等任意装了 Docker 的设备)。数据全部留在本地,仅一个 SQLite 文件。
>
> 群晖 DSM 图形化操作请看 [DEPLOY_SYNOLOGY.md](./DEPLOY_SYNOLOGY.md),本文面向会用命令行的环境。
## 0. 前置条件
| 项 | 要求 |
|---|---|
| 系统 | 任意装了 Docker(含 compose 插件)的机器 |
| 架构 | 镜像支持 `linux/amd64` 和 `linux/arm64` |
| 网络 | 首次需能访问 `ghcr.io` 拉取镜像(镜像公开,无需 docker login) |
| 端口 | 默认 `8420`,可在 compose 里改 |
镜像地址:`ghcr.io/sctale/tapledger-server`(`latest` 或具体版本号双标签)
## 1. 准备目录与配置
```bash
mkdir -p /opt/tapledger && cd /opt/tapledger
```
**docker-compose.yml**:
```yaml
services:
tapledger:
image: ghcr.io/sctale/tapledger-server:latest
container_name: tapledger-server
restart: unless-stopped
ports:
- "8420:8420"
environment:
- PORT=8420
- TZ=Asia/Shanghai
env_file:
- .env
volumes:
- ./data:/app/data
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8420/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
```
**.env**(与 compose 同目录,权限 `chmod 600 .env`):
```bash
# 必填:JWT 签名密钥,≥32 位随机串。生成:openssl rand -hex 32
JWT_SECRET=<粘贴你的随机串>
# 可选:管理面板口令(浏览器开 http://机器IP:8420/admin 在线开关「注册/加入家庭」)
#ADMIN_TOKEN=<另一个强随机串>
# 可选:走 Nginx/反代暴露公网时设为 1(v0.5.2 起,否则限流按代理 IP 聚合)
#TRUST_PROXY=1
# 可选:自动备份(默认每日热备份,保留 7 份)
#BACKUP_KEEP=7
#BACKUP_DISABLED=1
```
> ⚠️ **JWT_SECRET 是硬性要求**:镜像默认 `NODE_ENV=production`,缺失/过短/用示例值时容器**拒绝启动**(日志 `[fatal] 生产环境必须配置 JWT_SECRET`)。这是防 token 伪造的保护,不是 bug。此密钥请记到密码管理器——换了它全家要重新登录。
## 2. 启动
```bash
docker compose up -d
```
## 3. 验证
```bash
# 健康检查
curl http://localhost:8420/api/health
# → {"ok":true,"time":1720000000000}
# 看日志(应有一行 [backup] wrote tapledger-YYYY-MM-DD.db,说明自动备份正常)
docker logs tapledger-server
# 状态应为 healthy
docker ps
```
## 4. APP 端连接
1. 打开「一点账本」→ 我的 → 家庭同步
2. 服务器地址填 `http://<你的机器IP>:8420`(反代后填 `https://你的域名`)
3. 第一位成员注册 → 创建家庭 → 其余家人用 6 位邀请码加入
4. 之后各端记账自动双向同步(LWW 冲突合并)
## 5. 公网访问(强烈建议 HTTPS 反代)
不要让明文 8420 裸露公网。以 Nginx 为例:
```nginx
server {
listen 443 ssl;
server_name ledger.example.com;
# ssl_certificate ...; ssl_certificate_key ...;
location / {
proxy_pass http://127.0.0.1:8420;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # 配合 TRUST_PROXY=1
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 12m; # push 批量变更可能较大
}
}
```
反代场景 `.env` 加 `TRUST_PROXY=1` 并重启容器,否则登录限流(5 次/分/IP)会把全家聚合成一个 IP——一人输错密码全员 429。
公网部署后建议配 `ADMIN_TOKEN`,用 `/admin` 面板随时熔断陌生人注册。
## 6. 升级 / 回退
```bash
# 升级(数据在 ./data 卷内,升降级不丢)
docker compose pull && docker compose up -d
# 确认现在到底在跑哪个版本(tag 会被 latest 覆盖,看日志最可靠;v0.5.7 起启动即打印版本)
docker compose logs --tail 20
# [boot] TapLedger server v0.5.9 监听 :8420
# [boot] NODE_ENV=production|自动备份 开启
# 掉线类问题还会留痕:[auth] 401 原因=验签失败(JWT_SECRET 可能已变更) uid=? path=/api/me ip=…(v0.5.9 起)
# 不方便登容器时:直接问健康接口(v0.5.8 起返回 version,手机同步页也会显示「服务端 v0.5.9」)
curl -s http://<NAS_IP>:8420/api/health
# {"ok":true,"version":"0.5.9","time":...}
# 回退:compose 里 image 改回旧版本号 tag,再 up -d
# image: ghcr.io/sctale/tapledger-server:0.5.1
```
**版本注意**:
- 升到 v0.4.2+ 前先确认 `.env` 的 `JWT_SECRET` 是真实强随机串(否则拒绝启动)
- 升到 v0.5.6+ 不会把已登录的设备踢下线:新增的 `users.token_version` 由老库自动补列,旧 token 无 `tv` 载荷按 0 处理
- 当前最新:server **v0.5.9**(四种 401 分开文案,并把 `[auth] 401 原因=… uid=… path=… ip=…` 写进容器日志——掉线类事故从此在服务端侧留痕;同一原因每分钟限 5 条);v0.5.8 起 `/api/health` 返回 `version`、v0.5.7 起启动日志打印版本号;v0.5.6 跨账本写入守卫、记账人作者保留、未来时间戳钳制、push 返回 invalid/skipped 分类、改密接口 `POST /api/auth/password`、`/admin` 未配口令时整页 503;历史:v0.5.5 登录态 365 天 + 注销接口、v0.5.2 `TRUST_PROXY`、v0.5.0 管理面板、v0.4.3 每日自动热备份
- 编号提醒:APP 与服务端共用同一套 `[x.y.z]` 号(如 `[0.5.7]` 在 2026-08-18 是 APP 的,2026-10-05 是服务端的),服务端条目一律带「(服务端…)」标注,读的时候认准这个标记
## 7. 备份与恢复
- **自动**:容器每日热备份到 `./data/backups/tapledger-YYYY-MM-DD.db`(留 7 份,`BACKUP_KEEP` 可调)
- **手动整包**:`tar czf tapledger-backup-$(date +%Y%m%d).tar.gz /opt/tapledger/data`
- **恢复**:停容器 → 删 `data/` 下 `tapledger.db-wal`、`tapledger.db-shm` → 把目标备份复制为 `tapledger.db` → 起容器
- **迁移新机器**:整个目录(compose + `.env` + `data/`)原样拷过去,`docker compose up -d` 即可
## 8. FAQ
| 现象 | 原因 / 解决 |
|---|---|
| 容器反复重启,日志 `[fatal] ...JWT_SECRET` | `.env` 填强随机串后 `docker compose up -d --force-recreate` |
| 一直拉取不到镜像 | 机器访问不了 ghcr.io;换网络或手动 `docker pull` |
| APP 注册 403「已关闭注册」 | `/admin` 面板关过开关,开回即可;老账号登录不受影响 |
| 加入家庭 429 | 正常保护:同 IP 5 次/分,或同邀请码连错 10 次锁 10 分钟 |
| 反代后一人 429 全家遭 | 忘了设 `TRUST_PROXY=1`(见第 5 节) |
| 数据会不会丢 | 不会,全在 `./data/`;容器可随便删重建 |
---
> 遇到任何一步报错,把 `docker logs tapledger-server` 的输出贴出来即可排查。