- 新增备份管理页与 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 配置
113 行
6.3 KiB
YAML
113 行
6.3 KiB
YAML
# =============================================================================
|
||
# workbuddy-portal —— 单服务部署(采集 / 存储 / 呈现都在同一个进程里)
|
||
#
|
||
# docker compose up -d --build 本机构建并启动
|
||
# docker compose logs -f 跟踪日志
|
||
# docker compose down 停止(数据在命名卷里,不会丢)
|
||
#
|
||
# 设计取舍:
|
||
# * 刻意只有**一个**服务:SQLite 是单写者,调度线程也在 Web 进程内,
|
||
# 多副本只会带来锁竞争与重复采集,所以不做横向扩展。
|
||
# * 数据用**命名卷**而不是绑定挂载。这不是随手选的:
|
||
# Windows + Docker Desktop 走 9p 挂载,宿主的 Windows 进程一旦访问过
|
||
# 这个 WAL 库(哪怕只是 `manage.py stats` 这种纯读),容器侧下一次打开就会
|
||
# `sqlite3.OperationalError: unable to open database file`,且**不会自愈**,
|
||
# 必须重启容器。命名卷住在 Linux VM 的本地文件系统里,不存在这个问题。
|
||
# 需要在宿主机直接看到数据/日志时,叠加 docker-compose.hostdir.yml
|
||
# (只建议 Linux 宿主机用)。
|
||
# =============================================================================
|
||
name: workbuddy-portal
|
||
|
||
services:
|
||
portal:
|
||
# 默认就用 Gitea 注册表里的名字,构建完即可直接 push,不用再补 tag
|
||
image: ${WB_IMAGE:-git.iwali.top/wangchuanli/workbuddy-portal:latest}
|
||
build:
|
||
context: .
|
||
dockerfile: Dockerfile
|
||
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:
|
||
TZ: ${TZ:-Asia/Shanghai} # 决定「每日 09:00 / 17:00」调度与日志时间戳
|
||
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:
|
||
test: ["CMD", "python", "/app/docker/healthcheck.py"]
|
||
interval: 30s
|
||
timeout: 6s
|
||
start_period: 20s
|
||
retries: 3
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
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:
|