| #!/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}" |
|
|
| |
| |
| |
| export POSTGRES_USER="${POSTGRES_USER:-sub2api}" |
| export POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-change_me_please}" |
| export POSTGRES_DB="${POSTGRES_DB:-sub2api}" |
|
|
| |
| 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}" |
|
|
| |
| export PGHOST="/tmp" |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| ( |
| 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 |