文件
workbuddy-portal/docs/DEPLOYMENT.md
T
wangchuanli 10db94c162 fix(docker): 数据改用 Docker 命名卷,修容器打不开数据库的问题
## 现象
容器跑着跑着页面全部 500,应用日志:
  sqlite3.OperationalError: unable to open database file
  (db.py:33, conn.execute("PRAGMA journal_mode=WAL"))

## 根因(已最小复现)
数据原本用绑定挂载(./data:/app/data)。Windows + Docker Desktop 的绑定挂载走 9p
(mount 里是 type 9p, aname=drvfs;path=C:\)。9p 本身支持 WAL(新建库能开 WAL),
但**宿主的 Windows 进程打开过这个 WAL 库之后**,容器侧缓存的 -shm 映射就失效,
下一次连接无法重建共享内存文件 → 打不开数据库,且**不会自愈**。

复现:
  docker compose -f docker-compose.yml -f docker-compose.hostdir.yml up -d
  docker compose exec portal python -c "..."     # OK, 1665 条
  python manage.py stats                        # 宿主侧纯读一次
  docker compose exec portal python -c "..."     # ERR unable to open database file
  # 只有 docker compose restart portal 才恢复

## 处理
- docker-compose.yml 改用命名卷 wb_data / wb_logs(容器独占 /app/data)
- 新增 docker-compose.hostdir.yml 叠加层:需要宿主目录时用
  docker compose -f docker-compose.yml -f docker-compose.hostdir.yml up -d
  (注明**只建议 Linux**;Linux 的 bind mount 与容器同一文件系统,无此问题)
- 数据迁移:docker run --rm -v workbuddy-portal_wb_data:/to -v "$PWD/data":/from:ro \
    alpine:3.20 sh -c 'cp -a /from/. /to/'
- 文档同步:DEPLOYMENT 2.4/5.4/第六节全部改为命名卷 + 备份恢复用 docker run;
  新增第九节「Windows 绑定挂载的坑」(含复现步骤);FAQ、USER-GUIDE、README、CHANGELOG 同步

## 验证
- 宿主跑 manage.py stats 与 smoke.py 之后,容器侧仍能正常读写(此前会立刻失效)
- 容器实例 check_live 56/56;离线 smoke 99/99;hostdir 叠加层 config 校验通过
2026-09-14 15:09:24 +08:00

