文件
workbuddy-portal/docs/DEPLOYMENT.md
T
wangchuanli df7db3582e feat(multi-user): 多用户化 + 凭证加密 + 自助注册与图形验证码
数据隔离
- settings / usage_records 主键改为 (user_id, key) / (user_id, request_id),
  索引一律以 user_id 打头;collect_runs / audit_log 增加 user_id
- query / collect / scheduler 全链路把 uid 作为 conn 之后的第一个位置参数且无默认值
  (漏传直接 TypeError,不会退化成「返回全量」)
- 配置三级回落 个人→实例→DEFAULTS;NO_FALLBACK_KEYS={cookie,user_agent} 不回落

凭证保密
- 新增 workbuddy_portal/crypto.py:手写 ChaCha20(RFC8439 §2.3) + HMAC-SHA256
  encrypt-then-MAC,零第三方依赖;主密钥 cookie_key 与 SECRET_KEY 分键位存放
- get_secret() 是取明文的唯一通道;get_settings() 把加密键置空;
  secret_state() 只回 {set,chars,tail,broken};升级时自动加密历史明文

注册与验证码
- 新增 /register 与 workbuddy_portal/captcha.py(手写 PNG + 点阵字模 + 干扰线)
- 验证码答案只存服务端表、不进 session,一次性、5 分钟过期、按 purpose 隔离
- allow_register / register_max_per_ip / captcha_policy / captcha_length 四个实例级开关
- 失败限速改为 IP + 用户名双维度;停用账号每请求回查、立即失效

页面
- 新增 /profile(个人中心)与注册页;登录页加验证码与自助注册入口
- /config 增加凭证状态、cookie_broken 告警、实例级设置区;/users 增加邮箱/状态与启停

修复
- base.html 顶层 {% set me %} 覆盖子模板同名变量,导致个人中心「注册于」渲染为空
- WB_COOKIE_SECURE 未写进 compose 的 environment,在 .env 里设了不生效
- 「修改登录密码」提示写「至少 6 位」,与实际策略(≥8 位 + 两类字符)不符
- 「用户管理」删除说明写「可勾选保留」,与页面实际行为不符
- 注册页与 flash 文案里的 **强调** Markdown 字面量

验证与文档
- smoke.py 99 → 165 项断言(多用户隔离 / 凭证保密 / 注册与验证码 / 3 条防回归)
- check_live.py 56 → 83 项断言(新增注册 / 验证码 / 安全响应头一节)
- demo_data.py 造两个账号;shots.py 自动过验证码、重出 11 张截图
- README / SECURITY / ARCHITECTURE / API / DEPLOYMENT / USER-GUIDE / FAQ / CHANGELOG / CONTRIBUTING 同步
2026-09-15 17:32:35 +08:00

25 KiB
原始文件 Blame 文件历史

部署与运维指南

面向运维 / 部署者。从零到跑起来,以及跑起来之后的备份、升级、排错。

目录


一、部署方式怎么选

场景 建议
有 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 步骤

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_COOKIE_SECURE 0 1 = 会话 Cookie 只走 HTTPS。纯 HTTP 部署设成 1 会导致「登录成功又跳回登录页」,见 第九节
WB_DISABLE_SCHEDULER 0 1 = 不启动调度线程(只跑手动采集)
WB_IMPORT_CREDS 0 1 = 启动时尝试从挂载的编辑器配置导入 Cookie

TZ 与 WB_COOKIE_SECURE 都是进程环境变量,docker compose restart 不生效,要 up -d。

2.4 数据落点:用命名卷,不用绑定挂载

