feat(data): 数据库搬到 data/ 目录 + SQLite WAL + 单卷绑定挂载
- config.py 新增 PEAR_DATA_DIR(默认 <项目根>/data),SQLite 库、Flask-Session
统一收进 DATA_DIR;不设时本地开发行为不变
- applications/__init__.py 顶部先 load_dotenv,保证 .env 里的 PEAR_DATA_DIR
在 config 导入时就生效
- extensions/init_sqlalchemy.py 接 SQLite connect 事件:WAL + synchronous=NORMAL
+ busy_timeout=30s(多 worker 写不再撞锁)
- config.py 注入 SQLALCHEMY_ENGINE_OPTIONS(pool_pre_ping + SQLite connect_args
超时;MySQL 路径下自动跳过 connect_args 防止参数错误)
- 新增 alembic 迁移 a47a5d2a3f1b 建 site_nav_click(含 anon_id 字段),
解决之前该表只由 db.create_all 建、不在迁移链里的隐患
- .gitignore / .dockerignore 加 data/ 排除规则(且把 migrations/ 从
ignore 里重新放行 —— 否则新加的迁移进不了库)
- NAS Docker 部署全面重写:
- compose 唯一绑定挂载 ./data:/app/data,宿主 File Station 看得见
- start.sh 自检 data 目录 + 子目录;workers 默认改为 1 threads 8
(SQLite 写串行,单进程最稳)
- 新增 backup.sh:sqlite3 .backup 在线热备(确保 WAL 一致性)+ 压缩
+ 保留 14 天
- Dockerfile 安装 sqlite3 客户端备用,建 /app/data 子目录并 chown
- docs/deployment-nas.md 重写数据持久化章节(路径表 + 热备命令 +
恢复步骤 + 整库迁移 tar/untar 流程)
This commit is contained in:
+78
-37
@@ -20,7 +20,7 @@ NAS 上需安装:
|
||||
## 2. 准备 .env
|
||||
|
||||
```bash
|
||||
cd /path/to/pear-admin-flask
|
||||
cd /volume1/docker/pear-admin-flask
|
||||
cp .env.example .env
|
||||
# 编辑 .env:SECRET_KEY 必须改成强随机字符串
|
||||
python -c "import secrets; print(secrets.token_urlsafe(48))"
|
||||
@@ -37,6 +37,10 @@ python -c "import secrets; print(secrets.token_urlsafe(48))"
|
||||
# 进入项目目录
|
||||
cd /volume1/docker/pear-admin-flask # 改成你自己的路径
|
||||
|
||||
# 如果你之前用过 pear-admin-flask,要把现有库搬过来:
|
||||
mkdir -p data
|
||||
cp pear.db data/pear.db # 把根目录的库搬到 data/ 下(容器会把整个 ./data 挂进 /app/data)
|
||||
|
||||
# 构建镜像(首次约 3-5 分钟)
|
||||
docker compose -f deploy/nas/docker-compose.yaml build
|
||||
|
||||
@@ -84,37 +88,74 @@ ports:
|
||||
|
||||
## 6. 数据持久化
|
||||
|
||||
| 卷名 | 容器内路径 | 内容 |
|
||||
|---|---|---|
|
||||
| `pear-admin-nas_pear_db` | `/app/pear.db` | SQLite 数据库 |
|
||||
| `pear-admin-nas_logs` | `/app/logs` | gunicorn / Flask 日志 |
|
||||
| `pear-admin-nas_session` | `/app/flask_session` | 登录会话文件 |
|
||||
| `pear-admin-nas_upload` | `/app/static/upload` | 上传图片 |
|
||||
整个数据库 / 会话 / 备份 / 上传,**统一收进宿主 `./data` 目录**,容器内路径 `/app/data`。
|
||||
|
||||
查看卷:
|
||||
|
||||
```bash
|
||||
docker volume ls | grep pear
|
||||
```
|
||||
./data # docker-compose 唯一绑定挂载
|
||||
├── pear.db # SQLite 主库(WAL 模式)
|
||||
├── pear.db-wal # WAL 文件(不要删)
|
||||
├── pear.db-shm # 共享内存文件(不要删)
|
||||
├── backup/ # 在线热备(自动保留最近 14 天)
|
||||
│ └── pear-20260906_020000.db.gz
|
||||
├── flask_session/ # Flask-Session 文件
|
||||
├── logs/ # gunicorn / Flask 日志
|
||||
│ ├── access.log
|
||||
│ └── error.log
|
||||
└── upload/ # 上传图片
|
||||
```
|
||||
|
||||
### 备份 SQLite 数据库
|
||||
在 NAS File Station 里直接看得到、可以直接 tar 走 —— 这是从"看不见的虚拟卷"改成"看得见的文件夹"的核心收益。
|
||||
|
||||
### 备份(在线热备)
|
||||
|
||||
容器镜像里带了 `sqlite3`,`deploy/nas/backup.sh` 走 `sqlite3 .backup`(含 WAL 一致性,比 `cp` 安全):
|
||||
|
||||
```bash
|
||||
# 备份(推荐每天 1 次,可加入 cron)
|
||||
docker run --rm \
|
||||
-v pear-admin-nas_pear_db:/data \
|
||||
-v /volume1/docker/pear-backup:/backup \
|
||||
alpine sh -c "cp /data/pear.db /backup/pear-$(date +%F).db && ls -la /backup/"
|
||||
docker exec pear-admin-nas /app/deploy/nas/backup.sh
|
||||
# 输出:[backup] done. latest = /app/data/backup/pear-20260906_020000.db.gz
|
||||
```
|
||||
|
||||
恢复:
|
||||
### 自动备份(推荐,群晖 / 威联通 / Linux 都适用)
|
||||
|
||||
群晖 DSM:「控制面板 → 任务计划表 → 新增 → 计划的任务 → 用户定义的脚本」:
|
||||
|
||||
```
|
||||
每天 02:00 跑:
|
||||
docker exec pear-admin-nas /app/deploy/nas/backup.sh
|
||||
```
|
||||
|
||||
或者宿主 crontab:
|
||||
|
||||
```bash
|
||||
0 2 * * * docker exec pear-admin-nas /app/deploy/nas/backup.sh
|
||||
```
|
||||
|
||||
默认保留 14 天(`BACKUP_KEEP=14` 可改)。
|
||||
|
||||
### 恢复
|
||||
|
||||
```bash
|
||||
# 停容器
|
||||
docker compose -f deploy/nas/docker-compose.yaml down
|
||||
docker run --rm \
|
||||
-v pear-admin-nas_pear_db:/data \
|
||||
-v /volume1/docker/pear-backup:/backup \
|
||||
alpine sh -c "rm -f /data/pear.db && cp /backup/pear-2026-09-06.db /data/pear.db"
|
||||
|
||||
# 把备份解开(注意:必须先停容器才能覆盖 pear.db,否则 SQLite 还在持有文件锁)
|
||||
gunzip -c data/backup/pear-20260906_020000.db.gz > data/pear.db
|
||||
# 顺带把 -wal / -shm 也清掉(WAL 会自动重建)
|
||||
rm -f data/pear.db-wal data/pear.db-shm
|
||||
|
||||
# 起回去
|
||||
docker compose -f deploy/nas/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
整库迁移到新机器:
|
||||
|
||||
```bash
|
||||
# 旧机器:tar 整个 data 目录(不算大,库 100MB 级别)
|
||||
cd /volume1/docker/pear-admin-flask
|
||||
tar czf pear-data-$(date +%F).tgz data/
|
||||
|
||||
# 新机器:解开 → 启动即用
|
||||
tar xzf pear-data-2026-09-06.tgz -C /volume1/docker/pear-admin-flask/
|
||||
docker compose -f deploy/nas/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
@@ -137,7 +178,7 @@ docker compose -f deploy/nas/docker-compose.yaml up -d
|
||||
curl -i http://NAS_IP:5000/healthz
|
||||
```
|
||||
|
||||
数据库迁移由 `start.sh` 中的 `flask db upgrade` 自动执行(幂等)。
|
||||
数据库迁移由 `start.sh` 中的 `flask db upgrade` 自动执行(幂等;含 `site_nav_click` 等插件表的迁移)。
|
||||
|
||||
---
|
||||
|
||||
@@ -152,6 +193,7 @@ docker compose -f deploy/nas/docker-compose.yaml logs --tail=50
|
||||
最常见原因:
|
||||
- `SECRET_KEY` 还是占位符 `PLEASE_REPLACE_WITH_RANDOM_STRING` → ProConfig 会拒绝启动
|
||||
- 端口被占用 → 修改 `ports` 段
|
||||
- `data/` 目录存在但权限不对(容器内 uid 1001 没写权限)→ `chown -R 1001:1001 data/`
|
||||
|
||||
### Q2:访问首页 502 / 拒绝连接
|
||||
|
||||
@@ -165,26 +207,24 @@ docker inspect --format='{{.State.Health.Status}}' pear-admin-nas
|
||||
|
||||
### Q3:忘记 admin 密码
|
||||
|
||||
直接重置(删容器、再用空 pear.db 启动,再 `flask admin init` 重新生成默认账号):
|
||||
直接重置(删除整个 `data/pear.db`,再启动让 `flask admin init` 重新生成默认账号):
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/nas/docker-compose.yaml down
|
||||
docker volume rm pear-admin-nas_pear_db
|
||||
rm -f data/pear.db data/pear.db-wal data/pear.db-shm
|
||||
docker compose -f deploy/nas/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
⚠️ **此操作会清空所有数据**,务必先备份。
|
||||
⚠️ **此操作会清空所有数据**,务必先 `docker exec … backup.sh` 一份。
|
||||
|
||||
### Q4:日志占满磁盘
|
||||
|
||||
```bash
|
||||
# 查看日志大小
|
||||
docker system df -v
|
||||
|
||||
# 清理已停止容器的日志
|
||||
docker compose -f deploy/nas/docker-compose.yaml down
|
||||
docker volume rm pear-admin-nas_logs
|
||||
docker compose -f deploy/nas/docker-compose.yaml up -d
|
||||
# 看 data/logs 实际大小
|
||||
du -sh data/logs
|
||||
# 清空(保留文件)
|
||||
: > data/logs/access.log
|
||||
: > data/logs/error.log
|
||||
```
|
||||
|
||||
### Q5:想要外网访问
|
||||
@@ -211,9 +251,10 @@ deploy:
|
||||
|
||||
或减少 worker:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- GUNICORN_WORKERS=1 # 内存紧张时降到 1
|
||||
```env
|
||||
# .env 里
|
||||
GUNICORN_WORKERS=1
|
||||
GUNICORN_THREADS=16 # SQLite + WAL 单进程跑,线程数给够
|
||||
```
|
||||
|
||||
Gunicorn 默认 `2 workers × 4 threads = 8 个并发槽位**,对 ≤ 50 并发的场景完全够用。
|
||||
**为什么默认 workers=1 threads=8 而不是 2×4**:SQLite 写串行;多进程提交事务时仍可能撞锁,gunicorn worker 越多事故面越大。单进程写最稳,线程吃满网络并发。你的场景下 8 线程绰绰有余,要更高把线程拉到 16 就行。
|
||||
Reference in New Issue
Block a user