#!/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