容器内 存放位置 内容
/app/data Docker 命名卷 workbuddy-portal_wb_data usage.sqlite(正本)、instance.json(secret_key + cookie_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 绑定挂载的坑」。

命名卷住在 Linux VM 的本地文件系统里,不存在跨文件系统翻译的问题, 所以容器独占数据目录是唯一在所有平台上都正确的做法。

代价:宿主机的 manage.py 不能直接读容器里的库了。随之而来的三件事都有一行命令替代:

# 在容器里跑 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 即可:

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 不一致:

sudo chown -R 1000:1000 ./data ./logs

2.5 常用命令

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

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

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:

[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
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 时要注意两点,否则会踩坑:

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)后:

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 全程可用:

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 里加:

{ "insecure-registries": ["git.iwali.top"] }

然后 Apply & Restart(Linux 改 /etc/docker/daemon.json 后重启 docker)。 明文传输会让 Token 暴露在网络里,能走 HTTPS 就不要开这个口子。

5.2 登录

docker login git.iwali.top -u wangchuanli
# 密码用 Personal Access Token(Gitea「设置 → 应用 → 生成令牌」,
# 勾选 write:package;不要用网页登录密码)

5.3 构建并推送

# 镜像名默认已经是注册表地址(见 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 在另一台机器上拉取运行

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 验证远端

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(会话签名)与 cookie_key(各账号 Cookie 的加密主密钥)。丢了/被替换:所有人要重新登录,且所有账号存的 Cookie 都会变成「无法解密」,需要各自重填
exports/*.csv ★ 导出快照,可再生
workbuddy-portal_wb_logs ☆ 排错用,可再生

.env 不在里面——它含密码,单独用密码管理器保管。

不要在容器运行时用宿主机的 manage.py 去碰库(见 2.4 与第九节)。

6.2 备份

方式 A:整体打包命名卷(推荐,最完整)

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:只导数据库文件(最常用)

# 先 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:逻辑导出(跨版本最安全,可读性最好)

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 恢复

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 直接挂进去),数据在 宿主机目录里,搬进命名卷:

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

git pull
docker compose build
docker compose up -d                    # 重建容器,数据在挂载卷里不受影响
docker compose exec portal python manage.py stats

回滚:把 .env 里的 WB_IMAGE 指回旧版本标签,然后

docker compose up -d --no-build

裸机

git pull
.venv/bin/pip install -r requirements.txt
sudo systemctl restart workbuddy-portal

升级前

  1. 先备份(见第六节)。备份要同时包含 usage.sqlite 与 instance.json —— 后者存着凭证加密主密钥,只备库不备它,恢复后所有 Cookie 都要重填。
  2. 看一眼 CHANGELOG 有没有破坏性变更。

1.1.0 → 1.2.0(单用户 → 多用户)

无需任何手工迁移命令。 首次用新版启动时会自动完成,日志里能看到:

做了什么 效果
建 users 表、写入首个管理员 用 WB_ADMIN_USER / WB_ADMIN_PASSWORD,或沿用 admin / admin123
settings / usage_records / collect_runs / audit_log 改为 (user_id, …) 复合主键 老数据整体归到第一个账号
明文 Cookie 就地加密 日志记一条 明文凭证已加密:settings[uid=1].cookie
建 captchas 表、补索引 验证码用

迁移由 PRAGMA user_version 驱动,幂等:重复启动不会重复执行。 校验一下:

docker compose logs portal | grep -i "迁移\|migrat"
docker compose exec portal python manage.py users    # 账号 / 角色 / 数据量 / 凭证状态
docker compose exec portal python manage.py stats     # 各账号条数与积分

升级后请确认 Cookie 能解密:登录后打开「配置管理 → 我的云端凭证」, 正常应显示「已配置 · N 字符,结尾 …xxxx」。若显示「无法解密」,说明 instance.json 不匹配,重新粘贴一次即可。


八、日常巡检

频率 做什么
每天 打开「概览」看「采集健康」;确认今天有采集记录
每周 「日志管理」按 warn / error 筛一遍,看有没有 TLS 或解密类告警
每月 确认 Cookie 没过期(「配置管理」看提示);跑一次备份恢复演练
每季度 manage.py vacuum;出一份全量 CSV 归档;检查磁盘占用

一键体检:

docker compose exec portal python manage.py status
docker compose exec portal python manage.py stats

九、排错

容器起来了但页面打不开

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 了;防火墙有没有放行

登录成功却立刻又跳回登录页

几乎一定是 WB_COOKIE_SECURE 被设成了 1,而你在用 HTTP 访问。

会话 Cookie 带 Secure 属性后,浏览器只在 HTTPS 下才回传;服务端每次都收不到会话, 就判定「未登录」,再把你送回登录页。表现是「密码明明对,页面却停在登录页」,日志里 看起来像在反复登录。

# .env
WB_COOKIE_SECURE=0
docker compose up -d          # 环境变量,必须 up -d,restart 不生效

只有在前面真的挂了 HTTPS 反向代理、并且用域名访问时,才把它设为 1。 (另:如果站点前后端域名不同,还要看第四节的反代配置。)

exec format error / no such file or directory(entrypoint)

docker/entrypoint.sh 被 CRLF 污染了。仓库里有 .gitattributes 强制 *.sh 为 LF; 若手工传过文件,执行:

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 绑定挂载的坑」;根治办法是用默认的命名卷
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 实测):

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())"
# -> (944,)   容器侧正常

python manage.py stats          # 宿主侧随手跑一次「纯读」的 CLI
# -> 存档:944 条 …

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 说明云端该时段确实没数据

时区不对导致日期错位

docker compose exec portal date        # 应该输出 CST / +0800

若不是,检查 .env 的 TZ=Asia/Shanghai,改完 docker compose up -d 重建容器 (TZ 是环境变量,restart 不生效)。

想临时关掉自动采集

「任务管理 → 取消勾选『启用调度』→ 保存」。或者

echo "WB_DISABLE_SCHEDULER=1" >> .env && docker compose up -d

十、配置项速查

调度与采集参数都在数据库里,改完立即生效、不用重启(页面「任务管理 / 配置管理」可改, 也可以直接改表)。表的主键是 (user_id, key):user_id=0 表示实例级(所有账号共用, 仅管理员可改),其余是个人级(每个账号一份,互不可见)。

键 默认 作用域 说明
api_base / api_path 官方地址 实例级 接口地址(走镜像/代理时改)
allow_register 1 实例级 是否开放自助注册
register_max_per_ip 3 实例级 同一 IP 每日注册上限(1~50)
captcha_policy always 实例级 always / adaptive / off
captcha_length 4 实例级 验证码位数(4~6)
cookie 空 个人级 账号凭证,密文入库;页面只回「长度 + 结尾 4 位」
user_agent Chrome UA 个人级 与 Cookie 同源更稳。cookie 与 user_agent 不参与实例级回落(回落 = 串号越权)
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 证书

读配置时有三级回落:个人级 → 实例级 → 代码里的 DEFAULTS。 所以实例级的值只是「默认值」,任何账号都可以用自己的值覆盖它(cookie / user_agent 例外)。

写错的值会在保存时被拒绝并给出原因,不会污染配置。未知键也会被拒—— 接口不能用来往 settings 表里塞任意键。

环境变量(启动期,改了要重建容器):

变量 说明
TZ 时区,影响所有日期口径
WB_HOST / WB_PORT 容器内监听地址 / 端口
WB_DATA_DIR / WB_LOG_DIR / WB_DB 数据 / 日志 / 库文件路径覆盖
WB_COOKIE_SECURE 1 = 会话 Cookie 只走 HTTPS(纯 HTTP 部署必须留 0)
WB_DISABLE_SCHEDULER 1 = 不启动调度线程
WB_ADMIN_USER / WB_ADMIN_PASSWORD 首个管理员(仅库为空时生效)
WB_IMPORT_CREDS / WB_IMPORT_XLSX 启动时自动导入