sub / start.sh
gallyg's picture
Update start.sh
82f47c6 verified
#!/usr/bin/env bash
set -euo pipefail
echo "===== Application Startup at $(date '+%Y-%m-%d %H:%M:%S') ====="
export APP_HOME="${APP_HOME:-/app}"
export SUB2API_DATA_DIR="${SUB2API_DATA_DIR:-/app/data}"
export PGDATA="${PGDATA:-/app/pgdata}"
export REDIS_DIR="${REDIS_DIR:-/app/redis}"
export VENV_PATH="${VENV_PATH:-/app/venv}"
mkdir -p "$APP_HOME" "$SUB2API_DATA_DIR" "$PGDATA" "$REDIS_DIR" /var/log/supervisor
# ==========================================
# 可选:重置本地状态(仅调试/首次重新初始化时使用)
# ==========================================
if [[ "${RESET_APP_STATE:-false}" == "true" ]]; then
echo "[reset] wiping local app state..."
rm -rf "$PGDATA"/* || true
rm -rf "$SUB2API_DATA_DIR"/* || true
fi
# =============================
# 基础运行参数
# =============================
export AUTO_SETUP="${AUTO_SETUP:-true}"
export SERVER_HOST="${SERVER_HOST:-0.0.0.0}"
export SERVER_PORT="${SERVER_PORT:-8080}"
export SERVER_MODE="${SERVER_MODE:-release}"
export RUN_MODE="${RUN_MODE:-standard}"
export TZ="${TZ:-Asia/Singapore}"
# =============================
# PostgreSQL(本地)
# =============================
export POSTGRES_USER="${POSTGRES_USER:-sub2api}"
export POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-change_me_please}"
export POSTGRES_DB="${POSTGRES_DB:-sub2api}"
# 强制 sub2api 使用本地 PostgreSQL
export DATABASE_HOST="127.0.0.1"
export DATABASE_PORT="5432"
export DATABASE_USER="${POSTGRES_USER}"
export DATABASE_PASSWORD="${POSTGRES_PASSWORD}"
export DATABASE_DBNAME="${POSTGRES_DB}"
export DATABASE_SSLMODE="disable"
export DATABASE_MAX_OPEN_CONNS="${DATABASE_MAX_OPEN_CONNS:-50}"
export DATABASE_MAX_IDLE_CONNS="${DATABASE_MAX_IDLE_CONNS:-10}"
# bootstrap 阶段走 /tmp socket
export PGHOST="/tmp"
# =============================
# Redis(本地)
# =============================
export REDIS_HOST="127.0.0.1"
export REDIS_PORT="6379"
export REDIS_PASSWORD="${REDIS_PASSWORD:-}"
export REDIS_DB="${REDIS_DB:-0}"
# =============================
# 管理员和密钥
# =============================
export ADMIN_EMAIL="${ADMIN_EMAIL:-admin@example.com}"
export ADMIN_PASSWORD="${ADMIN_PASSWORD:-change_me_please}"
export JWT_SECRET="${JWT_SECRET:-}"
export TOTP_ENCRYPTION_KEY="${TOTP_ENCRYPTION_KEY:-}"
echo "[debug] ADMIN_EMAIL=${ADMIN_EMAIL}"
echo "[debug] ADMIN_PASSWORD_LEN=$(printf %s "$ADMIN_PASSWORD" | wc -c | tr -d ' ')"
# =============================
# 备份 / 恢复控制
# =============================
export AUTO_RESTORE_FROM_DATASET="${AUTO_RESTORE_FROM_DATASET:-true}"
export APPLY_CONFIG_OVERRIDE="${APPLY_CONFIG_OVERRIDE:-false}"
# =============================
# 启动前校验密钥格式
# =============================
validate_hex_even() {
local name="$1"
local value="$2"
if [[ -z "$value" ]]; then
echo "[fatal] ${name} is empty"
exit 1
fi
if ! echo -n "$value" | grep -Eq '^[0-9a-fA-F]+$'; then
echo "[fatal] ${name} must be hex only"
exit 1
fi
local len
len=$(echo -n "$value" | wc -c | tr -d ' ')
if (( len % 2 != 0 )); then
echo "[fatal] ${name} must be even-length hex, got length=${len}"
exit 1
fi
}
validate_hex_even "JWT_SECRET" "$JWT_SECRET"
validate_hex_even "TOTP_ENCRYPTION_KEY" "$TOTP_ENCRYPTION_KEY"
if [[ ! -x "$VENV_PATH/bin/python" ]]; then
echo "[fatal] Python venv not found at $VENV_PATH"
exit 1
fi
# =============================
# PostgreSQL 二进制目录
# =============================
PGBIN="/usr/lib/postgresql/15/bin"
if [[ ! -x "$PGBIN/postgres" ]]; then
echo "[fatal] PostgreSQL binary not found at $PGBIN/postgres"
ls -lah /usr/lib/postgresql || true
exit 1
fi
# =============================
# 目录权限
# =============================
mkdir -p "$PGDATA"
chown -R "$(whoami)":"$(whoami)" "$PGDATA"
chmod 700 "$PGDATA"
fresh_db=false
if [[ ! -s "$PGDATA/PG_VERSION" ]]; then
fresh_db=true
fi
# 如果是损坏的半初始化目录,先清理
if [[ -d "$PGDATA" && ! -s "$PGDATA/PG_VERSION" ]]; then
echo "[init] Cleaning incomplete PGDATA..."
rm -rf "$PGDATA"/*
fi
# =============================
# 初始化 PostgreSQL
# =============================
if [[ ! -s "$PGDATA/PG_VERSION" ]]; then
echo "[init] Initializing PostgreSQL data directory..."
"$PGBIN/initdb" -D "$PGDATA" > /tmp/initdb.log 2>&1 || {
echo "[fatal] initdb failed"
echo "===== /tmp/initdb.log ====="
cat /tmp/initdb.log || true
exit 1
}
chmod 700 "$PGDATA"
cat >> "$PGDATA/postgresql.conf" <<EOF
listen_addresses = '127.0.0.1'
port = 5432
max_connections = 100
shared_buffers = 128MB
unix_socket_directories = '/tmp'
logging_collector = off
log_destination = 'stderr'
EOF
cat > "$PGDATA/pg_hba.conf" <<EOF
local all all trust
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
EOF
echo "[init] Bootstrapping PostgreSQL..."
"$PGBIN/pg_ctl" -D "$PGDATA" -l /tmp/postgres-bootstrap.log start || {
echo "[fatal] pg_ctl bootstrap start failed"
echo "===== /tmp/postgres-bootstrap.log ====="
cat /tmp/postgres-bootstrap.log || true
echo "===== PGDATA listing ====="
ls -lah "$PGDATA" || true
exit 1
}
for i in {1..60}; do
if pg_isready -h 127.0.0.1 -p 5432 >/dev/null 2>&1; then
break
fi
sleep 1
done
echo "[init] Creating/updating application role..."
psql -h /tmp -U "$(whoami)" -d postgres -v ON_ERROR_STOP=1 <<SQL
DO
\$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${POSTGRES_USER}') THEN
CREATE ROLE ${POSTGRES_USER} LOGIN PASSWORD '${POSTGRES_PASSWORD}';
ELSE
ALTER ROLE ${POSTGRES_USER} WITH LOGIN PASSWORD '${POSTGRES_PASSWORD}';
END IF;
END
\$\$;
SQL
echo "[init] Creating application database if not exists..."
psql -h /tmp -U "$(whoami)" -d postgres -v ON_ERROR_STOP=1 <<SQL
SELECT 'CREATE DATABASE ${POSTGRES_DB} OWNER ${POSTGRES_USER}'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${POSTGRES_DB}')\gexec
SQL
# =============================
# 先恢复,再允许后续开始备份
# 只在 fresh DB 时尝试从 dataset 恢复
# =============================
if [[ "$fresh_db" == "true" && "$AUTO_RESTORE_FROM_DATASET" == "true" ]]; then
echo "[restore] fresh database detected, attempting restore from dataset..."
"$VENV_PATH/bin/python" "$APP_HOME/restore_from_dataset.py" --restore-latest || true
fi
echo "[init] Stopping bootstrap PostgreSQL..."
"$PGBIN/pg_ctl" -D "$PGDATA" stop
fi
# ==========================================
# 第二阶段:把备用配置拷贝成正式 config.yaml
# 只在首次 bootstrap 完成后启用
# ==========================================
if [[ "$APPLY_CONFIG_OVERRIDE" == "true" && "${RESET_APP_STATE:-false}" != "true" ]]; then
if [[ -f /app/config.override.yaml ]]; then
mkdir -p /app/data
cp /app/config.override.yaml /app/data/config.yaml
echo "[config] applied /app/config.override.yaml -> /app/data/config.yaml"
else
echo "[config] /app/config.override.yaml not found, skipping"
fi
fi
# =============================
# 生成本地 Redis 配置
# =============================
mkdir -p "$REDIS_DIR"
cat > "$REDIS_DIR/redis.conf" <<EOF
bind 127.0.0.1
port 6379
protected-mode yes
dir ${REDIS_DIR}
save 60 1000
appendonly yes
appendfilename appendonly.aof
EOF
if [[ -n "${REDIS_PASSWORD}" ]]; then
echo "requirepass ${REDIS_PASSWORD}" >> "$REDIS_DIR/redis.conf"
fi
# =============================
# 调试:启动完成后查看 users 表
# =============================
(
sleep 25
echo "[debug] current users in DB:"
psql -h /tmp -U "$(whoami)" -d "${POSTGRES_DB}" -c "select id, email, role, status, created_at from users order by id;" || true
) &
echo "[start] Launching supervisord..."
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf -n