#!/bin/bash set -e DATA_ROOT="${DATA_ROOT:-/data/wifiplatform}" DATA_DIR="${DB_DATA_DIR:-$DATA_ROOT/mysql}" SOURCE_DATA_ROOT="${SOURCE_DATA_ROOT:-}" LOG_DIR="$DATA_ROOT/logs" PAYMENT_STORAGE_DIR="$DATA_ROOT/storage/payment" QUARANTINE_DIR="$DATA_ROOT/quarantine/mysql-table-artifacts" INIT_FLAG="$DATA_ROOT/.db_initialized" RESTORE_SQL_PATH="${RESTORE_SQL_PATH:-}" DB_DIR="$DATA_DIR/${DB_NAME:-wifiplatform}" MYSQL_SYSTEM_DIR="$DATA_DIR/mysql" DEFAULT_DISABLED_LOG_TYPES="env,migrate,migration" normalize_log_type() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' } log_type_list_contains() { local list="$1" local needle needle="$(normalize_log_type "$2")" [ -n "$needle" ] || return 1 IFS=',' read -ra items <<< "$list" for item in "${items[@]}"; do item="$(normalize_log_type "$item")" if [ "$item" = "$needle" ] || [ "$item" = "all" ]; then return 0 fi done return 1 } log_type_enabled() { local type="$1" local disabled="${LOG_DISABLED_TYPES:-$DEFAULT_DISABLED_LOG_TYPES}" local enabled="${LOG_ENABLED_TYPES:-}" if log_type_list_contains "$enabled" "all" || log_type_list_contains "$enabled" "$type"; then return 0 fi if log_type_list_contains "$disabled" "all" || log_type_list_contains "$disabled" "$type"; then return 1 fi return 0 } repair_blocking_table_artifacts() { [ -d "$DB_DIR" ] || return 0 local quarantine_root="$QUARANTINE_DIR/$(date '+%Y%m%d%H%M%S')" local found=0 local candidate="" for table in \ clients \ client_staff \ devices \ device_payments \ device_status_log \ wifi_plans \ access_tokens \ payments \ sessions \ payouts \ balance_ledger \ omada_alerts \ sms_log \ password_resets \ system_config \ device_portal_settings \ device_portal_payment_options \ token_compensations \ token_deauth_events \ client_notification_preferences \ client_payment_credentials do for candidate in "$DB_DIR/$table" "$DB_DIR/$table".*; do [ -e "$candidate" ] || continue [ -d "$candidate" ] || continue mkdir -p "$quarantine_root" echo "[entrypoint] Found blocking MariaDB directory artifact: $(basename "$candidate")" mv "$candidate" "$quarantine_root/$(basename "$candidate")" found=1 done done if [ "$found" -eq 1 ]; then echo "[entrypoint] Moved blocking MariaDB artifacts to $quarantine_root" fi } dump_mariadb_diagnostics() { echo "[entrypoint][diag] MariaDB version:" mysqld --version 2>&1 || true echo "[entrypoint][diag] Data directory listing:" ls -la "$DATA_DIR" 2>&1 || true echo "[entrypoint][diag] Runtime socket directory listing:" ls -la /run/mysqld 2>&1 || true echo "[entrypoint][diag] Recent MariaDB log output:" tail -n 120 "$LOG_DIR/mariadb.log" 2>/dev/null || true } stop_mariadb_pid() { local pid="${1:-}" [ -n "$pid" ] || return 0 if kill -0 "$pid" 2>/dev/null; then echo "[entrypoint] Stopping MariaDB process $pid..." kill "$pid" 2>/dev/null || true for i in $(seq 1 15); do if ! kill -0 "$pid" 2>/dev/null; then break fi sleep 1 done if kill -0 "$pid" 2>/dev/null; then echo "[entrypoint] MariaDB process $pid did not stop; forcing termination..." kill -9 "$pid" 2>/dev/null || true fi fi rm -f /run/mysqld/mysqld.sock /run/mysqld/mysqld.pid } clone_source_data_root_if_requested() { [ -n "$SOURCE_DATA_ROOT" ] || return 0 local source_mysql="$SOURCE_DATA_ROOT/mysql" echo "[entrypoint] SOURCE_DATA_ROOT=$SOURCE_DATA_ROOT" echo "[entrypoint] Clone-on-boot requested from $source_mysql to $DATA_DIR" if [ "$source_mysql" = "$DATA_DIR" ]; then echo "[entrypoint] ERROR: SOURCE_DATA_ROOT and DATA_ROOT resolve to the same mysql directory." exit 1 fi if [ ! -d "$source_mysql/mysql" ]; then echo "[entrypoint] ERROR: source MariaDB system tables not found at $source_mysql/mysql" exit 1 fi if [ -d "$MYSQL_SYSTEM_DIR" ]; then echo "[entrypoint] Target MariaDB system tables already exist at $MYSQL_SYSTEM_DIR; skipping clone." return 0 fi if [ -e "$DATA_DIR" ]; then echo "[entrypoint] ERROR: target data dir exists but has no MariaDB system tables: $DATA_DIR" echo "[entrypoint] Use a fresh DATA_ROOT for clone-on-boot." exit 1 fi mkdir -p "$DATA_ROOT" echo "[entrypoint] Copying MariaDB files from source data root..." cp -a "$source_mysql" "$DATA_DIR" if [ -d "$SOURCE_DATA_ROOT/storage" ] && [ ! -d "$DATA_ROOT/storage" ]; then echo "[entrypoint] Copying storage assets from source data root..." cp -a "$SOURCE_DATA_ROOT/storage" "$DATA_ROOT/storage" fi touch "$INIT_FLAG" echo "[entrypoint] Clone-on-boot completed; first-run DB initialization will be skipped." } echo "===== Application Startup at $(date '+%F %T') =====" echo "[entrypoint] Starting WiFi Platform..." echo "[entrypoint] Running as: $(id)" if log_type_enabled "env"; then echo "[entrypoint] Environment variables:" printenv | sort | while IFS= read -r line; do key="${line%%=*}" case "$key" in *TOKEN*|*SECRET*|*PASS*|*PASSWORD*|*KEY*) echo "[env] $key=[redacted]" ;; *) echo "[env] $line" ;; esac done else echo "[entrypoint] Environment variable logging disabled (enable with LOG_ENABLED_TYPES=env)." fi echo "[entrypoint] DATA_ROOT=$DATA_ROOT" echo "[entrypoint] DATA_DIR=$DATA_DIR" echo "[entrypoint] DB_NAME=${DB_NAME:-wifiplatform}" # ── Ensure persistent dirs exist (writable on HF /data volume) ── clone_source_data_root_if_requested mkdir -p "$DATA_DIR" "$LOG_DIR" "$PAYMENT_STORAGE_DIR" "$QUARANTINE_DIR" repair_blocking_table_artifacts # ── First-run: initialize + load schema ── if [ ! -f "$INIT_FLAG" ]; then echo "[entrypoint] First run — initializing MariaDB data directory..." echo "[entrypoint] MariaDB binaries:" command -v mysql_install_db || true command -v mysqld || true command -v mysql || true mysqld --version || true if [ -d "$MYSQL_SYSTEM_DIR" ]; then echo "[entrypoint] Existing MariaDB system tables found at $MYSQL_SYSTEM_DIR; skipping mysql_install_db." echo "[entrypoint] This usually means a previous bootstrap partially completed before $INIT_FLAG was written." else # Initialize WITHOUT --user=mysql (we ARE the user) echo "[entrypoint] Running mysql_install_db..." mysql_install_db \ --datadir="$DATA_DIR" \ --auth-root-authentication-method=normal \ --skip-test-db > "$LOG_DIR/mysql_install_db.log" 2>&1 || { echo "[entrypoint] ERROR: mysql_install_db failed. Output:" cat "$LOG_DIR/mysql_install_db.log" 2>/dev/null || true exit 1 } echo "[entrypoint] mysql_install_db output:" cat "$LOG_DIR/mysql_install_db.log" 2>/dev/null || true fi # Start MariaDB temporarily — use mysqld directly, not mysqld_safe # mysqld_safe tries to chown things internally echo "[entrypoint] Starting temporary MariaDB for setup..." mysqld \ --datadir="$DATA_DIR" \ --socket=/run/mysqld/mysqld.sock \ --skip-networking \ --log-error="$LOG_DIR/mariadb.log" & MYSQL_PID=$! # Wait for MariaDB to be ready echo "[entrypoint] Waiting for MariaDB to start..." for i in $(seq 1 30); do if mysqladmin ping --socket=/run/mysqld/mysqld.sock --silent 2>/dev/null; then echo "[entrypoint] MariaDB is up (attempt $i)." break fi if [ "$i" -eq 30 ]; then echo "[entrypoint] ERROR: MariaDB failed to start. Log output:" cat "$LOG_DIR/mariadb.log" 2>/dev/null || true exit 1 fi sleep 1 done echo "[entrypoint] Probing MariaDB root socket access..." if ! mysql -u root --socket=/run/mysqld/mysqld.sock -e "SELECT CURRENT_USER(), USER(), @@version, @@datadir;" 2>&1; then echo "[entrypoint] ERROR: root socket login failed before bootstrap SQL." dump_mariadb_diagnostics echo "[entrypoint][diag] Restarting temporary MariaDB with --skip-grant-tables to inspect and repair auth rows..." stop_mariadb_pid "$MYSQL_PID" wait "$MYSQL_PID" 2>/dev/null || true mysqld \ --datadir="$DATA_DIR" \ --socket=/run/mysqld/mysqld.sock \ --skip-networking \ --skip-grant-tables \ --log-error="$LOG_DIR/mariadb-skip-grants.log" & MYSQL_PID=$! for i in $(seq 1 30); do if mysqladmin --socket=/run/mysqld/mysqld.sock ping --silent 2>/dev/null; then echo "[entrypoint][diag] MariaDB skip-grants instance is up (attempt $i)." break fi if [ "$i" -eq 30 ]; then echo "[entrypoint][diag] ERROR: skip-grants instance failed to start." cat "$LOG_DIR/mariadb-skip-grants.log" 2>/dev/null || true stop_mariadb_pid "$MYSQL_PID" exit 1 fi sleep 1 done echo "[entrypoint][diag] mysql.global_priv rows:" mysql --socket=/run/mysqld/mysqld.sock -N -e "SELECT Host, User, Priv FROM mysql.global_priv;" 2>&1 || true echo "[entrypoint][diag] mysql.user rows:" mysql --socket=/run/mysqld/mysqld.sock -N -e "SELECT Host, User, plugin, authentication_string FROM mysql.user;" 2>&1 || true echo "[entrypoint][repair] Resetting root@localhost to passwordless mysql_native_password for bootstrap..." mysql --socket=/run/mysqld/mysqld.sock </dev/null || true echo "[entrypoint][repair] Restarting MariaDB after root auth repair..." mysqld \ --datadir="$DATA_DIR" \ --socket=/run/mysqld/mysqld.sock \ --skip-networking \ --log-error="$LOG_DIR/mariadb.log" & MYSQL_PID=$! for i in $(seq 1 30); do if mysqladmin ping --socket=/run/mysqld/mysqld.sock --silent 2>/dev/null; then echo "[entrypoint][repair] MariaDB is back up (attempt $i)." break fi if [ "$i" -eq 30 ]; then echo "[entrypoint][repair] ERROR: MariaDB failed to restart after auth repair." dump_mariadb_diagnostics exit 1 fi sleep 1 done echo "[entrypoint][repair] Re-probing root socket access..." mysql -u root --socket=/run/mysqld/mysqld.sock -e "SELECT CURRENT_USER(), USER(), @@version, @@datadir;" 2>&1 || { echo "[entrypoint][repair] ERROR: root socket login still failed after auth repair." dump_mariadb_diagnostics exit 1 } fi echo "[entrypoint] Creating database and user..." mysql -u root --socket=/run/mysqld/mysqld.sock </dev/null || echo 0)" APP_TABLE_COUNT="${APP_TABLE_COUNT:-0}" if [ "$APP_TABLE_COUNT" -gt 0 ]; then echo "[entrypoint] Database ${DB_NAME:-wifiplatform} already has $APP_TABLE_COUNT tables; skipping schema/restore load." elif [ -n "$RESTORE_SQL_PATH" ] && [ -f "$RESTORE_SQL_PATH" ]; then echo "[entrypoint] Restoring database from $RESTORE_SQL_PATH..." mysql -u root --socket=/run/mysqld/mysqld.sock "${DB_NAME:-wifiplatform}" < "$RESTORE_SQL_PATH" else echo "[entrypoint] Loading schema..." mysql -u root --socket=/run/mysqld/mysqld.sock "${DB_NAME:-wifiplatform}" < /app/schema.sql fi # Stop the temporary instance gracefully mysqladmin -u root --socket=/run/mysqld/mysqld.sock shutdown wait "$MYSQL_PID" 2>/dev/null || true touch "$INIT_FLAG" echo "[entrypoint] Database initialized successfully." fi # ── Start MariaDB for production ── echo "[entrypoint] Starting MariaDB..." mysqld \ --datadir="$DATA_DIR" \ --socket=/run/mysqld/mysqld.sock \ --log-error="$LOG_DIR/mariadb.log" & echo "[entrypoint] Waiting for MariaDB to be ready..." for i in $(seq 1 30); do if mysqladmin ping --socket=/run/mysqld/mysqld.sock --silent 2>/dev/null; then echo "[entrypoint] MariaDB ready." break fi if [ "$i" -eq 30 ]; then echo "[entrypoint] ERROR: MariaDB failed to start." cat "$LOG_DIR/mariadb.log" 2>/dev/null || true exit 1 fi sleep 1 done # ── Start Node app (exec replaces shell → PID 1 → clean signals) ── echo "[entrypoint] Starting Node.js app..." exec node src/server.js