- 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 流程)
58 lines
2.3 KiB
Bash
58 lines
2.3 KiB
Bash
#!/bin/bash
|
||
# Pear Admin Flask - NAS 生产启动脚本
|
||
#
|
||
# 流程:
|
||
# 1. 数据目录检查:PEAR_DATA_DIR 必须存在且可写;不存在则用默认行为
|
||
# 2. alembic 迁移到最新版本(幂等;migrations 文件夹随镜像打包)
|
||
# 3. flask admin init 幂等初始化菜单/权限(已有则跳过)
|
||
# 4. exec 切换到 gunicorn,PID 1 由 gunicorn 接管
|
||
#
|
||
# 注意:必须 exec,否则容器启动后 PID 1 是 shell 而非 gunicorn,
|
||
# docker stop 发出的 SIGTERM 不会被正确转发,graceful shutdown 失效。
|
||
|
||
set -e
|
||
|
||
DATA_DIR=${PEAR_DATA_DIR:-/app/data}
|
||
echo "=== [1/4] 数据目录 ==="
|
||
echo "PEAR_DATA_DIR=${DATA_DIR}"
|
||
mkdir -p "${DATA_DIR}/backup" "${DATA_DIR}/flask_session" "${DATA_DIR}/logs" "${DATA_DIR}/upload"
|
||
# 确保子目录可写(WAL 模式下 SQLite 需要 pear.db 同目录的 -wal / -shm 也能创建)
|
||
touch "${DATA_DIR}/.write_test" && rm -f "${DATA_DIR}/.write_test" || {
|
||
echo "FATAL: data dir not writable: ${DATA_DIR}" >&2
|
||
exit 1
|
||
}
|
||
|
||
echo "=== [2/4] 等待数据库就绪 ==="
|
||
# 默认 SQLite 不需要等待;这里预留扩展位(如未来切 MySQL,可在此加 wait-for-it)
|
||
if [[ "${SQLALCHEMY_DATABASE_URI:-}" == mysql* ]]; then
|
||
echo "检测到 MySQL,等待 10s 让 db 服务启动..."
|
||
sleep 10
|
||
fi
|
||
|
||
echo "=== [3/4] 初始化数据库(幂等) ==="
|
||
# flask 命令依赖 FLASK_APP;镜像里已经设过;这里再 fallback 一次
|
||
export FLASK_APP=${FLASK_APP:-app.py}
|
||
|
||
# 数据库迁移:migrate 仅当模型有变才生成新版本;upgrade 永远是幂等的
|
||
flask db upgrade || echo "WARN: flask db upgrade failed (首次启动可能正常)"
|
||
flask admin init || true
|
||
|
||
echo "=== [4/4] 启动 Gunicorn ==="
|
||
# SQLite + WAL 模式下,多 worker 写文件本身没问题(同时间互不阻塞),
|
||
# 但多个事务并发提交仍可能撞锁。这里把 worker 降到 1(线程给够),
|
||
# 既能吃满并发量、又把"database is locked"概率压到最低。
|
||
WORKERS=${GUNICORN_WORKERS:-1}
|
||
THREADS=${GUNICORN_THREADS:-8}
|
||
TIMEOUT=60
|
||
|
||
exec gunicorn \
|
||
--bind 0.0.0.0:5000 \
|
||
--workers "$WORKERS" \
|
||
--threads "$THREADS" \
|
||
--timeout "$TIMEOUT" \
|
||
--graceful-timeout 30 \
|
||
--keep-alive 5 \
|
||
--access-logfile "${DATA_DIR}/logs/access.log" \
|
||
--error-logfile "${DATA_DIR}/logs/error.log" \
|
||
--log-level info \
|
||
app:app |