feat: 新增备份恢复与公网加固
- 新增备份管理页与 API:在线快照、自动周期备份、按份数清理、下载、一键恢复(恢复前自动兜底) - 新增 /profile/export,普通用户可导出本人全部数据(不含 Cookie 明文) - 修复 X-Forwarded-For 可伪造导致三道 IP 防线失效,统一走 client_ip() 取客户端地址 - 取消 admin123 硬编码默认口令,留空则生成随机初始口令并仅打印一次 - .dockerignore 排除 backups/ 并加构建期断言,防止密钥随镜像分发 - 新增会话版本号,改密/停用/删除及恢复备份后其他会话立即失效 - 新增容器资源上限、采集跨度硬顶 31 天、重操作最小间隔与并发 409 - 新增访问日志、HSTS 条件下发、口令黑名单、验证码抗模板匹配、instance.json 0600 - 版本号升至 1.4.0,同步更新 README、SECURITY、.env.example 与 compose 配置
这个提交包含在:
@@ -28,6 +28,28 @@ services:
|
||||
container_name: workbuddy-portal
|
||||
restart: unless-stopped
|
||||
init: true # tini 接管 PID 1:docker stop 能干净地传到 python
|
||||
|
||||
# ---- 容器资源上限(对外提供服务时的第一道闸门)----
|
||||
# 应用层已经做了「采集频率 / 并发 / 跨度」三重限制,但那些是**业务**刹车;
|
||||
# 这里限制的是**进程**本身能吃掉多少宿主机资源 —— 两者都要有:
|
||||
# 业务刹车管「正常的重活别做太多」,容器上限管的是一次异常(内存泄漏、
|
||||
# 正则回溯、某次超大导出)能不能把整台机器带下去。
|
||||
#
|
||||
# 单写者架构下**不要**靠加副本扛负载,所以「限制单实例资源 + 限制单账号
|
||||
# 频率」才是正解,而不是横向扩展(见文件头的设计取舍说明)。
|
||||
cpus: "${WB_CPUS:-1.0}" # 1 核:SQLite 单写者,多给核也并行不起来
|
||||
mem_limit: "${WB_MEM_LIMIT:-512m}"
|
||||
memswap_limit: "${WB_MEM_LIMIT:-512m}" # 与 mem_limit 相等 = 禁用 swap,
|
||||
# 否则内存超限会悄悄滑进 swap,
|
||||
# 表现为「越来越慢」而不是「被 OOM 杀掉」
|
||||
pids_limit: ${WB_PIDS_LIMIT:-256} # 挡 fork 炸弹
|
||||
# 文件句柄:SQLite 会在主库之外再持有 -wal / -shm,备份快照与恢复期间
|
||||
# 还要同时开临时库 + ATTACH 源库,默认 1024 在高并发下偏紧。
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 4096
|
||||
hard: 8192
|
||||
|
||||
ports:
|
||||
- "${WB_BIND:-0.0.0.0}:${WB_PORT:-8848}:8848"
|
||||
environment:
|
||||
@@ -35,16 +57,37 @@ services:
|
||||
WB_HOST: 0.0.0.0
|
||||
WB_PORT: "8848"
|
||||
WB_ADMIN_USER: ${WB_ADMIN_USER:-admin}
|
||||
# 留空时程序会生成**随机**口令并只在启动日志里打印一次(不再有 admin123 兜底),
|
||||
# 所以首次部署要去 `docker compose logs portal | grep 口令` 抄一次。
|
||||
WB_ADMIN_PASSWORD: ${WB_ADMIN_PASSWORD:-}
|
||||
WB_DISABLE_SCHEDULER: ${WB_DISABLE_SCHEDULER:-0}
|
||||
# ---- 反向代理与传输安全(三个必须一起决定,别只改一个)----
|
||||
# WB_TRUST_PROXY:默认 0 = 不信任 X-Forwarded-For。
|
||||
# 直接暴露给公网时必须留 0 —— 否则攻击者每次换一个伪造的 XFF,
|
||||
# 验证码限速 / 注册配额 / 登录锁定三道 IP 防线会同时失效。
|
||||
# 置 1 的前提:**你自己的**反代会写这个头。开启后程序取 XFF 里
|
||||
# **最右侧**的合法 IP(最近一跳由你的代理写入,客户端加不进去),
|
||||
# 所以 nginx 写 $proxy_add_x_forwarded_for(保留链路,便于排查)或
|
||||
# 写 $remote_addr(覆盖)都可以 —— 关键是别去信最左边那段。
|
||||
WB_TRUST_PROXY: ${WB_TRUST_PROXY:-0}
|
||||
# 前面挂了 HTTPS 反代时置 1:读到 X-Forwarded-Proto: https 就不跳转
|
||||
WB_FORCE_HTTPS: ${WB_FORCE_HTTPS:-0}
|
||||
# 会话 Cookie 是否只走 HTTPS。纯 HTTP 部署必须留 0:设成 1 时浏览器
|
||||
# 不会回传会话 Cookie,表现为「登录成功又立刻跳回登录页」。改它要 up -d(环境变量)。
|
||||
WB_COOKIE_SECURE: ${WB_COOKIE_SECURE:-0}
|
||||
# 访问日志(waitress 自己不记 access log,出事无据可查时很要命)
|
||||
WB_ACCESS_LOG: ${WB_ACCESS_LOG:-1}
|
||||
# waitress 线程数 = 单实例能同时吃进几个慢请求(采集/导出/备份恢复)
|
||||
WB_THREADS: ${WB_THREADS:-4}
|
||||
WB_BACKUP_DIR: /app/backups
|
||||
WB_IMPORT_CREDS: ${WB_IMPORT_CREDS:-0}
|
||||
WB_IMPORT_XLSX: ${WB_IMPORT_XLSX:-}
|
||||
volumes:
|
||||
- wb_data:/app/data # 数据正本 + 导出 + secret_key
|
||||
- wb_logs:/app/logs # 应用日志(滚动 2 MB × 3)
|
||||
# 备份**单独一个卷**:备份与正本同卷时,一次 `down -v` 或卷损坏会把
|
||||
# 两者一起带走 —— 那正是最需要备份的时刻。分开挂才有意义。
|
||||
- wb_backups:/app/backups
|
||||
# 可选:把编辑器配置挂进来,配合 WB_IMPORT_CREDS=1 自动接管 cookie
|
||||
# - ${WB_EDITOR_SETTINGS:-./nonexistent.json}:/mnt/editor-settings.json:ro
|
||||
healthcheck:
|
||||
@@ -62,3 +105,8 @@ services:
|
||||
volumes:
|
||||
wb_data:
|
||||
wb_logs:
|
||||
# 备份卷。**必须与 wb_data 分开** —— 同卷时 `down -v` 会把正本与备份一起删。
|
||||
# 想让它跟着正本走(比如整套搬到别的机器)就先 `docker run --rm -v
|
||||
# workbuddy-portal_wb_backups:/b -v $PWD/backups:/o alpine cp -a /b/. /o/`
|
||||
# 把归档捞到宿主机目录里,再一起搬。
|
||||
wb_backups:
|
||||
|
||||
在新工单中引用
屏蔽一个用户