# 小白复盘局域网 Docker 部署

本文以 Linux 服务器为目标，容器内外均使用 `8765` 端口，宿主机监听
`0.0.0.0:8765`。局域网用户通过 `http://服务器局域网IP:8765` 访问。

## 1. 部署结构

```text
局域网浏览器
    |
    v
服务器 0.0.0.0:8765
    |
    v
xiaobai-review 容器 :8765
    |-- /app              只读应用代码
    |   `-- backend/features/heaven/assets/heaven_knowledge.json
    |                     镜像内 seed（不受 data 挂载遮盖）
    `-- /app/data         宿主机 ./data 持久化挂载
        |-- review.db
        |-- iching_zh.json
        `-- heaven_knowledge.json   优先读取；缺失时回退到上方 seed
```

账号、加密后的公共数据 Token、平台模型 API Key、生辰资料、行情快照和复盘数据均在
`data/review.db`。解密密钥来自 `.env` 中的 `APP_ENCRYPTION_KEY`。数据库与
密钥必须成对备份，任意一个丢失都无法恢复账号内的加密资料。

问天静态知识文件：

- `data/iching_zh.json`、`data/heaven_knowledge.json` 纳入 Git 与镜像构建；
  `.dockerignore` 不排除这两个文件（只排除 `data/*.db`、`data/cache/` 等运行时产物）。
- Compose 把宿主机 `./data` 整目录挂到 `/app/data`，会遮盖镜像里同路径文件。
  因此宿主机 `data/` 应保留上述两个 JSON；若只缺 `heaven_knowledge.json`，
  服务会回退读取镜像内
  `backend/features/heaven/assets/heaven_knowledge.json`，解势仍可用。
- 持久化位置：正式环境以宿主机项目目录下的 `./data/heaven_knowledge.json` 为准；
  补文件后无需改代码，重启容器即可加载。

管理员私有的问师 Skill 保存在宿主机 `data/private-mentor-skills/`。该目录随 `data`
挂载进入容器，但被 Git 与 Docker 构建上下文排除，不会进入 Gitea 或镜像。私有 Skill
只对管理员账号返回和开放调用，也会随本指南的 `data` 备份一起保存。

首个注册账号自动成为管理员。管理员在“系统管理”中配置全站共享行情、后台刷新、平台会员模型及手动会员；普通用户的“账号设置”用于个人资料、会员状态、修改密码和切换账号。后台行情更新不会主动刷新任何浏览器页面。

## 2. 服务器要求

- 64 位 Linux 服务器；
- Docker Engine 24 或更新版本；
- Docker Compose v2，命令形式为 `docker compose`；
- 服务器可以访问 Tushare、已配置的 LLM 和实时聚合数据源；
- 局域网内没有其他服务占用 TCP `8765`。

验证 Docker：

```bash
docker --version
docker compose version
```

## 3. 迁移现有数据

迁移前先停止当前 Windows 服务，避免复制过程中 SQLite 继续写入。
然后在应用目录执行一次 WAL 检查点：

```powershell
python -c "import sqlite3; c=sqlite3.connect('data/review.db'); print(c.execute('PRAGMA wal_checkpoint(TRUNCATE)').fetchone()); c.close()"
```

结果第一项应为 `0`。必须迁移以下内容：

```text
data/
.env
Dockerfile
compose.yaml
其余程序文件
```

不要重新生成 `APP_ENCRYPTION_KEY`。部署已有数据库时，目标服务器 `.env` 中的
值必须与原服务器完全一致。

可以在项目目录生成迁移包：

```powershell
tar --exclude='__pycache__' --exclude='*.log' --exclude='data/cache' -czf ..\xiaobai-review.tar.gz .
scp ..\xiaobai-review.tar.gz 用户名@服务器IP:/tmp/
```

迁移包包含数据库和密钥，传输完成后应及时删除两端的压缩包。

## 4. 首次启动

在 Linux 服务器执行：

```bash
sudo mkdir -p /opt/xiaobai-review
sudo chown "$USER":"$USER" /opt/xiaobai-review
tar -xzf /tmp/xiaobai-review.tar.gz -C /opt/xiaobai-review
cd /opt/xiaobai-review
chmod 600 .env
sudo chown -R 10001:10001 data
docker compose config
docker compose build --pull
docker compose up -d
```

镜像使用 UID/GID `10001` 的非 root 用户运行，因此宿主机 `data` 目录必须允许
该用户写入。不要把整个应用目录设为可写。

检查运行状态：

```bash
docker compose ps
docker compose logs --tail=100 xiaobai-review
curl http://127.0.0.1:8765/api/health
docker inspect --format '{{.State.Health.Status}}' xiaobai-review
```

健康接口应返回类似内容：

```json
{"ok": true, "storage": "sqlite", "account_required": true}
```

随后在局域网电脑访问：

```text
http://服务器局域网IP:8765
```

## 5. 防火墙

Compose 已明确绑定 `0.0.0.0:8765`。服务器防火墙建议只允许实际局域网网段，
不要在路由器上把该端口映射到公网。

Ubuntu/UFW 示例，假设局域网为 `192.168.1.0/24`：

```bash
sudo ufw allow from 192.168.1.0/24 to any port 8765 proto tcp
sudo ufw status
```

如果服务器位于其他网段，应替换为实际 CIDR。访问失败时同时检查云服务器安全组、
虚拟化平台防火墙和宿主机防火墙。

## 6. 日常管理

查看日志：

```bash
cd /opt/xiaobai-review
docker compose logs -f --tail=100 xiaobai-review
```

重启：

```bash
docker compose restart xiaobai-review
```

停止：

```bash
docker compose down
```

### 服务器本地目录更新与构建（日常推荐）

生产机 `192.168.200.11` 的 `/opt/1panel/docker/compose/xiaobaifupan` 自 2026-08-29（HEL-235B）
起已是受 Git 管理的工作目录，只跟踪 Gitea `main`（仓库
`http://192.168.200.36:3200/leefer/xiaobai-review.git`）。由于目录顶层归 root，
`.git` 存放在部署账号家目录（外部 Git 目录方案）：

```text
~/xiaobai-build/repos/xiaobai-review.git          Git 元数据（分支/历史/索引）
/opt/1panel/docker/compose/xiaobaifupan           工作目录（程序文件本体）
~/xiaobai-build/update-from-main.sh               一键更新+构建入口
~/xiaobai-git                                    便捷查看（status/log/diff）
```

日常更新只需要在服务器上执行一条命令：

```bash
~/xiaobai-build/update-from-main.sh            # 更新到 main 并构建 main-<短号> 镜像
~/xiaobai-build/update-from-main.sh verify-tag main-a8732f5   # 部署前复核镜像与 main 一致
```

脚本在构建前强制完成五道校验，任一不符立即停止、不产出镜像：

1. `git fetch` 成功（连不上 Gitea 即停）；
2. 必须在 `main` 分支（智能体不得用功能分支直接当正式线）；
3. 工作区无未提交改动、无多余文件；
4. 只允许快进合并到 `origin/main`（分叉即停）；main 新增/删除顶层文件时会给出
   需管理员执行的精确清单（目录顶层归 root）；
5. 构建后回读镜像 `org.opencontainers.image.revision`，与 `main` 提交不一致则删除镜像。

镜像 tag 固定为 `main-<提交短号7位>`（不带提交号的模糊 tag 一律禁止）；每次构建在
`~/xiaobai-build/BUILD_LOG.tsv` 留痕。构建只产出镜像，不启动、不替换容器；换版与
回滚步骤见 `~/xiaobai-build/README.md`。

`compose.yaml` 的镜像名与 revision 标签同样做了强校验：直接 `docker compose up -d --build`
会因缺少 `XIAOBAI_GIT_REV` / `XIAOBAI_GIT_SHORT` 变量而拒绝执行，避免再出现构建进
`latest` 的模糊版本。需要用 compose 时先 `export` 这两个变量（值以
`~/xiaobai-build/xiaobai-git rev-parse HEAD` 为准），或直接用上面的脚本。

### 智能体高级入口：Git 归档流式构建

有仓库检出、能免密 SSH 到部署机的智能体可以用 `tools/build_image.sh <提交号> <镜像tag>`
从任意明确提交流式构建（`git archive | ssh docker build`），tag 同样必须以
`-<提交短号7位>` 结尾，构建后回读 revision 校验并留痕。用于在服务器不便拉取时的
应急构建；日常正式线仍应走 `main`。

### 历史方式（已废弃）

早期文档建议在服务器重新 `git clone` 一份或手工上传代码后 `docker compose up --build`。
这两条路径已废弃：服务器上**只允许存在一个受管工作目录**（上述
`/opt/1panel/docker/compose/xiaobaifupan`），任何脱离 Git 校验的本地构建都会把
来源提交变成不可追溯状态，禁止使用。

## 7. 备份与恢复

最稳妥的备份方式是短暂停服后同时备份数据库目录和密钥：

```bash
cd /opt/xiaobai-review
docker compose stop xiaobai-review
tar -czf "xiaobai-backup-$(date +%Y%m%d-%H%M%S).tar.gz" data .env
docker compose start xiaobai-review
```

恢复时先停止容器，再恢复 `data` 和与其配套的 `.env`，修复权限后启动：

```bash
docker compose down
sudo chown -R 10001:10001 data
chmod 600 .env
docker compose up -d
```

## 8. 常见问题

### 容器反复重启

```bash
docker compose logs --tail=200 xiaobai-review
```

优先检查 `.env` 是否存在、`APP_ENCRYPTION_KEY` 是否为空，以及 `data` 是否可写。

### 提示账号加密数据无法解密

目标服务器使用了错误的 `APP_ENCRYPTION_KEY`。停止容器并恢复与数据库配套的
原始 `.env`，不要通过重置密钥绕过该错误。

### SQLite 显示只读或无法打开

```bash
sudo chown -R 10001:10001 /opt/xiaobai-review/data
sudo chmod -R u+rwX /opt/xiaobai-review/data
docker compose restart xiaobai-review
```

### 本机健康检查正常但其他电脑无法访问

确认 `docker compose ps` 显示 `0.0.0.0:8765->8765/tcp`，然后检查服务器防火墙和
客户端到服务器的网络路由。

## 9. 安全边界

当前部署使用局域网 HTTP，账号密码和会话只适合可信内网使用。不要直接将
`8765` 暴露到互联网。以后需要公网访问时，应在容器前增加 Caddy 或 Nginx，
启用 HTTPS，并限制可信来源。
