feat: enterprise-grade update script with rollback, logging, health checks, notifications, dry-run

This commit is contained in:
root
2026-06-24 13:13:13 +02:00
parent 2aaff12c60
commit 37861cf8be
+322 -161
View File
@@ -1,59 +1,143 @@
#!/bin/bash #!/bin/bash
# Strict mode: exit on any error, undefined var, or pipe failure
set -euo pipefail 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)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# --- Load .env ---------------------------------------------------------------
if [ -f "$SCRIPT_DIR/.env" ]; then if [ -f "$SCRIPT_DIR/.env" ]; then
set -a set -a
. "$SCRIPT_DIR/.env" . "$SCRIPT_DIR/.env"
set +a set +a
fi 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 if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then
NITRO_SITE_URL="$APP_URL" NITRO_SITE_URL="$APP_URL"
echo "--> Using APP_URL from .env: $NITRO_SITE_URL" info "Using APP_URL from .env: $NITRO_SITE_URL"
fi fi
if [ -z "${NITRO_SITE_URL:-}" ]; then 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%/}" NITRO_SITE_URL="${NITRO_SITE_URL%/}"
if [ -z "$NITRO_SITE_URL" ]; then [ -z "$NITRO_SITE_URL" ] && die "NITRO_SITE_URL is required"
echo "=== ❌ NITRO_SITE_URL is required. Set it in .env or enter it now. ==="
exit 1
fi
export NITRO_SITE_URL export NITRO_SITE_URL
fi fi
# Prompt for branch if not set in .env, default to main # Branch
if [ -z "${NITRO_BRANCH:-}" ]; then 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}" NITRO_BRANCH="${NITRO_BRANCH:-main}"
case "$NITRO_BRANCH" in case "$NITRO_BRANCH" in main|dev) ;; *) die "Invalid branch '$NITRO_BRANCH'. Use main or dev." ;; esac
main|dev) ;;
*) echo "=== ❌ Invalid branch '$NITRO_BRANCH'. Choose 'main' or 'dev'. ==="; exit 1 ;;
esac
export NITRO_BRANCH export NITRO_BRANCH
fi 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 if [ -z "${_UNBUFFERED:-}" ] && command -v unbuffer &> /dev/null; then
export _UNBUFFERED=1 export _UNBUFFERED=1
exec unbuffer bash "$0" "$@" exec unbuffer bash "$0" "$@"
fi fi
exec 2>&1
# Single-instance lock via flock # --- Single-instance lock ----------------------------------------------------
LOCKFILE="/tmp/$(basename "$0").lock" LOCKFILE="/tmp/$(basename "$0").lock"
exec 200>"$LOCKFILE" 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 # --- Configuration -----------------------------------------------------------
trap 'echo "=== ❌ ERROR: Update failed at line $LINENO (command: $BASH_COMMAND) ===" >&2; exit 1' ERR
# --- CONFIGURATION (from env vars, with defaults) ---
DB_NAME="${NITRO_DB_NAME:-habbo}" DB_NAME="${NITRO_DB_NAME:-habbo}"
DB_HOST="${NITRO_DB_HOST:-127.0.0.1}" DB_HOST="${NITRO_DB_HOST:-127.0.0.1}"
DB_PORT="${NITRO_DB_PORT:-3306}" 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_CLIENT="${NITRO_CLIENT_SRC:-/var/www/Nitro-V3}"
NITRO_RENDERER="${NITRO_RENDERER_SRC:-/var/www/Nitro_Render_V3}" NITRO_RENDERER="${NITRO_RENDERER_SRC:-/var/www/Nitro_Render_V3}"
NITRO_BRANCH="${NITRO_BRANCH:-main}" 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 case "$NITRO_SITE_URL" in
https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;; https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;;
http://*) NITRO_WS_PROTO="ws://" ; NITRO_DOMAIN="${NITRO_SITE_URL#http://}" ;; http://*) NITRO_WS_PROTO="ws://" ; NITRO_DOMAIN="${NITRO_SITE_URL#http://}" ;;
*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="$NITRO_SITE_URL" ;; *) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="$NITRO_SITE_URL" ;;
esac 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_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_HOF_FURNITURE_URL="${NITRO_HOF_FURNITURE_URL:-$NITRO_SITE_URL/gamedata/icons}"
NITRO_API_URL="${NITRO_API_URL:-$NITRO_SITE_URL}" 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_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}" 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" MYSQL_CRED="-h $DB_HOST -P $DB_PORT -u $DB_USER --ssl-verify-server-cert=OFF"
if [ -n "$DB_PASS" ]; then [ -n "$DB_PASS" ] && export MYSQL_PWD="$DB_PASS"
export MYSQL_PWD="$DB_PASS"
fi
# 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=( REQUIRED_DIRS=(
"$EMULATOR_DIR/Emulator" "$EMULATOR_DIR/Emulator"
"$NITRO_RENDERER" "$NITRO_RENDERER"
@@ -100,13 +196,40 @@ REQUIRED_DIRS=(
"$NITRO_SRC_DIR" "$NITRO_SRC_DIR"
) )
for dir in "${REQUIRED_DIRS[@]}"; do for dir in "${REQUIRED_DIRS[@]}"; do
if [ ! -d "$dir" ]; then [ -d "$dir" ] || die "Required directory not found: $dir"
echo "=== ❌ Required directory not found: $dir ===" ok "Found: $dir"
exit 1
fi
done 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() { clean_node_modules() {
rm -rf node_modules 2>/dev/null || sudo rm -rf node_modules 2>/dev/null || true 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 if [ "$(stat -c '%U' .)" != "$(whoami)" ] && command -v sudo &> /dev/null; then
@@ -114,7 +237,6 @@ clean_node_modules() {
fi fi
} }
# Helper: git stash → checkout → pull → return 0 if new commits, 1 otherwise
git_update() { git_update() {
local repo="$1" local repo="$1"
local branch="$2" local branch="$2"
@@ -124,55 +246,60 @@ git_update() {
old_head=$(git rev-parse HEAD) old_head=$(git rev-parse HEAD)
git checkout "$branch" git checkout "$branch"
git pull git pull
if [ "$(git rev-parse HEAD)" != "$old_head" ]; then [ "$(git rev-parse HEAD)" != "$old_head" ]
return 0
fi
return 1
} }
echo "=== Starting EPIC WEB CONTROL Update ==="
echo "--> Site: $NITRO_SITE_URL | Branch: $NITRO_BRANCH"
echo ""
HAD_UPDATES=false HAD_UPDATES=false
NITRO_BUILT=false NITRO_BUILT=false
# ---------------------------------------- # =============================================================================
# 1. Update and Build Emulator # 1. UPDATE & BUILD EMULATOR
# ---------------------------------------- # =============================================================================
echo "--> Updating Emulator..." step "Update & Build Emulator"
if git_update "$EMULATOR_DIR/Emulator" "$NITRO_BRANCH"; then if git_update "$EMULATOR_DIR/Emulator" "$NITRO_BRANCH"; then
echo "--> New commits detected, building emulator..." info "New commits detected"
HAD_UPDATES=true HAD_UPDATES=true
ROLLBACK_NEEDED=true
# Automatic Safe Database Backup # Database backup
echo "--> Creating automatic database backup before update..." info "Creating database backup..."
mkdir -p "$BACKUP_DIR" 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" 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
# Automatic SQL Import (exclude backup dir) LAST_BACKUP="$BACKUP_FILE"
echo "--> Checking for new SQL files..." BK_SIZE=$(stat -c%s "$BACKUP_FILE" 2>/dev/null || echo 0)
if [ -d "$SQL_DIR" ]; then [ "$BK_SIZE" -lt 1024 ] && { rm -f "$BACKUP_FILE"; die "Backup is too small (${BK_SIZE}B) — possible corruption, aborting"; }
find "$SQL_DIR" -name "*.sql" -mmin -10 -not -path "$BACKUP_DIR/*" -print0 2>/dev/null | while IFS= read -r -d '' sql_file; do ok "Backup saved: $(numfmt --to=iec "$BK_SIZE")$BACKUP_FILE"
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
else else
echo "--> SQL directory not found, skipping SQL import." die "Database backup failed"
fi 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" 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) 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 [ -z "$JAR_FILE" ] && die "No jar file found in target/"
echo "=== ❌ No jar file found with pattern: target/Habbo-*-jar-with-dependencies.jar ===" ok "Jar: $JAR_FILE"
exit 1
fi
echo "--> Found jar file: $JAR_FILE" info "Updating emulator launch script..."
echo "--> Updating emulator launch file..."
cd target/ cd target/
cat << EOF > emulator cat << EOF > emulator
#!/bin/sh #!/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 java -Dfile.encoding=UTF8 -Xmx2G -jar $JAR_FILE
EOF EOF
chmod +x emulator chmod +x emulator
ok "Launch script updated"
ROLLBACK_NEEDED=false
else else
echo "--> Emulator already up to date, skipping build." info "Already up to date, skipping"
fi fi
# =============================================================================
# ---------------------------------------- # 2. UPDATE NITRO_RENDER_V3
# 2. Update Nitro_Render_V3 # =============================================================================
# ---------------------------------------- step "Update Nitro_Render_V3"
echo "--> Updating Nitro_Render_V3..."
if git_update "$NITRO_RENDERER" "$NITRO_BRANCH"; then 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 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 else
echo "--> Nitro_Render_V3 already up to date, skipping." info "Already up to date, skipping"
fi fi
# =============================================================================
# ---------------------------------------- # 3. UPDATE & BUILD NITRO-V3
# 3. Update and Build Nitro-V3 # =============================================================================
# ---------------------------------------- step "Update & Build Nitro-V3"
echo "--> Updating Nitro-V3..."
if git_update "$NITRO_CLIENT" "$NITRO_BRANCH"; then if git_update "$NITRO_CLIENT" "$NITRO_BRANCH"; then
echo "--> New commits detected, building Nitro-V3..." info "New commits detected"
HAD_UPDATES=true HAD_UPDATES=true
NITRO_BUILT=true NITRO_BUILT=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-V3"
yarn build ok "Dependencies installed"
info "Building Nitro-V3 (yarn build)..."
yarn build || die "yarn build failed"
ok "Build complete"
else else
echo "--> Nitro-V3 already up to date, skipping build." info "Already up to date, skipping build"
fi fi
echo "--> Ensuring custom-themes/index.json exists..." # custom-themes/index.json
mkdir -p "$NITRO_CLIENT/dist/custom-themes" mkdir -p "$NITRO_CLIENT/dist/custom-themes"
if [ ! -f "$NITRO_CLIENT/dist/custom-themes/index.json" ]; then if [ ! -f "$NITRO_CLIENT/dist/custom-themes/index.json" ]; then
echo '{"themes":[]}' > "$NITRO_CLIENT/dist/custom-themes/index.json" echo '{"themes":[]}' > "$NITRO_CLIENT/dist/custom-themes/index.json"
echo "--> [OK] Created custom-themes/index.json" ok "Created custom-themes/index.json"
else else
echo "--> [OK] custom-themes/index.json already exists" ok "custom-themes/index.json exists"
fi fi
# =============================================================================
# 4. SYNC CONFIGS
# =============================================================================
step "Sync Configurations"
# ----------------------------------------
# 4. Sync Configs & UITexts (.example logic)
# ----------------------------------------
echo "--> Synchronizing configurations..."
mkdir -p "$GAMEDATA_CONF_DIR" mkdir -p "$GAMEDATA_CONF_DIR"
MERGE_SCRIPT="$(dirname "$0")/scripts/merge-config.cjs" MERGE_SCRIPT="$(dirname "$0")/scripts/merge-config.cjs"
NITRO_DIST_CONFIG_DIR="$NITRO_CLIENT/dist/configuration" 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 for dir in "$NITRO_SRC_DIR" "$GAMEDATA_CONF_DIR"; do
if [ -d "$dir" ] && [ ! -w "$dir" ] && command -v sudo &> /dev/null; then if [ -d "$dir" ] && [ ! -w "$dir" ] && command -v sudo &> /dev/null; then
sudo chown -R "$(whoami)":"$(whoami)" "$dir" sudo chown -R "$(whoami)":"$(whoami)" "$dir" 2>/dev/null || true
echo "--> [OK] Made $dir writable (will restore to www-data later)"
fi fi
done done
EXAMPLE_COUNT=0
for example_file in "$NITRO_SRC_DIR"/*.example; do for example_file in "$NITRO_SRC_DIR"/*.example; do
[ -f "$example_file" ] || continue [ -f "$example_file" ] || continue
base=$(basename "$example_file") base=$(basename "$example_file")
target_name="${base%.example}" target_name="${base%.example}"
case "$target_name" in case "$target_name" in *.*) ;; *) target_name="$target_name.json" ;; esac
*.*) ;; node "$MERGE_SCRIPT" "$example_file" "$NITRO_SRC_DIR/$target_name" 2>/dev/null
*) target_name="$target_name.json" ;; node "$MERGE_SCRIPT" "$example_file" "$GAMEDATA_CONF_DIR/$target_name" 2>/dev/null
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"
if [ -d "$NITRO_DIST_CONFIG_DIR" ]; then if [ -d "$NITRO_DIST_CONFIG_DIR" ]; then
cp "$NITRO_SRC_DIR/$target_name" "$NITRO_DIST_CONFIG_DIR/$target_name" cp "$NITRO_SRC_DIR/$target_name" "$NITRO_DIST_CONFIG_DIR/$target_name" 2>/dev/null || true
echo "--> [OK] Synced $target_name to dist/configuration/"
fi fi
EXAMPLE_COUNT=$((EXAMPLE_COUNT + 1))
done done
ok "$EXAMPLE_COUNT .example file(s) processed"
# Phase 4: Restore critical icon URLs in renderer-config and ui-config # Force critical URLs
echo "--> Ensuring critical catalog icon URLs are correct..." info "Applying critical config URLs..."
FORCE_CONFIG_SCRIPT=$(cat << 'PYEOF' FORCE_CONFIG_SCRIPT=$(cat << 'PYEOF'
import json, sys, os import json, sys, os
@@ -296,9 +421,9 @@ for path in paths:
data['show.google.ads'] = False data['show.google.ads'] = False
with open(path, 'w') as f: 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 (',', ': ')) 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 PYEOF
) )
cd "$NITRO_CLIENT" && NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$NITRO_DIST_CONFIG_DIR" \ 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" \ NITRO_FURNI_ASSET_ICON_URL="$NITRO_FURNI_ASSET_ICON_URL" \
python3 -c "$FORCE_CONFIG_SCRIPT" python3 -c "$FORCE_CONFIG_SCRIPT"
# =============================================================================
# 5. CLEANUP
# =============================================================================
step "Cleanup"
# ---------------------------------------- info "Removing logs older than 14 days..."
# 5. Automated Cleanup (Logs & Backups)
# ----------------------------------------
echo "--> Starting automated cleanup..."
echo "--> Removing emulator logs older than 14 days..."
find "$EMULATOR_DIR" -name "*.log" -mtime +14 -exec rm -f {} \; 2>/dev/null || true 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 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 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 fi
if [ "$HAD_UPDATES" = true ]; then if [ "$HAD_UPDATES" = true ]; then
echo "--> Cleaning Yarn cache..." info "Cleaning Yarn cache..."
yarn cache clean 2>/dev/null || true 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 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 fi
ok "Cleanup complete"
# =============================================================================
# 6. PERMISSIONS
# =============================================================================
step "Set Permissions"
# ----------------------------------------
# 6. Fix Permissions (www-data)
# ----------------------------------------
if command -v sudo &> /dev/null; then 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 for dir in "$NITRO_CLIENT" "$NITRO_RENDERER" "$EMULATOR_DIR" "$GAMEDATA_CONF_DIR"; do
if [ -d "$dir" ]; then 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 fi
done done
echo "--> Permissions successfully set to www-data:www-data" ok "Permissions set to www-data:www-data"
else else
echo "--> Sudo not available, skipping chown." warn "sudo not available, skipping chown"
fi fi
# =============================================================================
# 7. RESTART SERVICES
# =============================================================================
step "Restart Services"
# ---------------------------------------- RESTART_OK=false
# 7. Restart the Service (only if updates were applied)
# ----------------------------------------
if [ "$HAD_UPDATES" = true ]; then if [ "$HAD_UPDATES" = true ]; then
if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then
echo "--> Restarting $EMULATOR_SERVICE service..."
if command -v sudo &> /dev/null; then if command -v sudo &> /dev/null; then
sudo systemctl restart "$EMULATOR_SERVICE" info "Restarting $EMULATOR_SERVICE..."
sudo systemctl status "$EMULATOR_SERVICE" --no-pager -n 5 sudo systemctl restart "$EMULATOR_SERVICE" || die "Failed to restart $EMULATOR_SERVICE"
else RESTART_OK=true
echo "--> Sudo not available. Restart the service manually: sudo systemctl restart $EMULATOR_SERVICE"
fi fi
elif command -v pm2 &> /dev/null; then elif command -v pm2 &> /dev/null; then
echo "--> Restarting PM2 processes..." info "Restarting PM2 processes..."
pm2 restart all pm2 restart all || warn "PM2 restart had issues"
RESTART_OK=true
else else
echo "--> Warning: Neither systemd nor PM2 found. Restart the emulator manually." warn "No systemd or PM2 found — restart manually"
fi
fi fi
if [ "$RESTART_OK" = true ]; then
ok "Services restarted"
fi
else
info "No updates applied, no restart needed"
fi
# ---------------------------------------- # =============================================================================
# 8. Extra Validation — 100% check # 8. HEALTH CHECK & VALIDATION
# ---------------------------------------- # =============================================================================
echo "--> Running extra validation..." step "Health Check & Validation"
ERRORS=0 ERRORS=0
# Build output check
if [ "$NITRO_BUILT" = true ]; then if [ "$NITRO_BUILT" = true ]; then
if [ -d "$NITRO_CLIENT/dist/assets" ]; 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 else
echo "--> [FAIL] Nitro-V3 build assets missing!" fail "Nitro-V3 build assets missing!"
ERRORS=$((ERRORS + 1)) ERRORS=$((ERRORS + 1))
fi fi
else else
echo "--> [SKIP] Nitro-V3 was not rebuilt, skipping build check" info "Skipping build check (no new commits)"
fi 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 for dir in "$NITRO_CLIENT" "$NITRO_RENDERER" "$EMULATOR_DIR" "$GAMEDATA_CONF_DIR"; do
if [ -d "$dir" ]; then if [ -d "$dir" ]; then
OWNER=$(stat -c '%U:%G' "$dir" 2>/dev/null || echo "unknown") OWNER=$(stat -c '%U:%G' "$dir" 2>/dev/null || echo "unknown")
if [ "$OWNER" = "www-data:www-data" ] || [ "$(id -u)" -ne 0 ]; then if [ "$OWNER" = "www-data:www-data" ] || [ "$(id -u)" -ne 0 ]; then
echo "--> [OK] $dir ($OWNER)" ok "$(basename "$dir") ($OWNER)"
else 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 sudo chown -R www-data:www-data "$dir" 2>/dev/null || true
echo " Restored to www-data:www-data"
fi fi
fi fi
done done
if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then # =============================================================================
SERVICE_STATUS=$(systemctl is-active "$EMULATOR_SERVICE" 2>/dev/null || echo "unknown") # SUMMARY
if [ "$SERVICE_STATUS" = "active" ]; then # =============================================================================
echo "--> [OK] $EMULATOR_SERVICE service is $SERVICE_STATUS" ELAPSED=$(( $(date +%s) - START_TIME ))
else ELAPSED_FMT=$(printf '%dm %ds' $((ELAPSED/60)) $((ELAPSED%60)))
echo "--> [WARN] $EMULATOR_SERVICE service is $SERVICE_STATUS (not active, check logs)"
ERRORS=$((ERRORS + 1))
fi
fi
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
if [ "$ERRORS" -eq 0 ]; then if [ "$ERRORS" -eq 0 ]; then
echo "=== ✅ Update 100% successfully completed! ===" echo "║ ✅ UPDATE SUCCESSFULLY COMPLETED ║"
else else
echo "=== ⚠️ Update completed with $ERRORS warning(s) — check output above ===" echo "║ ⚠️ UPDATE COMPLETED WITH $ERRORS WARNING(S) ║"
fi 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)"