将调度时刻、采集参数等实例级配置收归管理员,普通账号仅可维护本人 Cookie 与 User-Agent。 新增 config.writable_by 作为唯一写权限入口,set_setting 强制全局键落到 user_id=0, 消除「管理员改了只有自己生效」的静默缺陷。新增 tools/check_docs.py 文档自检, smoke 断言扩至 215 项、check_live 扩至 122 项并支持普通账号越权验收, 忽略 backups/、data/*.bak* 与 legacy-v1/,版本升至 v1.3.0。
4.3 KiB
backups/ — 数据库备份存放处
人工或脚本产生的数据库快照统一放这里。不要放进 data/,原因见下。
为什么备份不放在 data/
data/ 是运行时数据目录,在 Docker 部署下会被挂载成卷(workbuddy-portal_wb_data)。
- 备份放进
data/→ 执行docker compose down -v或重建卷时,备份会跟着正本一起被删掉, 这正好是最需要备份的那一刻。 - 备份放在仓库目录下的
backups/→ 卷重建不影响它,同时离源码够近、搬家不丢。
backups/ 下的一切都被 .gitignore 忽略(见仓库根 .gitignore 的「数据库备份」段),
绝不要提交:快照里含 settings 表的加密凭证密文与 users 表的密码哈希。
目录内容
| 文件 | 说明 |
|---|---|
usage.sqlite.bak-pre-v13 |
v1.2.0 → v1.3.0 迁移(DB_SCHEMA_VERSION 2 → 3)前的快照。保留用意:迁移同时做了「配置作用域收敛」(把调度与采集参数从个人级提升到实例级 user_id=0),万一收敛结果不符合预期,可回滚到这份 uv=2 的库重来。 |
校验记录(2026-09-16)
integrity_check : ok
user_version : 2
usage_records : 1665 条
SUM(credits) : 8513.36
迁移后正本 data/usage.sqlite(uv=3)同样是 1665 条 / 8513.36 积分,零丢失。
快照本身是自包含的:全部数据都在主文件里(
-wal为 0 字节,没有未落盘的提交帧)。但要注意一个会反复出现的现象:只要有人以 WAL 模式打开过这份快照, SQLite 就会就地重建
…-wal/…-shm两个侧车文件 —— 连只读打开也会 (SQLite 需要-shm做锁表)。所以「移走一次」不是长久之计, 校验命令里加immutable=1才是根治(告诉 SQLite 这个文件不会变,不必建锁表)。侧车只是运行时缓存,
backups/已在.gitignore里被整体忽略, 不会误入库。归档或搬运快照前把两个侧车移走即可,前提是先确认-wal是 0 字节。
怎么用
校验一份备份是否可用(immutable=1 = 只读且不建锁表,不会改动也不会污染快照):
python -c "
import sqlite3
c = sqlite3.connect('file:backups/usage.sqlite.bak-pre-v13?immutable=1', uri=True)
print('integrity:', c.execute('PRAGMA integrity_check').fetchone()[0])
print('user_version:', c.execute('PRAGMA user_version').fetchone()[0])
print('records:', c.execute('SELECT COUNT(*) FROM usage_records').fetchone()[0])
"
备份文件的唯一权威判据是
integrity_check与行数,别拿sha256当验签 —— 快照落盘后再被 SQLite 干净关闭过一次,主文件字节可能变化而内容完全一致。 校验只需sqlite3标准库,用你跑服务的同一个解释器即可。
回滚一份备份:停掉服务 → 备份当前正本 → 把快照覆盖回 data/usage.sqlite
→ 同时删除 data/usage.sqlite-wal 与 data/usage.sqlite-shm(残留的 WAL 会让 SQLite 读到旧状态)
→ 启动服务 → 跑 python manage.py status 与 python manage.py stats 确认账号与存档条数。
回滚前务必确认目标库的
user_version与服务端workbuddy_portal/db.py的DB_SCHEMA_VERSION兼容:回滚到更老的版本号时,服务会在下次启动时重跑迁移。
自动备份(可选)
容器部署下推荐用宿主机的 cron 做,先落盘再压缩,避免 SQLite 在线拷贝产生撕裂快照:
# 每天 03:30,用 SQLite 自带的一致性备份命令(不是 cp!)
docker compose exec -T portal python -c "
import sqlite3
src = sqlite3.connect('/app/data/usage.sqlite')
dst = sqlite3.connect('/tmp/wb-backup.sqlite')
src.backup(dst); dst.close(); src.close()
"
docker compose cp portal:/tmp/wb-backup.sqlite \
"./backups/usage-$(date +%Y%m%d).sqlite"
docker compose exec -T portal rm -f /tmp/wb-backup.sqlite
cp 一个正在被写入的 SQLite 文件可能拿到半截事务;sqlite3.Connection.backup()
走的是官方的在线备份 API,能保证快照一致。手工离线拷贝时才可以直接 cp。
清理策略
保留最近 7 份日备 + 每份迁移前快照。旧的直接删,别在这里堆积——
backups/ 是安全网,不是归档;数据正本永远只有 data/usage.sqlite 一个。