| #!/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}" |
|
|
| |
| clone_source_data_root_if_requested |
| mkdir -p "$DATA_DIR" "$LOG_DIR" "$PAYMENT_STORAGE_DIR" "$QUARANTINE_DIR" |
| repair_blocking_table_artifacts |
|
|
| |
| 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 |
| |
| 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 |
|
|
| |
| |
| 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=$! |
|
|
| |
| 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 <<SQL |
| UPDATE mysql.global_priv |
| SET Priv = JSON_SET( |
| JSON_REMOVE(COALESCE(Priv, JSON_OBJECT()), '$.auth_or'), |
| '$.plugin', |
| 'mysql_native_password', |
| '$.authentication_string', |
| '' |
| ) |
| WHERE Host = 'localhost' AND User = 'root'; |
| FLUSH PRIVILEGES; |
| SQL |
|
|
| stop_mariadb_pid "$MYSQL_PID" |
| wait "$MYSQL_PID" 2>/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 <<SQL |
| CREATE DATABASE IF NOT EXISTS \`${DB_NAME:-wifiplatform}\` |
| CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; |
| CREATE USER IF NOT EXISTS '${DB_USER:-wifiuser}'@'localhost' |
| IDENTIFIED BY '${DB_PASS:-wifipass}'; |
| GRANT ALL PRIVILEGES ON \`${DB_NAME:-wifiplatform}\`.* |
| TO '${DB_USER:-wifiuser}'@'localhost'; |
| FLUSH PRIVILEGES; |
| SQL |
|
|
| APP_TABLE_COUNT="$(mysql -u root --socket=/run/mysqld/mysqld.sock -N -B -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = '${DB_NAME:-wifiplatform}' AND TABLE_TYPE = 'BASE TABLE';" 2>/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 |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| echo "[entrypoint] Starting Node.js app..." |
| exec node src/server.js |
|
|