Files
pear-admin-flask/docs/deployment-nas.md
T
bwstudio be99b8a925 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 流程)
2026-09-06 19:43:28 +08:00

260 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pear Admin Flask - 个人 NAS 部署指南
适用:内网 / 家庭 / 小团队(≤ 50 并发用户,无 K8s 需求)。
---
## 1. 前置检查
NAS 上需安装:
| 软件 | 最低版本 | 验证命令 |
|---|---|---|
| Docker Engine | 20.10+ | `docker --version` |
| Docker Compose | v2.x | `docker compose version` |
> 如果 NAS 是群晖 / 威联通,请在套件中心安装 **Container Manager** 或自行 SSH 安装 docker。
---
## 2. 准备 .env
```bash
cd /volume1/docker/pear-admin-flask
cp .env.example .env
# 编辑 .envSECRET_KEY 必须改成强随机字符串
python -c "import secrets; print(secrets.token_urlsafe(48))"
# 把输出贴到 .env 的 SECRET_KEY=
```
`.env` **绝不能提交到 git**(已在 `.gitignore` 中)。
---
## 3. 首次构建 + 启动
```bash
# 进入项目目录
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
# 后台启动
docker compose -f deploy/nas/docker-compose.yaml up -d
# 查看启动日志(应看到 "Listening at: http://0.0.0.0:5000"
docker compose -f deploy/nas/docker-compose.yaml logs -f
```
启动成功后访问:`http://NAS_IP:5000`
默认账号 `admin` / `123456`(首次登录后**强烈建议改密码**)。
---
## 4. 健康检查
```bash
# 进程是否还活着(DB OK 返回 200;DB 不可达返回 503)
curl -i http://NAS_IP:5000/healthz
# 200 OK: {"status":"ok","db_ok":true,"env":"production","system":"Pear Admin","timestamp":"..."}
# 容器视角的健康状态(Docker 30s 探一次)
docker inspect --format='{{.State.Health.Status}}' pear-admin-nas
# healthy / unhealthy / starting
```
---
## 5. 端口与局域网访问
`docker-compose.yaml` 中默认 `5000:5000`,局域网内任意设备 `http://NAS_IP:5000` 都能访问。
如要改端口(如 NAS 上 5000 被占用):
```yaml
ports:
- "8080:5000" # 宿主 8080 → 容器 5000
```
手机访问:`http://NAS_IP:8080`(确保 NAS 防火墙放行)。
---
## 6. 数据持久化
整个数据库 / 会话 / 备份 / 上传,**统一收进宿主 `./data` 目录**,容器内路径 `/app/data`
```
./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/ # 上传图片
```
在 NAS File Station 里直接看得到、可以直接 tar 走 —— 这是从"看不见的虚拟卷"改成"看得见的文件夹"的核心收益。
### 备份(在线热备)
容器镜像里带了 `sqlite3``deploy/nas/backup.sh``sqlite3 .backup`(含 WAL 一致性,比 `cp` 安全):
```bash
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
# 把备份解开(注意:必须先停容器才能覆盖 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
```
---
## 7. 升级流程
```bash
# 1. 拉取最新代码
cd /volume1/docker/pear-admin-flask
git pull
# 2. 重新构建镜像
docker compose -f deploy/nas/docker-compose.yaml build
# 3. 重启容器(migrations 自动跑)
docker compose -f deploy/nas/docker-compose.yaml up -d
# 4. 验证
curl -i http://NAS_IP:5000/healthz
```
数据库迁移由 `start.sh` 中的 `flask db upgrade` 自动执行(幂等;含 `site_nav_click` 等插件表的迁移)。
---
## 8. 常见问题
### Q1:容器启动后立刻退出
```bash
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 / 拒绝连接
```bash
# 确认容器在跑
docker ps | grep pear-admin-nas
# 看健康状态
docker inspect --format='{{.State.Health.Status}}' pear-admin-nas
```
### Q3:忘记 admin 密码
直接重置(删除整个 `data/pear.db`,再启动让 `flask admin init` 重新生成默认账号):
```bash
docker compose -f deploy/nas/docker-compose.yaml down
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
# 看 data/logs 实际大小
du -sh data/logs
# 清空(保留文件)
: > data/logs/access.log
: > data/logs/error.log
```
### Q5:想要外网访问
需要做两件事:
1. NAS 上做端口映射(5000 → 公网 IP + DDNS
2. **必须**在前面套一层 Nginx/Caddy 加 HTTPS(避免密码明文)
可参考 `deploy/nas/README.md`(如果后续加入 nginx 反代方案)。
---
## 9. 性能调优
NAS 通常内存有限(4-8 GB)。如遇卡顿:
```yaml
# docker-compose.yaml 中调小资源限制
deploy:
resources:
limits:
memory: 768M # 从 1024M 降到 768M
```
或减少 worker
```env
# .env 里
GUNICORN_WORKERS=1
GUNICORN_THREADS=16 # SQLite + WAL 单进程跑,线程数给够
```
**为什么默认 workers=1 threads=8 而不是 2×4**:SQLite 写串行;多进程提交事务时仍可能撞锁,gunicorn worker 越多事故面越大。单进程写最稳,线程吃满网络并发。你的场景下 8 线程绰绰有余,要更高把线程拉到 16 就行。