From 37861cf8be1a8000de09abd7584e861b326a6cfb Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Jun 2026 13:13:13 +0200 Subject: [PATCH] feat: enterprise-grade update script with rollback, logging, health checks, notifications, dry-run --- update-Nitrov3.sh | 487 ++++++++++++++++++++++++++++++---------------- 1 file changed, 324 insertions(+), 163 deletions(-) diff --git a/update-Nitrov3.sh b/update-Nitrov3.sh index e3eaa62..47edd9e 100755 --- a/update-Nitrov3.sh +++ b/update-Nitrov3.sh @@ -1,59 +1,143 @@ #!/bin/bash - -# Strict mode: exit on any error, undefined var, or pipe failure set -euo pipefail -# Load .env from the same directory as this script +# ============================================================================= +# EPIC WEB CONTROL — Enterprise Update Script +# Compatible with Laravel 13 + Nitro V3 + MariaDB +# ============================================================================= + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# --- Load .env --------------------------------------------------------------- if [ -f "$SCRIPT_DIR/.env" ]; then set -a . "$SCRIPT_DIR/.env" set +a fi -# Use CMS APP_URL as fallback for NITRO_SITE_URL, then prompt if still unset +# --- Logging ----------------------------------------------------------------- +LOG_DIR="${NITRO_LOG_DIR:-$SCRIPT_DIR/storage/logs}" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/update-$(date +%Y%m%d-%H%M%S).log" +exec > >(tee -a "$LOG_FILE") 2>&1 + +STEP_NUM=0 +STEP_TOTAL=8 +START_TIME=$(date +%s) + +step() { + STEP_NUM=$((STEP_NUM + 1)) + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + printf "║ Step %d/%d — %-49s║\n" "$STEP_NUM" "$STEP_TOTAL" "$1" + echo "╚══════════════════════════════════════════════════════════════╝" +} + +info() { echo " --> $1"; } +ok() { echo " [OK] $1"; } +warn() { echo " [WARN] $1"; } +fail() { echo " [FAIL] $1"; } +die() { echo ""; echo "=== ❌ $1 ==="; cleanup_notify_failure "$1"; exit 1; } + +# --- Notifications ----------------------------------------------------------- +NOTIFY_URL="${NITRO_NOTIFY_URL:-}" +NOTIFY_FAILED=false + +notify() { + local status="$1" + local message="$2" + [ -z "$NOTIFY_URL" ] && return 0 + local elapsed=$(( $(date +%s) - START_TIME )) + local elapsed_fmt + elapsed_fmt=$(printf '%dm %ds' $((elapsed/60)) $((elapsed%60))) + curl -s -o /dev/null -X POST "$NOTIFY_URL" \ + -H "Content-Type: application/json" \ + -d "{ + \"text\": \"[Nitro Update] $status\", + \"attachments\": [{ + \"color\": \"$([ "$status" = "SUCCESS" ] && echo \"good\" || echo \"danger\")\", + \"fields\": [ + {\"title\": \"Status\", \"value\": \"$message\", \"short\": true}, + {\"title\": \"Duration\", \"value\": \"$elapsed_fmt\", \"short\": true}, + {\"title\": \"Server\", \"value\": \"$(hostname)\", \"short\": true}, + {\"title\": \"Log\", \"value\": \"$LOG_FILE\", \"short\": false} + ] + }] + }" 2>/dev/null || true +} + +cleanup_notify_failure() { + $NOTIFY_FAILED && return + NOTIFY_FAILED=true + notify "FAILED" "$1" +} + +# --- Rollback support -------------------------------------------------------- +LAST_BACKUP="" +ROLLBACK_NEEDED=false + +rollback_db() { + [ -z "$LAST_BACKUP" ] && { warn "No backup file known — cannot rollback DB."; return 1; } + [ ! -f "$LAST_BACKUP" ] && { warn "Backup file not found: $LAST_BACKUP"; return 1; } + info "Rolling back database from: $LAST_BACKUP" + mariadb $MYSQL_CRED "$DB_NAME" < "$LAST_BACKUP" && ok "Database restored." || warn "Rollback failed." +} + +cleanup_on_exit() { + local exit_code=$? + if [ $exit_code -ne 0 ] && [ "$ROLLBACK_NEEDED" = true ]; then + echo "" + echo "=== ⚠️ Update failed — initiating rollback ===" + rollback_db + fi + [ $exit_code -ne 0 ] && cleanup_notify_failure "Script exited with code $exit_code" +} + +trap cleanup_on_exit EXIT +trap 'echo ""; die "Interrupted by user"' SIGINT SIGTERM + +# --- Pre-flight: interactive prompts (must be before unbuffer) --------------- + +# Site URL if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then NITRO_SITE_URL="$APP_URL" - echo "--> Using APP_URL from .env: $NITRO_SITE_URL" + info "Using APP_URL from .env: $NITRO_SITE_URL" fi - if [ -z "${NITRO_SITE_URL:-}" ]; then - read -r -p "Enter your site URL (e.g. https://example.com): " NITRO_SITE_URL + read -r -p " Enter your site URL (e.g. https://example.com): " NITRO_SITE_URL NITRO_SITE_URL="${NITRO_SITE_URL%/}" - if [ -z "$NITRO_SITE_URL" ]; then - echo "=== ❌ NITRO_SITE_URL is required. Set it in .env or enter it now. ===" - exit 1 - fi + [ -z "$NITRO_SITE_URL" ] && die "NITRO_SITE_URL is required" export NITRO_SITE_URL fi -# Prompt for branch if not set in .env, default to main +# Branch if [ -z "${NITRO_BRANCH:-}" ]; then - read -r -p "Enter branch (main/dev) [main]: " NITRO_BRANCH + read -r -p " Enter branch (main/dev) [main]: " NITRO_BRANCH NITRO_BRANCH="${NITRO_BRANCH:-main}" - case "$NITRO_BRANCH" in - main|dev) ;; - *) echo "=== ❌ Invalid branch '$NITRO_BRANCH'. Choose 'main' or 'dev'. ==="; exit 1 ;; - esac + case "$NITRO_BRANCH" in main|dev) ;; *) die "Invalid branch '$NITRO_BRANCH'. Use main or dev." ;; esac export NITRO_BRANCH fi -# Real-time output via pseudo-terminal (unbuffer from expect) +# Dry-run mode +DRY_RUN=false +if [ "${1:-}" = "--dry-run" ] || [ "${1:-}" = "-n" ]; then + DRY_RUN=true + echo "" + info "DRY-RUN mode: no changes will be made" +fi + +# --- Unbuffer re-exec -------------------------------------------------------- if [ -z "${_UNBUFFERED:-}" ] && command -v unbuffer &> /dev/null; then export _UNBUFFERED=1 exec unbuffer bash "$0" "$@" fi -exec 2>&1 -# Single-instance lock via flock +# --- Single-instance lock ---------------------------------------------------- LOCKFILE="/tmp/$(basename "$0").lock" exec 200>"$LOCKFILE" -flock -n 200 || { echo "=== ❌ Another instance already running, exiting ==="; exit 1; } +flock -n 200 || die "Another instance is already running" -# Trap for clean error messages -trap 'echo "=== ❌ ERROR: Update failed at line $LINENO (command: $BASH_COMMAND) ===" >&2; exit 1' ERR - -# --- CONFIGURATION (from env vars, with defaults) --- +# --- Configuration ----------------------------------------------------------- DB_NAME="${NITRO_DB_NAME:-habbo}" DB_HOST="${NITRO_DB_HOST:-127.0.0.1}" DB_PORT="${NITRO_DB_PORT:-3306}" @@ -68,15 +152,18 @@ BACKUP_DIR="${NITRO_BACKUP_DIR:-$EMULATOR_DIR/Database Updates/backups}" NITRO_CLIENT="${NITRO_CLIENT_SRC:-/var/www/Nitro-V3}" NITRO_RENDERER="${NITRO_RENDERER_SRC:-/var/www/Nitro_Render_V3}" NITRO_BRANCH="${NITRO_BRANCH:-main}" +MIN_DISK_GB="${NITRO_MIN_DISK_GB:-5}" +HEALTH_RETRIES="${NITRO_HEALTH_RETRIES:-12}" +HEALTH_INTERVAL="${NITRO_HEALTH_INTERVAL:-5}" -# Derive ws/wss protocol and domain from site URL +# Derive ws/wss protocol and domain case "$NITRO_SITE_URL" in https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;; http://*) NITRO_WS_PROTO="ws://" ; NITRO_DOMAIN="${NITRO_SITE_URL#http://}" ;; *) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="$NITRO_SITE_URL" ;; esac -# Critical URLs (override via NITRO_* env vars, auto-derived otherwise) +# Critical URLs (override via NITRO_* env vars) NITRO_IMAGE_LIBRARY_URL="${NITRO_IMAGE_LIBRARY_URL:-$NITRO_SITE_URL/gamedata/c_images/}" NITRO_HOF_FURNITURE_URL="${NITRO_HOF_FURNITURE_URL:-$NITRO_SITE_URL/gamedata/icons}" NITRO_API_URL="${NITRO_API_URL:-$NITRO_SITE_URL}" @@ -86,13 +173,22 @@ NITRO_GAMEDATA_URL="${NITRO_GAMEDATA_URL:-$NITRO_SITE_URL/gamedata}" NITRO_ASSET_URL="${NITRO_ASSET_URL:-$NITRO_SITE_URL/gamedata/bundled}" NITRO_FURNI_ASSET_ICON_URL="${NITRO_FURNI_ASSET_ICON_URL:-$NITRO_SITE_URL/gamedata/icons/%libname%%param%_icon.png}" -# Build MySQL/MariaDB credentials argument (password via MYSQL_PWD — not visible in ps) +# MySQL credentials MYSQL_CRED="-h $DB_HOST -P $DB_PORT -u $DB_USER --ssl-verify-server-cert=OFF" -if [ -n "$DB_PASS" ]; then - export MYSQL_PWD="$DB_PASS" -fi +[ -n "$DB_PASS" ] && export MYSQL_PWD="$DB_PASS" -# Verify all required directories exist before starting +# ============================================================================= +# PRE-FLIGHT CHECKS +# ============================================================================= +step "Pre-flight Checks" + +info "Checking required commands..." +for cmd in git mariadb mariadb-dump mvn node python3 yarn; do + command -v "$cmd" &>/dev/null || die "Required command not found: $cmd" +done +ok "All required commands available" + +info "Checking directories..." REQUIRED_DIRS=( "$EMULATOR_DIR/Emulator" "$NITRO_RENDERER" @@ -100,13 +196,40 @@ REQUIRED_DIRS=( "$NITRO_SRC_DIR" ) for dir in "${REQUIRED_DIRS[@]}"; do - if [ ! -d "$dir" ]; then - echo "=== ❌ Required directory not found: $dir ===" - exit 1 - fi + [ -d "$dir" ] || die "Required directory not found: $dir" + ok "Found: $dir" done -# Helper: forcefully remove node_modules (with sudo fallback) and restore owner +info "Checking disk space..." +AVAIL_KB=$(df --output=avail "$EMULATOR_DIR" 2>/dev/null | tail -1) +AVAIL_GB=$(( AVAIL_KB / 1024 / 1024 )) +[ "$AVAIL_GB" -lt "$MIN_DISK_GB" ] && die "Only ${AVAIL_GB}GB free on $EMULATOR_DIR (min ${MIN_DISK_GB}GB required)" +ok "${AVAIL_GB}GB disk space available" + +info "Checking database connection..." +mariadb $MYSQL_CRED -e "SELECT 1" "$DB_NAME" &>/dev/null || die "Cannot connect to database $DB_NAME on $DB_HOST:$DB_PORT" +ok "Database connection OK" + +if [ "$DRY_RUN" = true ]; then + echo "" + warn "DRY-RUN — skipping actual update" + echo " Site: $NITRO_SITE_URL" + echo " Branch: $NITRO_BRANCH" + echo " Emulator: $EMULATOR_DIR/Emulator" + echo " Nitro-V3: $NITRO_CLIENT" + echo " Renderer: $NITRO_RENDERER" + echo "" + info "Dry-run complete. Pass --dry-run or -n to preview." + exit 0 +fi + +echo "" +info "Site: $NITRO_SITE_URL | Branch: $NITRO_BRANCH" +info "Log: $LOG_FILE" + +# ============================================================================= +# HELPERS +# ============================================================================= clean_node_modules() { rm -rf node_modules 2>/dev/null || sudo rm -rf node_modules 2>/dev/null || true if [ "$(stat -c '%U' .)" != "$(whoami)" ] && command -v sudo &> /dev/null; then @@ -114,7 +237,6 @@ clean_node_modules() { fi } -# Helper: git stash → checkout → pull → return 0 if new commits, 1 otherwise git_update() { local repo="$1" local branch="$2" @@ -124,55 +246,60 @@ git_update() { old_head=$(git rev-parse HEAD) git checkout "$branch" git pull - if [ "$(git rev-parse HEAD)" != "$old_head" ]; then - return 0 - fi - return 1 + [ "$(git rev-parse HEAD)" != "$old_head" ] } -echo "=== Starting EPIC WEB CONTROL Update ===" -echo "--> Site: $NITRO_SITE_URL | Branch: $NITRO_BRANCH" -echo "" - HAD_UPDATES=false NITRO_BUILT=false -# ---------------------------------------- -# 1. Update and Build Emulator -# ---------------------------------------- -echo "--> Updating Emulator..." +# ============================================================================= +# 1. UPDATE & BUILD EMULATOR +# ============================================================================= +step "Update & Build Emulator" if git_update "$EMULATOR_DIR/Emulator" "$NITRO_BRANCH"; then - echo "--> New commits detected, building emulator..." + info "New commits detected" HAD_UPDATES=true + ROLLBACK_NEEDED=true - # Automatic Safe Database Backup - echo "--> Creating automatic database backup before update..." + # Database backup + info "Creating database backup..." mkdir -p "$BACKUP_DIR" - mariadb-dump $MYSQL_CRED --force --skip-lock-tables "$DB_NAME" > "$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).sql" || echo "--> Backup has missing tables (not critical) — update continues" - - # Automatic SQL Import (exclude backup dir) - echo "--> Checking for new SQL files..." - if [ -d "$SQL_DIR" ]; then - find "$SQL_DIR" -name "*.sql" -mmin -10 -not -path "$BACKUP_DIR/*" -print0 2>/dev/null | while IFS= read -r -d '' sql_file; do - echo "--> Importing new SQL file: $(basename "$sql_file")" - mariadb $MYSQL_CRED --force "$DB_NAME" < "$sql_file" || echo "--> Error importing $(basename "$sql_file"), moving to next..." - done + BACKUP_FILE="$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).sql" + if mariadb-dump $MYSQL_CRED --force --skip-lock-tables --routines --events --triggers "$DB_NAME" > "$BACKUP_FILE"; then + LAST_BACKUP="$BACKUP_FILE" + BK_SIZE=$(stat -c%s "$BACKUP_FILE" 2>/dev/null || echo 0) + [ "$BK_SIZE" -lt 1024 ] && { rm -f "$BACKUP_FILE"; die "Backup is too small (${BK_SIZE}B) — possible corruption, aborting"; } + ok "Backup saved: $(numfmt --to=iec "$BK_SIZE") — $BACKUP_FILE" else - echo "--> SQL directory not found, skipping SQL import." + die "Database backup failed" fi + # SQL imports + info "Checking for new SQL files..." + if [ -d "$SQL_DIR" ]; then + SQL_COUNT=0 + while IFS= read -r -d '' sql_file; do + info "Importing: $(basename "$sql_file")" + mariadb $MYSQL_CRED --force "$DB_NAME" < "$sql_file" || warn "Import failed for $(basename "$sql_file")" + SQL_COUNT=$((SQL_COUNT + 1)) + done < <(find "$SQL_DIR" -name '*.sql' -mmin -10 -not -path "$BACKUP_DIR/*" -print0 2>/dev/null) + [ "$SQL_COUNT" -gt 0 ] && ok "$SQL_COUNT SQL file(s) imported" || info "No new SQL files found" + else + info "SQL directory not found, skipping" + fi + + # Maven build + info "Building emulator (mvn package)..." cd "$EMULATOR_DIR/Emulator" - mvn package + mvn package -q || die "Maven build failed" + ok "Build complete" JAR_FILE=$(find target -maxdepth 1 -name 'Habbo-*-jar-with-dependencies.jar' -printf '%T@ %p\n' 2>/dev/null | sort -rn | sed -n '1s/^[0-9.]* //p' | xargs basename) - if [ -z "$JAR_FILE" ]; then - echo "=== ❌ No jar file found with pattern: target/Habbo-*-jar-with-dependencies.jar ===" - exit 1 - fi + [ -z "$JAR_FILE" ] && die "No jar file found in target/" + ok "Jar: $JAR_FILE" - echo "--> Found jar file: $JAR_FILE" - echo "--> Updating emulator launch file..." + info "Updating emulator launch script..." cd target/ cat << EOF > emulator #!/bin/sh @@ -183,88 +310,86 @@ mv /var/log/emu/emulator.log /var/log/emu/\$file_name java -Dfile.encoding=UTF8 -Xmx2G -jar $JAR_FILE EOF chmod +x emulator + ok "Launch script updated" + ROLLBACK_NEEDED=false else - echo "--> Emulator already up to date, skipping build." + info "Already up to date, skipping" fi - -# ---------------------------------------- -# 2. Update Nitro_Render_V3 -# ---------------------------------------- -echo "--> Updating Nitro_Render_V3..." +# ============================================================================= +# 2. UPDATE NITRO_RENDER_V3 +# ============================================================================= +step "Update Nitro_Render_V3" if git_update "$NITRO_RENDERER" "$NITRO_BRANCH"; then - echo "--> New commits detected, running yarn install for Nitro_Render_V3..." + info "New commits detected" HAD_UPDATES=true - yarn install --frozen-lockfile || { echo "--> Retrying with clean node_modules..."; clean_node_modules && yarn install; } + yarn install --frozen-lockfile || { info "Retrying with clean node_modules..."; clean_node_modules && yarn install; } || die "yarn install failed for Nitro_Render_V3" + ok "Dependencies installed" else - echo "--> Nitro_Render_V3 already up to date, skipping." + info "Already up to date, skipping" fi - -# ---------------------------------------- -# 3. Update and Build Nitro-V3 -# ---------------------------------------- -echo "--> Updating Nitro-V3..." +# ============================================================================= +# 3. UPDATE & BUILD NITRO-V3 +# ============================================================================= +step "Update & Build Nitro-V3" if git_update "$NITRO_CLIENT" "$NITRO_BRANCH"; then - echo "--> New commits detected, building Nitro-V3..." + info "New commits detected" HAD_UPDATES=true NITRO_BUILT=true - yarn install --frozen-lockfile || { echo "--> Retrying with clean node_modules..."; clean_node_modules && yarn install; } - yarn build + yarn install --frozen-lockfile || { info "Retrying with clean node_modules..."; clean_node_modules && yarn install; } || die "yarn install failed for Nitro-V3" + ok "Dependencies installed" + info "Building Nitro-V3 (yarn build)..." + yarn build || die "yarn build failed" + ok "Build complete" else - echo "--> Nitro-V3 already up to date, skipping build." + info "Already up to date, skipping build" fi -echo "--> Ensuring custom-themes/index.json exists..." +# custom-themes/index.json mkdir -p "$NITRO_CLIENT/dist/custom-themes" if [ ! -f "$NITRO_CLIENT/dist/custom-themes/index.json" ]; then echo '{"themes":[]}' > "$NITRO_CLIENT/dist/custom-themes/index.json" - echo "--> [OK] Created custom-themes/index.json" + ok "Created custom-themes/index.json" else - echo "--> [OK] custom-themes/index.json already exists" + ok "custom-themes/index.json exists" fi +# ============================================================================= +# 4. SYNC CONFIGS +# ============================================================================= +step "Sync Configurations" -# ---------------------------------------- -# 4. Sync Configs & UITexts (.example logic) -# ---------------------------------------- -echo "--> Synchronizing configurations..." mkdir -p "$GAMEDATA_CONF_DIR" - MERGE_SCRIPT="$(dirname "$0")/scripts/merge-config.cjs" NITRO_DIST_CONFIG_DIR="$NITRO_CLIENT/dist/configuration" -# Temporarily make config dirs writable if owned by www-data (restored in section 6) +# Make dirs writable for dir in "$NITRO_SRC_DIR" "$GAMEDATA_CONF_DIR"; do if [ -d "$dir" ] && [ ! -w "$dir" ] && command -v sudo &> /dev/null; then - sudo chown -R "$(whoami)":"$(whoami)" "$dir" - echo "--> [OK] Made $dir writable (will restore to www-data later)" + sudo chown -R "$(whoami)":"$(whoami)" "$dir" 2>/dev/null || true fi done +EXAMPLE_COUNT=0 for example_file in "$NITRO_SRC_DIR"/*.example; do [ -f "$example_file" ] || continue base=$(basename "$example_file") target_name="${base%.example}" - case "$target_name" in - *.*) ;; - *) target_name="$target_name.json" ;; - esac - - echo "--> Merging $base -> $target_name (live)..." - node "$MERGE_SCRIPT" "$example_file" "$NITRO_SRC_DIR/$target_name" - echo "--> Merging $base -> $target_name (gamedata)..." - node "$MERGE_SCRIPT" "$example_file" "$GAMEDATA_CONF_DIR/$target_name" + case "$target_name" in *.*) ;; *) target_name="$target_name.json" ;; esac + node "$MERGE_SCRIPT" "$example_file" "$NITRO_SRC_DIR/$target_name" 2>/dev/null + node "$MERGE_SCRIPT" "$example_file" "$GAMEDATA_CONF_DIR/$target_name" 2>/dev/null if [ -d "$NITRO_DIST_CONFIG_DIR" ]; then - cp "$NITRO_SRC_DIR/$target_name" "$NITRO_DIST_CONFIG_DIR/$target_name" - echo "--> [OK] Synced $target_name to dist/configuration/" + cp "$NITRO_SRC_DIR/$target_name" "$NITRO_DIST_CONFIG_DIR/$target_name" 2>/dev/null || true fi + EXAMPLE_COUNT=$((EXAMPLE_COUNT + 1)) done +ok "$EXAMPLE_COUNT .example file(s) processed" -# Phase 4: Restore critical icon URLs in renderer-config and ui-config -echo "--> Ensuring critical catalog icon URLs are correct..." +# Force critical URLs +info "Applying critical config URLs..." FORCE_CONFIG_SCRIPT=$(cat << 'PYEOF' import json, sys, os @@ -296,9 +421,9 @@ for path in paths: data['show.google.ads'] = False with open(path, 'w') as f: json.dump(data, f, indent=(4 if 'dist' not in path else None), separators=(',', ':') if 'dist' in path else (',', ': ')) - print(f'--> [OK] Fixed: {os.path.basename(path)}') + print(f' [OK] Fixed: {os.path.basename(path)}') -print('--> [OK] Catalog icon URLs are set correctly') +print(' [OK] All config URLs synced') PYEOF ) cd "$NITRO_CLIENT" && NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$NITRO_DIST_CONFIG_DIR" \ @@ -308,108 +433,144 @@ cd "$NITRO_CLIENT" && NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$NIT NITRO_FURNI_ASSET_ICON_URL="$NITRO_FURNI_ASSET_ICON_URL" \ python3 -c "$FORCE_CONFIG_SCRIPT" +# ============================================================================= +# 5. CLEANUP +# ============================================================================= +step "Cleanup" -# ---------------------------------------- -# 5. Automated Cleanup (Logs & Backups) -# ---------------------------------------- -echo "--> Starting automated cleanup..." - -echo "--> Removing emulator logs older than 14 days..." +info "Removing logs older than 14 days..." find "$EMULATOR_DIR" -name "*.log" -mtime +14 -exec rm -f {} \; 2>/dev/null || true -echo "--> Managing update backups (keeping max 5)..." +info "Managing backups (keeping max 5)..." if [ -d "$BACKUP_DIR" ]; then find "$BACKUP_DIR" -maxdepth 1 -name '*.sql' -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +6 | sed 's/^[0-9.]* //' | xargs -r rm -f || true fi if [ "$HAD_UPDATES" = true ]; then - echo "--> Cleaning Yarn cache..." + info "Cleaning Yarn cache..." yarn cache clean 2>/dev/null || true - echo "--> Cleaning up old emulator .jar files (keeping the latest)..." + info "Removing old .jar files..." find "$EMULATOR_DIR/Emulator/target" -maxdepth 1 -name 'Habbo-*-jar-with-dependencies.jar' -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +2 | sed 's/^[0-9.]* //' | xargs -r rm -f || true fi +ok "Cleanup complete" +# ============================================================================= +# 6. PERMISSIONS +# ============================================================================= +step "Set Permissions" -# ---------------------------------------- -# 6. Fix Permissions (www-data) -# ---------------------------------------- if command -v sudo &> /dev/null; then - echo "--> Setting permissions to www-data:www-data..." for dir in "$NITRO_CLIENT" "$NITRO_RENDERER" "$EMULATOR_DIR" "$GAMEDATA_CONF_DIR"; do if [ -d "$dir" ]; then - sudo chown -R www-data:www-data "$dir" + sudo chown -R www-data:www-data "$dir" 2>/dev/null || warn "Could not chown $dir" fi done - echo "--> Permissions successfully set to www-data:www-data" + ok "Permissions set to www-data:www-data" else - echo "--> Sudo not available, skipping chown." + warn "sudo not available, skipping chown" fi +# ============================================================================= +# 7. RESTART SERVICES +# ============================================================================= +step "Restart Services" -# ---------------------------------------- -# 7. Restart the Service (only if updates were applied) -# ---------------------------------------- +RESTART_OK=false if [ "$HAD_UPDATES" = true ]; then if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then - echo "--> Restarting $EMULATOR_SERVICE service..." if command -v sudo &> /dev/null; then - sudo systemctl restart "$EMULATOR_SERVICE" - sudo systemctl status "$EMULATOR_SERVICE" --no-pager -n 5 - else - echo "--> Sudo not available. Restart the service manually: sudo systemctl restart $EMULATOR_SERVICE" + info "Restarting $EMULATOR_SERVICE..." + sudo systemctl restart "$EMULATOR_SERVICE" || die "Failed to restart $EMULATOR_SERVICE" + RESTART_OK=true fi elif command -v pm2 &> /dev/null; then - echo "--> Restarting PM2 processes..." - pm2 restart all + info "Restarting PM2 processes..." + pm2 restart all || warn "PM2 restart had issues" + RESTART_OK=true else - echo "--> Warning: Neither systemd nor PM2 found. Restart the emulator manually." + warn "No systemd or PM2 found — restart manually" fi + + if [ "$RESTART_OK" = true ]; then + ok "Services restarted" + fi +else + info "No updates applied, no restart needed" fi - -# ---------------------------------------- -# 8. Extra Validation — 100% check -# ---------------------------------------- -echo "--> Running extra validation..." +# ============================================================================= +# 8. HEALTH CHECK & VALIDATION +# ============================================================================= +step "Health Check & Validation" ERRORS=0 +# Build output check if [ "$NITRO_BUILT" = true ]; then if [ -d "$NITRO_CLIENT/dist/assets" ]; then - echo "--> [OK] Nitro-V3 build assets found" + ASSET_COUNT=$(find "$NITRO_CLIENT/dist/assets" -type f 2>/dev/null | wc -l) + ok "Nitro-V3 built: $ASSET_COUNT assets in dist/assets" else - echo "--> [FAIL] Nitro-V3 build assets missing!" + fail "Nitro-V3 build assets missing!" ERRORS=$((ERRORS + 1)) fi else - echo "--> [SKIP] Nitro-V3 was not rebuilt, skipping build check" + info "Skipping build check (no new commits)" fi +# Health check with retries for emulator +if [ "$HAD_UPDATES" = true ] && systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then + info "Waiting for $EMULATOR_SERVICE to become active..." + HEALTHY=false + for i in $(seq 1 "$HEALTH_RETRIES"); do + sleep "$HEALTH_INTERVAL" + STATUS=$(systemctl is-active "$EMULATOR_SERVICE" 2>/dev/null || echo "unknown") + if [ "$STATUS" = "active" ]; then + HEALTHY=true + break + fi + info " Attempt $i/$HEALTH_RETRIES — status: $STATUS" + done + if [ "$HEALTHY" = true ]; then + ok "$EMULATOR_SERVICE is active ($(( i * HEALTH_INTERVAL ))s to start)" + else + fail "$EMULATOR_SERVICE did not become active after ${HEALTH_RETRIES} retries" + ERRORS=$((ERRORS + 1)) + fi +fi + +# Permission audit for dir in "$NITRO_CLIENT" "$NITRO_RENDERER" "$EMULATOR_DIR" "$GAMEDATA_CONF_DIR"; do if [ -d "$dir" ]; then OWNER=$(stat -c '%U:%G' "$dir" 2>/dev/null || echo "unknown") if [ "$OWNER" = "www-data:www-data" ] || [ "$(id -u)" -ne 0 ]; then - echo "--> [OK] $dir ($OWNER)" + ok "$(basename "$dir") ($OWNER)" else - echo "--> [WARN] $dir has owner $OWNER instead of www-data:www-data" + warn "$(basename "$dir") owner is $OWNER (should be www-data:www-data)" sudo chown -R www-data:www-data "$dir" 2>/dev/null || true - echo " Restored to www-data:www-data" fi fi done -if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then - SERVICE_STATUS=$(systemctl is-active "$EMULATOR_SERVICE" 2>/dev/null || echo "unknown") - if [ "$SERVICE_STATUS" = "active" ]; then - echo "--> [OK] $EMULATOR_SERVICE service is $SERVICE_STATUS" - else - echo "--> [WARN] $EMULATOR_SERVICE service is $SERVICE_STATUS (not active, check logs)" - ERRORS=$((ERRORS + 1)) - fi -fi +# ============================================================================= +# SUMMARY +# ============================================================================= +ELAPSED=$(( $(date +%s) - START_TIME )) +ELAPSED_FMT=$(printf '%dm %ds' $((ELAPSED/60)) $((ELAPSED%60))) +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" if [ "$ERRORS" -eq 0 ]; then - echo "=== ✅ Update 100% successfully completed! ===" + echo "║ ✅ UPDATE SUCCESSFULLY COMPLETED ║" else - echo "=== ⚠️ Update completed with $ERRORS warning(s) — check output above ===" + echo "║ ⚠️ UPDATE COMPLETED WITH $ERRORS WARNING(S) ║" fi +echo "╠══════════════════════════════════════════════════════════════╣" +printf "║ Duration: %-46s║\n" "$ELAPSED_FMT" +printf "║ Updates: %-46s║\n" "$([ "$HAD_UPDATES" = true ] && echo "Yes" || echo "No")" +printf "║ Nitro build: %-46s║\n" "$([ "$NITRO_BUILT" = true ] && echo "Yes" || echo "No")" +printf "║ Log file: %-46s║\n" "$LOG_FILE" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +notify "SUCCESS" "Completed in $ELAPSED_FMT (${ERRORS} warnings)" \ No newline at end of file