633 行
22 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 部署与运维指南
> 面向**运维 / 部署者**。从零到跑起来,以及跑起来之后的备份、升级、排错。
**目录**
- [一、部署方式怎么选](#一部署方式怎么选)
- [二、Docker Compose 部署](#二docker-compose-部署)
- [三、裸机部署](#三裸机部署)
- [四、反向代理与 HTTPS](#四反向代理与-https)
- [五、把镜像推到 Gitea 注册表](#五把镜像推到-gitea-注册表)
- [六、备份与恢复](#六备份与恢复)
- [七、升级与回滚](#七升级与回滚)
- [八、日常巡检](#八日常巡检)
- [九、排错](#九排错)
- [十、配置项速查](#十配置项速查)
---
## 一、部署方式怎么选
| 场景 | 建议 |
|---|---|
| 有 Docker(NAS / 服务器 / 本机 Docker Desktop) | **Docker Compose**,最省事,升级只需换镜像 |
| 不想装 Docker,或要跑在 Windows 上用系统计划任务兜底 | 裸机 Python + `waitress` |
| 想给多人访问 | 任一种方式 + 反向代理(加 HTTPS 更稳) |
> **不要横向扩展**。SQLite 是单写者,调度线程也在 Web 进程内,
> 多副本只会带来锁竞争和重复采集。这个服务天然是单实例的。
---
## 二、Docker Compose 部署
### 2.1 前置
- Docker Engine 20.10+ / Docker Desktop(含 Compose v2)
- 至少 200 MB 磁盘(镜像 152 MB + 数据)
### 2.2 步骤
```bash
git clone https://git.iwali.top/wangchuanli/workbuddy-portal.git
cd workbuddy-portal
cp .env.example .env
vi .env # 至少设置 WB_ADMIN_PASSWORD
docker compose up -d --build
docker compose ps # 等 STATUS 变成 (healthy)
docker compose logs -f
```
浏览器打开 `http://<服务器IP>:8848`。
### 2.3 `.env` 主要变量
| 变量 | 默认 | 说明 |
|---|---|---|
| `WB_BIND` | `0.0.0.0` | 宿主机绑定地址。只想本机访问就设 `127.0.0.1` |
| `WB_PORT` | `8848` | 宿主机端口 |
| `TZ` | `Asia/Shanghai` | **影响「每日 09:00/17:00」与所有日期口径** |
| `WB_ADMIN_USER` | `admin` | 首个管理员用户名(只在库为空时生效) |
| `WB_ADMIN_PASSWORD` | 空 | 首个管理员密码。**留空会用 `admin123`**,务必显式设置 |
| `WB_DISABLE_SCHEDULER` | `0` | `1` = 不启动调度线程(只跑手动采集) |
| `WB_IMPORT_CREDS` | `0` | `1` = 启动时尝试从挂载的编辑器配置导入 Cookie |
### 2.4 数据落点:用命名卷,不用绑定挂载
| 容器内 | 存放位置 | 内容 |
|---|---|---|
| `/app/data` | Docker 命名卷 `workbuddy-portal_wb_data` | `usage.sqlite`(正本)、`instance.json`(secret_key)、`exports/` |
| `/app/logs` | Docker 命名卷 `workbuddy-portal_wb_logs` | `app.log`(滚动 2 MB × 3) |
**为什么是命名卷而不是脚本目录里的 `./data`**(这不是随手选的):
> Windows + Docker Desktop 的绑定挂载走 **9p**(`aname=drvfs;path=C:\`)。
> 宿主的 Windows 进程只要访问过这个 WAL 库——**哪怕只是 `manage.py stats` 这种纯读**——
> 容器侧下一次打开就会 `sqlite3.OperationalError: unable to open database file`,
> 而且**不会自愈**,必须 `docker compose restart portal`。实测复现见
> [第九节「Windows 绑定挂载的坑」](#windows-绑定挂载的坑容器打不开数据库)。
命名卷住在 Linux VM 的本地文件系统里,不存在跨文件系统翻译的问题,
所以容器独占数据目录是**唯一在所有平台上都正确**的做法。
**代价**:宿主机的 `manage.py` 不能直接读容器里的库了。随之而来的三件事都有一行命令替代:
```bash
# 在容器里跑 CLI(推荐,始终打到同一份数据)
docker compose exec portal python manage.py stats
docker compose exec portal python manage.py vacuum
docker compose exec portal python manage.py passwd admin 新密码
# 看日志
docker compose logs -f portal
# 备份 / 恢复:见第六节
```
#### 想要「宿主机直接看到数据/日志」怎么办
叠加 `docker-compose.hostdir.yml` 即可:
```bash
docker compose -f docker-compose.yml -f docker-compose.hostdir.yml up -d
# 可用 WB_HOST_DATA_DIR / WB_HOST_LOG_DIR 指定目标目录
```
> ⚠️ **只建议 Linux 宿主机使用**(bind mount 与容器是同一个文件系统,
> SQLite 的 WAL 与文件锁语义正常)。Windows + Docker Desktop 下会踩上面那个坑。
>
> Linux 首次运行若报 `unable to open database file`,是宿主目录属主与容器内 uid 1000 不一致:
> ```bash
> sudo chown -R 1000:1000 ./data ./logs
> ```
### 2.5 常用命令
```bash
docker compose ps
docker compose logs -f --tail=100
docker compose restart
docker compose down # 停并删容器,数据保留
docker compose up -d --build # 改完代码重新构建
# 在容器里跑 CLI(同一个数据卷)
docker compose exec portal python manage.py stats
docker compose exec portal python manage.py status
docker compose exec portal python manage.py passwd admin 新密码
docker compose exec portal python manage.py vacuum
docker compose exec portal python manage.py collect # 手动采集一次
```
> **本机调试**(Docker Desktop on Windows)已实测:命名卷上的 SQLite(WAL)读写正常,
> 启动补采、采集、导出、CSV 流式下载、健康检查全部可用;
> 且**宿主侧再跑 `manage.py` / `tools/smoke.py` 都不会影响容器**(这正是改用命名卷的原因)。
---
## 三、裸机部署
### 3.1 Windows
```bat
git clone https://git.iwali.top/wangchuanli/workbuddy-portal.git
cd workbuddy-portal
py -3 -m venv .venv
.venv\Scripts\pip install -r requirements.txt
.venv\Scripts\python manage.py init --user admin --password 你的强密码
.venv\Scripts\python manage.py serve
```
开机自启用「任务计划程序」:触发器「计算机启动时」,操作
`<项目路径>\.venv\Scripts\python.exe`,参数 `manage.py serve`,起始位置设为项目目录。
### 3.2 Linux
```bash
git clone https://git.iwali.top/wangchuanli/workbuddy-portal.git
cd workbuddy-portal
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python manage.py init --user admin --password 你的强密码
```
`/etc/systemd/system/workbuddy-portal.service`:
```ini
[Unit]
Description=WorkBuddy Portal
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=workbuddy
WorkingDirectory=/opt/workbuddy-portal
Environment=TZ=Asia/Shanghai
ExecStart=/opt/workbuddy-portal/.venv/bin/python manage.py serve --host 0.0.0.0 --port 8848
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now workbuddy-portal
sudo systemctl status workbuddy-portal
journalctl -u workbuddy-portal -f
```
> **不要**用 `manage.py collect` + cron 替代内置调度,除非你确实想让调度留在外部
> (那种情况下 Web 端要加 `--no-scheduler`,避免和 cron 抢锁——虽然文件锁会保证正确性,
> 但会白跑一次)。
---
## 四、反向代理与 HTTPS
前面挂 nginx 时要注意两点,否则会踩坑:
```nginx
server {
listen 443 ssl;
server_name portal.example.com;
ssl_certificate /etc/ssl/certs/portal.crt;
ssl_certificate_key /etc/ssl/private/portal.key;
location / {
proxy_pass http://127.0.0.1:8848;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 必须透传:登录失败限速按真实 IP 计数,否则所有请求都算到代理头上
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 导出 CSV 已带 X-Accel-Buffering: no,这里关掉代理缓冲才能边查边吐
proxy_buffering off;
proxy_read_timeout 300s; # 采集/导出可能跑几分钟
}
}
```
排错要点:
| 现象 | 原因 |
|---|---|
| 登录限速「误伤」所有人 | 没透传 `X-Forwarded-For` |
| 导出 CSV 要等很久才出第一个字节 | 没关 `proxy_buffering` |
| 手动采集走到 504 | `proxy_read_timeout` 太短 |
---
## 五、把代码与镜像推到 Gitea
Gitea 自带容器注册表(`registry/2.0`)。代码仓库与镜像的目标都是 `git.iwali.top/wangchuanli/workbuddy-portal`。
### 5.0 一条命令推代码 + 推镜像
准备好 Gitea **Access Token**(「设置 → 应用 → 生成令牌」,勾选 `repo` + `write:package`)后:
```bash
export GITEA_TOKEN=<你的令牌>
tools/push-all.sh # 推 main 分支 + 镜像 latest
tools/push-all.sh 1.1.0 # 同时打一个版本 tag 并推送
```
脚本做的事:
1. `git push origin main` —— 用 `http.extraHeader` 传 Basic 认证,Token **只在环境变量里**,
不会写进 `.git/config`、URL 或 reflog;同时用 `-c credential.helper=` 屏蔽凭据助手
(否则在非交互 / 无桌面会话里 Git Credential Manager 会挂住等弹窗)。
2. `docker compose build` —— 镜像名本身就是注册表地址。
3. `docker login` + `docker push`(`--password-stdin`,Token 不进命令行历史)。
> 不用脚本、手工推也行:`git push origin main` 弹出凭据窗口时,
> 用户名填 `wangchuanli`,**密码处填 Access Token**(不是网页登录密码)。
### 5.1 传输协议:默认走 HTTPS(不用配 insecure-registries)
`git.iwali.top` 有 **Let's Encrypt 通配证书(`*.iwali.top`)**,HTTPS 全程可用:
```bash
curl -s -o /dev/null -w "%{http_code}\n" https://git.iwali.top/api/v1/version # 200
curl -s -o /dev/null -w "%{http_code}\n" https://git.iwali.top/v2/ # 401(需认证,正常)
```
所以:
- **git 远端用 `https://`**(仓库地址见上);
- **镜像名就是 `git.iwali.top/...`,Docker 默认按 HTTPS 访问** —— 无需任何额外配置。
> 只有在 Gitea 前面**没有** TLS 终止(纯 `http://`)时,才需要在 Docker Desktop
> **Settings → Docker Engine** 的 `daemon.json` 里加:
> ```json
> { "insecure-registries": ["git.iwali.top"] }
> ```
> 然后 `Apply & Restart`(Linux 改 `/etc/docker/daemon.json` 后重启 docker)。
> 明文传输会让 Token 暴露在网络里,**能走 HTTPS 就不要开这个口子**。
### 5.2 登录
```bash
docker login git.iwali.top -u wangchuanli
# 密码用 Personal Access Token(Gitea「设置 → 应用 → 生成令牌」,
# 勾选 write:package;不要用网页登录密码)
```
### 5.3 构建并推送
```bash
# 镜像名默认已经是注册表地址(见 docker-compose.yml 的 image: 字段)
docker compose build
# 打上语义化版本标签
docker tag git.iwali.top/wangchuanli/workbuddy-portal:latest \
git.iwali.top/wangchuanli/workbuddy-portal:1.1.0
docker push git.iwali.top/wangchuanli/workbuddy-portal:latest
docker push git.iwali.top/wangchuanli/workbuddy-portal:1.1.0
```
### 5.4 在另一台机器上拉取运行
```bash
docker pull git.iwali.top/wangchuanli/workbuddy-portal:1.1.0
# 不 clone 仓库也能跑:只写一个 compose 文件
cat > docker-compose.yml <<'YAML'
services:
portal:
image: git.iwali.top/wangchuanli/workbuddy-portal:1.1.0
restart: unless-stopped
ports: ["8848:8848"]
environment:
TZ: Asia/Shanghai
WB_ADMIN_PASSWORD: "改成你的强密码"
volumes:
- wb_data:/app/data
- wb_logs:/app/logs
volumes:
wb_data:
wb_logs:
YAML
docker compose up -d
```
### 5.5 验证远端
```bash
docker manifest inspect git.iwali.top/wangchuanli/workbuddy-portal:1.1.0
# 或
curl -s -u wangchuanli:TOKEN \
https://git.iwali.top/api/v1/packages/wangchuanli?type=container
```
---
## 六、备份与恢复
### 6.1 备份什么
数据都在命名卷 `workbuddy-portal_wb_data` 里(对应容器内 `/app/data`):
| 文件 | 重要性 | 说明 |
|---|---|---|
| `usage.sqlite` | ★★★ | **数据正本**,丢了要重新采集,且官网窗口外的数据永久丢失 |
| `usage.sqlite-wal` / `-shm` | ★★★ | WAL 模式下未 checkpoint 的数据在这里,**要一起拷** |
| `instance.json` | ★★ | 含 `secret_key`,丢了所有人都要重新登录(数据不受影响) |
| `exports/*.csv` | ★ | 导出快照,可再生 |
| `workbuddy-portal_wb_logs` | ☆ | 排错用,可再生 |
`.env` 不在里面——它含密码,**单独用密码管理器保管**。
> **不要**在容器运行时用宿主机的 `manage.py` 去碰库(见 2.4 与第九节)。
### 6.2 备份
**方式 A:整体打包命名卷(推荐,最完整)**
```bash
docker compose stop portal
docker run --rm \
-v workbuddy-portal_wb_data:/data:ro \
-v "$PWD/backup":/backup \
alpine:3.20 tar czf /backup/wb-data-$(date +%F).tar.gz -C /data .
docker compose start portal
```
**方式 B:只导数据库文件(最常用)**
```bash
# 先 checkpoint,把 WAL 落进主库,再拷——这样单独一个 .sqlite 就是完整的
docker compose exec portal python -c "
from workbuddy_portal import db
db.connect().execute('PRAGMA wal_checkpoint(TRUNCATE)')"
docker run --rm \
-v workbuddy-portal_wb_data:/data:ro \
-v "$PWD/backup":/backup \
alpine:3.20 cp /data/usage.sqlite /backup/usage-$(date +%F).sqlite
```
**方式 C:逻辑导出(跨版本最安全,可读性最好)**
```bash
docker compose exec portal python manage.py export-csv /app/data/exports
docker compose cp portal:/app/data/exports/. ./backup/exports/
```
建议方式 B 每天跑、方式 C 每季度跑一次;方式 A 在升级前跑。
### 6.3 恢复
```bash
docker compose down
# 把备份灌回命名卷(先清空,避免新旧 WAL 混用损坏数据)
docker volume rm workbuddy-portal_wb_data
docker volume create workbuddy-portal_wb_data
docker run --rm \
-v workbuddy-portal_wb_data:/to \
-v "$PWD/backup":/from:ro \
alpine:3.20 sh -c 'cp -a /from/. /to/'
docker compose up -d
docker compose exec portal python manage.py stats # 核对条数
```
> **别把旧库和旧 WAL 混着用**:WAL 里记的是相对旧库的增量,配错会损坏数据。
> 用方式 B 的备份(已 checkpoint)最省心,恢复时目标目录里只放一个 `.sqlite` 即可。
### 6.4 迁移:从绑定挂载换到命名卷
如果之前用的是 `docker-compose.hostdir.yml`(或旧版把 `./data` 直接挂进去),数据在
宿主机目录里,搬进命名卷:
```bash
docker compose down
docker volume create workbuddy-portal_wb_data
docker run --rm \
-v workbuddy-portal_wb_data:/to \
-v "$PWD/data":/from:ro \
alpine:3.20 sh -c 'cp -a /from/. /to/'
docker compose up -d # 不带 -f hostdir 叠加层
docker compose exec portal python manage.py stats
```
> 迁移完成后,宿主机上的 `./data` 就不再被容器使用了,可以留作迁移前的冷备。
---
## 七、升级与回滚
### Docker
```bash
git pull
docker compose build
docker compose up -d # 重建容器,数据在挂载卷里不受影响
docker compose exec portal python manage.py stats
```
回滚:把 `.env` 里的 `WB_IMAGE` 指回旧版本标签,然后
```bash
docker compose up -d --no-build
```
### 裸机
```bash
git pull
.venv/bin/pip install -r requirements.txt
sudo systemctl restart workbuddy-portal
```
### 升级前
1. **先备份**(见第六节)——`schema.sql` 用的是 `CREATE TABLE IF NOT EXISTS`,
加表加索引是安全的,但改列需要手工迁移,所以备份是唯一保险。
2. 看一眼 [CHANGELOG](CHANGELOG.md) 有没有破坏性变更。
---
## 八、日常巡检
| 频率 | 做什么 |
|---|---|
| 每天 | 打开「概览」看「采集健康」;确认今天有采集记录 |
| 每周 | 「日志管理」按 `warn` / `error` 筛一遍,看有没有 TLS 或解密类告警 |
| 每月 | 确认 Cookie 没过期(「配置管理」看提示);跑一次备份恢复演练 |
| 每季度 | `manage.py vacuum`;出一份全量 CSV 归档;检查磁盘占用 |
一键体检:
```bash
docker compose exec portal python manage.py status
docker compose exec portal python manage.py stats
```
---
## 九、排错
### 容器起来了但页面打不开
```bash
docker compose ps # 看 STATUS 是否 (healthy)
docker compose logs --tail=100
```
| 症状 | 排查 |
|---|---|
| `STATUS` 是 `Restarting` | 看日志里的 Python traceback;多半是 `data/` 权限或端口冲突 |
| `unhealthy` 但 `Up` | 健康检查打 `/login` 失败;`docker compose exec portal python /app/docker/healthcheck.py` 看具体报错 |
| 端口占用 | 改 `.env` 的 `WB_PORT`,如 `18848:8848` |
| 宿主机能访问、局域网不能 | `WB_BIND` 是不是被改成 `127.0.0.1` 了;防火墙有没有放行 |
### `exec format error` / `no such file or directory`(entrypoint)
`docker/entrypoint.sh` 被 CRLF 污染了。仓库里有 `.gitattributes` 强制 `*.sh` 为 LF;
若手工传过文件,执行:
```bash
python -c "p='docker/entrypoint.sh';d=open(p,'rb').read();open(p,'wb').write(d.replace(b'\r\n',b'\n'))"
```
### 数据库相关
| 报错 | 原因 / 处理 |
|---|---|
| `unable to open database file`(**用了 `hostdir` 叠加层**) | 宿主目录属主与容器内 uid 1000 不一致:`sudo chown -R 1000:1000 ./data` |
| `unable to open database file`(**Windows + Docker Desktop**) | 见下面 [「Windows 绑定挂载的坑」](#windows-绑定挂载的坑容器打不开数据库);根治办法是用默认的命名卷 |
| `database is locked` | 有另一个写进程;等它跑完(文件锁会串行化,但 SQLite 层仍会短暂报错) |
| `disk I/O error` | 挂载文件系统不支持 SQLite 的锁语义。改用本地盘或 Docker 命名卷 |
### Windows 绑定挂载的坑:容器打不开数据库
**症状**:容器起来时一切正常,跑着跑着 `/` `/records` 全部 `500`,应用日志里是
```
File "/app/workbuddy_portal/db.py", line 33, in connect
conn.execute("PRAGMA journal_mode=WAL")
sqlite3.OperationalError: unable to open database file
```
**根因**:Windows + Docker Desktop 的绑定挂载走 **9p**(`mount` 里能看到
`type 9p (…aname=drvfs;path=C:\…)`)。9p 本身支持 WAL(在上面新建库没问题),
但**当宿主的 Windows 进程打开过这个 WAL 库之后**,容器侧缓存的 `-shm` 映射就失效了,
下一次连接无法重建共享内存文件,于是打不开数据库。
**最小复现**(2026-09-14 实测):
```bash
docker compose -f docker-compose.yml -f docker-compose.hostdir.yml up -d
docker compose exec portal python -c \
"import sqlite3;print(sqlite3.connect('/app/data/usage.sqlite').execute('select count(*) from usage_records').fetchone())"
# -> (1665,) 容器侧正常
python manage.py stats # 宿主侧随手跑一次「纯读」的 CLI
# -> 存档:1665 条 …
docker compose exec portal python -c \
"import sqlite3;sqlite3.connect('/app/data/usage.sqlite')"
# -> sqlite3.OperationalError: unable to open database file
```
关键点:**纯读也会触发**,而且**宿主进程退出后容器不会自愈**——
只有 `docker compose restart portal` 才恢复。
**处理**:
1. **根治(推荐)**:不要用 `hostdir` 叠加层,直接用默认的**命名卷**
(`docker-compose.yml`)。容器独占 `/app/data`,宿主侧一律通过
`docker compose exec portal python manage.py …` 操作。
2. **临时**:`docker compose restart portal` 立刻恢复,但下一次宿主访问会再坏一次。
3. **想在宿主侧看数据**:用第六节的方式导出到宿主目录再看,不要直接连库。
> Linux 宿主机上不存在这个问题(bind mount 与容器是同一个文件系统),
> 所以 `docker-compose.hostdir.yml` 对 Linux 是安全的。
### 采集相关
| 报错 | 处理 |
|---|---|
| `cookie_expired` / `401` | 重新获取 Cookie 填进「配置管理」,然后按区间补采 |
| `TLS` / `SSLError` | 企业代理 / 自签证书场景,临时把 `ssl_verify` 设为 `0`;否则保持开启 |
| 返回 `409 busy` | 正常——已有采集在跑,等它结束 |
| 新增一直是 0 | 看「抓取」条数:>0 说明都是已存在的(正常);=0 说明云端该时段确实没数据 |
### 时区不对导致日期错位
```bash
docker compose exec portal date # 应该输出 CST / +0800
```
若不是,检查 `.env` 的 `TZ=Asia/Shanghai`,改完 `docker compose up -d` 重建容器
(`TZ` 是环境变量,`restart` 不生效)。
### 想临时关掉自动采集
「任务管理 → 取消勾选『启用调度』→ 保存」。或者
```bash
echo "WB_DISABLE_SCHEDULER=1" >> .env && docker compose up -d
```
---
## 十、配置项速查
调度与采集参数都在数据库里,**改完立即生效、不用重启**(页面「任务管理 / 配置管理」可改,
也可以直接改表):
| 键 | 默认 | 说明 |
|---|---|---|
| `schedule_enabled` | `1` | 调度总开关 |
| `schedule_times` | `09:00,17:00` | 每日时刻,逗号分隔,本地时区 |
| `catch_up` | `1` | 启动补跑开关 |
| `catch_up_grace_hours` | `12` | 补跑宽限期(小时) |
| `page_size` | `200` | 采集单页条数(20~1000) |
| `rewind_minutes` | `2` | 断点回退分钟数(0~120) |
| `drift_tolerance_minutes` | `5` | 云端时间漂移告警阈值(0~720) |
| `max_prompt` | `2048` | Prompt 入库截断长度,0 = 不截断 |
| `verify_days` | `0` | 采集后整日校验天数(0~90) |
| `timeout` | `30` | HTTP 超时秒数(5~300) |
| `ssl_verify` | `1` | 校验云端 HTTPS 证书 |
| `api_base` / `api_path` | 官方地址 | 接口地址(走镜像/代理时改) |
| `cookie` | 空 | 账号凭证(页面只回掩码) |
| `user_agent` | Chrome UA | 与 Cookie 同源更稳 |
**写错的值会在保存时被拒绝**并给出原因,不会污染配置。
环境变量(启动期,改了要重建容器):
| 变量 | 说明 |
|---|---|
| `TZ` | 时区,影响所有日期口径 |
| `WB_HOST` / `WB_PORT` | 容器内监听地址 / 端口 |
| `WB_DATA_DIR` / `WB_LOG_DIR` / `WB_DB` | 数据 / 日志 / 库文件路径覆盖 |
| `WB_DISABLE_SCHEDULER` | `1` = 不启动调度线程 |
| `WB_ADMIN_USER` / `WB_ADMIN_PASSWORD` | 首个管理员(仅库为空时生效) |
| `WB_IMPORT_CREDS` / `WB_IMPORT_XLSX` | 启动时自动导入 |