3281 lines
148 KiB
Bash
Executable File
3281 lines
148 KiB
Bash
Executable File
#!/bin/bash
|
|
set -Eeo pipefail
|
|
|
|
# ╔══════════════════════════════════════════════════════════════════════════════╗
|
|
# ║ SMaRT — Emulator / Octane / Octane-Renderer ║
|
|
# ║ Updater & Manager by Remco ║
|
|
# ╚══════════════════════════════════════════════════════════════════════════════╝
|
|
|
|
# =============================================================================
|
|
# CONSTANTS
|
|
# =============================================================================
|
|
readonly SCRIPT_VERSION="8.0.2"
|
|
readonly SCRIPT_BUILD="20260803"
|
|
readonly SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly START_TIME=$(date +%s)
|
|
readonly LOCK_FILE="/tmp/${SCRIPT_NAME%.sh}.lock"
|
|
|
|
# =============================================================================
|
|
# COLORS
|
|
# =============================================================================
|
|
if [ -t 1 ] && command -v tput &>/dev/null && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then
|
|
C_RESET="\033[0m" C_BOLD="\033[1m" C_DIM="\033[2m"
|
|
C_RED="\033[1;31m" C_GREEN="\033[1;32m" C_YELLOW="\033[1;33m"
|
|
C_BLUE="\033[1;34m" C_MAGENTA="\033[1;35m" C_CYAN="\033[1;36m"
|
|
C_WHITE="\033[1;37m" C_GOLD="\033[38;5;220m"
|
|
C_BG_RED="\033[41m" C_BG_GREEN="\033[42m"
|
|
C_BG_BLUE="\033[44m" C_BG_MAGENTA="\033[45m" C_BG_CYAN="\033[46m"
|
|
else
|
|
C_RESET="" C_BOLD="" C_DIM=""
|
|
C_RED="" C_GREEN="" C_YELLOW="" C_BLUE="" C_MAGENTA="" C_CYAN="" C_WHITE="" C_GOLD=""
|
|
C_BG_RED="" C_BG_GREEN="" C_BG_BLUE="" C_BG_MAGENTA="" C_BG_CYAN=""
|
|
fi
|
|
|
|
# =============================================================================
|
|
# EMOJIS
|
|
# =============================================================================
|
|
if locale charmap 2>/dev/null | grep -qi utf; then
|
|
E_OK="✔" E_FAIL="✘" E_WARN="⚠" E_ARROW="➜" E_DOT="●"
|
|
E_GEAR="⚙" E_ROCKET="🚀" E_SHIELD="🛡" E_DB="🗄" E_GIT="🔀"
|
|
E_WRENCH="🔧" E_CHART="📊" E_BOLT="⚡" E_BROOM="🧹" E_EYE="👁"
|
|
E_LOCK="🔒" E_HEART="♥" E_CLOUD="☁" E_FIRE="🔥" E_GLOBE="🌐"
|
|
E_CONSOLE="🖥" E_CLOCK="⏱" E_DOCKER="🐳" E_CHECK="🔍"
|
|
else
|
|
E_OK="[OK]" E_FAIL="[!]" E_WARN="[!]" E_ARROW="->" E_DOT="*"
|
|
E_GEAR="[G]" E_ROCKET="[R]" E_SHIELD="[S]" E_DB="[DB]" E_GIT="[G]"
|
|
E_WRENCH="[W]" E_CHART="[C]" E_BOLT="[!]" E_BROOM="[C]" E_EYE="[E]"
|
|
E_LOCK="[L]" E_HEART="[H]" E_CLOUD="[C]" E_FIRE="[F]" E_GLOBE="[W]"
|
|
E_CONSOLE="[M]" E_CLOCK="[T]" E_DOCKER="[D]" E_CHECK="[C]"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# STATE
|
|
# =============================================================================
|
|
SELECTIVE_UPDATE=""
|
|
DRY_RUN=false
|
|
HAD_UPDATES=false
|
|
NITRO_BUILT=false
|
|
ERRORS=0
|
|
WARNINGS=0
|
|
UPDATED_REPOS=()
|
|
LAST_BACKUP=""
|
|
ROLLBACK_NEEDED=false
|
|
BINARY_BACKUP_DIR=""
|
|
NOTIFY_FAILED=false
|
|
CONFIG_BACKUP_DIR=""
|
|
MAVEN_OPTS="${MAVEN_OPTS:--Xmx2G}"
|
|
|
|
# =============================================================================
|
|
# LOAD .ENV
|
|
# =============================================================================
|
|
if [ -f "$SCRIPT_DIR/.env" ]; then set -a; . "$SCRIPT_DIR/.env"; set +a; fi
|
|
|
|
if [ -z "${NITRO_DB_NAME:-}" ] && [ -n "${DATABASE_URL:-}" ]; then
|
|
eval "$(
|
|
python3 -c "
|
|
import os, urllib.parse
|
|
url = os.environ['DATABASE_URL']
|
|
parsed = urllib.parse.urlparse(url)
|
|
auth = parsed.netloc.rsplit('@', 1)[0] if '@' in parsed.netloc else ''
|
|
user, pw = (auth.split(':', 1) + ['', ''])[:2]
|
|
host = parsed.hostname or '127.0.0.1'
|
|
port = parsed.port or 3306
|
|
name = parsed.path.lstrip('/').split('?')[0]
|
|
print(f'DB_USER={user!r}')
|
|
print(f'DB_PASS={urllib.parse.unquote(pw)!r}')
|
|
print(f'DB_HOST={host!r}')
|
|
print(f'DB_PORT={port!r}')
|
|
print(f'DB_NAME={name!r}')
|
|
" 2>/dev/null || true
|
|
)"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# CONFIG
|
|
# =============================================================================
|
|
LOG_DIR="${NITRO_LOG_DIR:-$SCRIPT_DIR/storage/logs}"
|
|
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
|
LOG_FILE="$LOG_DIR/update-$(date +%Y%m%d-%H%M%S).log"
|
|
NOTIFY_URL="${NITRO_NOTIFY_URL:-}"
|
|
DB_NAME="${NITRO_DB_NAME:-${DB_NAME:-habbo}}"
|
|
DB_HOST="${NITRO_DB_HOST:-${DB_HOST:-127.0.0.1}}"
|
|
DB_PORT="${NITRO_DB_PORT:-${DB_PORT:-3306}}"
|
|
DB_USER="${NITRO_DB_USER:-${DB_USER:-root}}"
|
|
DB_PASS="${NITRO_DB_PASS:-${DB_PASS:-}}"
|
|
EMULATOR_SERVICE="${NITRO_EMULATOR_SERVICE:-emulator}"
|
|
EMULATOR_DIR="${NITRO_EMULATOR_PATH:-/var/www/emulator}"
|
|
# Polaris-Emulator: the git repository root is $EMULATOR_DIR itself, while the
|
|
# Maven project (pom.xml + target/) lives in the $EMULATOR_DIR/Emulator submodule.
|
|
EMULATOR_REPO="${NITRO_EMULATOR_REPO:-$EMULATOR_DIR}"
|
|
EMULATOR_MODULE="${NITRO_EMULATOR_MODULE:-$EMULATOR_DIR/Emulator}"
|
|
SQL_DIR="${NITRO_SQL_DIR:-$EMULATOR_DIR/Database/Database Updates}"
|
|
GAMEDATA_CONF_DIR="${NITRO_GAMEDATA_DIR:-/var/www/Gamedata/config}"
|
|
NITRO_SRC_DIR="${NITRO_CLIENT_DIR:-/var/www/Octane/public/configuration}"
|
|
BACKUP_DIR="${NITRO_BACKUP_DIR:-$EMULATOR_DIR/Database/Database Updates/backups}"
|
|
NITRO_CLIENT="${NITRO_CLIENT_SRC:-/var/www/Octane}"
|
|
NITRO_RENDERER="${NITRO_RENDERER_SRC:-/var/www/Octane-Renderer}"
|
|
NITRO_BRANCH="${NITRO_BRANCH:-}"
|
|
# Dedicated per-repo yarn cache folders. The renderer + client installs run in
|
|
# parallel and yarn 1.22 has no cache locking, so a single shared cache gets
|
|
# corrupted and node_modules is left incomplete (vite/pixi.js missing) while
|
|
# yarn still exits 0. Isolating each repo's cache via --cache-folder prevents
|
|
# that race entirely.
|
|
# GitHub repository URLs for auto-setup (used when repos are missing)
|
|
GIT_REPO_CLIENT="${NITRO_GIT_CLIENT:-https://github.com/duckietm/Octane.git}"
|
|
GIT_REPO_RENDERER="${NITRO_GIT_RENDERER:-https://github.com/duckietm/Octane-Renderer.git}"
|
|
YARN_CACHE_DIR="${NITRO_YARN_CACHE_DIR:-/var/www/.yarn-cache}"
|
|
mkdir -p "$YARN_CACHE_DIR" 2>/dev/null || true
|
|
MIN_DISK_GB="${NITRO_MIN_DISK_GB:-5}"
|
|
HEALTH_RETRIES="${NITRO_HEALTH_RETRIES:-12}"
|
|
HEALTH_INTERVAL="${NITRO_HEALTH_INTERVAL:-5}"
|
|
NITRO_SSL_VERIFY="${NITRO_SSL_VERIFY:-OFF}"
|
|
CONFIG_BACKUP_DIR="${SCRIPT_DIR}/storage/config-backups"
|
|
mkdir -p "$CONFIG_BACKUP_DIR" 2>/dev/null || true
|
|
mkdir -p "$BACKUP_DIR" 2>/dev/null || true
|
|
NITRO_LANG="${NITRO_LANG:-en}"
|
|
# Nginx site config(s) that serve /gamedata/. After every build/rebuild the
|
|
# update script (re)adds an exact-match redirect so /gamedata/ points at
|
|
# /gamedata/config/ instead of the raw directory listing.
|
|
NGINX_SITE_CONFS=(
|
|
"${NITRO_NGINX_SITE_CONF:-/etc/nginx/sites-enabled/cms.conf}"
|
|
"${NITRO_NGINX_SITE_CONF_AVAILABLE:-/etc/nginx/sites-available/cms.conf}"
|
|
)
|
|
|
|
MYSQL_CRED="-h $DB_HOST -P $DB_PORT -u $DB_USER --ssl-verify-server-cert=${NITRO_SSL_VERIFY}"
|
|
[ -n "$DB_PASS" ] && MYSQL_CRED="$MYSQL_CRED --password=$DB_PASS"
|
|
|
|
if command -v mariadb &>/dev/null; then
|
|
MYSQL_CLIENT="mariadb"; MYSQL_DUMP="mariadb-dump"
|
|
elif command -v mysql &>/dev/null; then
|
|
MYSQL_CLIENT="mysql"; MYSQL_DUMP="mysqldump"
|
|
else
|
|
MYSQL_CLIENT="mariadb"; MYSQL_DUMP="mariadb-dump"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# LANGUAGE / INTERNATIONALIZATION
|
|
# =============================================================================
|
|
declare -A _T
|
|
|
|
_t() {
|
|
local key="$1"; shift
|
|
local text="${_T[$key]:-$key}"
|
|
for arg in "$@"; do
|
|
text="${text/\%s/$arg}"
|
|
done
|
|
echo "$text"
|
|
}
|
|
|
|
load_language() {
|
|
local lang="${NITRO_LANG:-en}"
|
|
case "${lang,,}" in
|
|
nl) _T=(
|
|
# ── MENU ──────────────────────────────────────────────
|
|
["UPDATE"]="UPDATE"
|
|
["TOOLS"]="TOOLS"
|
|
["ADVANCED"]="ADVANCED"
|
|
["Quick Update — current branch"]="Snelle Update — huidige branch"
|
|
["Full Update (choose branch)"]="Volledige Update (kies branch)"
|
|
["Emulator Only"]="Alleen Emulator"
|
|
["Octane Client Only"]="Alleen Octane Client"
|
|
["Renderer Only"]="Alleen Renderer"
|
|
["Configs Only"]="Alleen Configuraties"
|
|
["Dev Mode (skip builds, only configs)"]="Dev Modus (skip builds, alleen configs)"
|
|
["Switch Branch"]="Wissel Branch"
|
|
["Database"]="Database"
|
|
["Backup / Restore"]="Backup / Herstel"
|
|
["Services"]="Services"
|
|
["Status"]="Status"
|
|
["Health Check"]="Gezondheidscheck"
|
|
["Clean"]="Opschonen"
|
|
["Logs"]="Logboeken"
|
|
["Config"]="Configuratie"
|
|
["Git Log"]="Git Logboek"
|
|
["Compare Branches"]="Vergelijk Branches"
|
|
["Cron Scheduler"]="Cron Planner"
|
|
["View Git Stashes"]="Bekijk Git Stashes"
|
|
["Exit"]="Afsluiten"
|
|
["Flyaway Repair"]="Vliegende Reparatie"
|
|
["Auto-setup missing repos"]="Automatisch ontbrekende repos installeren"
|
|
["Cleanup old Nitro dirs"]="Oude Nitro mappen opruimen"
|
|
["Rebuild Frontend"]="Frontend Herbouwen"
|
|
["Rebuild Emulator"]="Emulator Herbouwen"
|
|
["Select [0-23]"]="Selecteer [0-23]"
|
|
["Press Enter to continue..."]="Druk op Enter om door te gaan..."
|
|
["Goodbye!"]="Tot ziens!"
|
|
# ── HELP ──────────────────────────────────────────────
|
|
["SMaRT — Emulator / Octane / Octane-Renderer Updater & Manager"]="SMaRT — Emulator / Octane / Octane-Renderer Updater & Beheerder"
|
|
["USAGE:"]="GEBRUIK:"
|
|
["COMMANDS:"]="COMMANDO'S:"
|
|
["Open menu (default)"]="Open menu (standaard)"
|
|
["Run update"]="Voer update uit"
|
|
["Create database backup"]="Maak databasebackup"
|
|
["Restore from backup"]="Herstel vanuit backup"
|
|
["List backups"]="Toon backups"
|
|
["System dashboard"]="Systeemdashboard"
|
|
["Health check"]="Gezondheidscheck"
|
|
["Service manager"]="Servicebeheerder"
|
|
["Database tools"]="Database tools"
|
|
["Show config"]="Toon configuratie"
|
|
["View logs"]="Bekijk logboeken"
|
|
["Cache cleanup"]="Cache opschonen"
|
|
["Git history"]="Git geschiedenis"
|
|
["Branch diff"]="Branch vergelijking"
|
|
["Cron setup"]="Cron instellen"
|
|
["View git stashes"]="Bekijk git stashes"
|
|
["Branch (auto-detects)"]="Branch (auto-detectie)"
|
|
["Site URL"]="Site URL"
|
|
["emulator|client|renderer|configs|dev"]="emulator|client|renderer|configs|dev"
|
|
["Preview only (no changes)"]="Alleen voorbeeld (geen wijzigingen)"
|
|
["Show this help"]="Toon deze hulp"
|
|
["Show version"]="Toon versie"
|
|
["OPTIONS:"]="OPTIES:"
|
|
["Unknown:"]="Onbekend:"
|
|
# ── STEP HEADERS ──────────────────────────────────────
|
|
["Update & Build Emulator"]="Update & Bouw Emulator"
|
|
["Update Octane-Renderer"]="Update Octane-Renderer"
|
|
["Update & Build Octane"]="Update & Bouw Octane"
|
|
["Sync Configurations"]="Synchroniseer Configuraties"
|
|
["Cleanup"]="Opschonen"
|
|
["Set Permissions"]="Stel Permissies in"
|
|
["Restart Services"]="Herstart Services"
|
|
["System Diagnostics"]="Systeemdiagnose"
|
|
["Database Backup"]="Databasebackup"
|
|
["Database Restore"]="Database Herstel"
|
|
["Backup Inventory"]="Backup Inventaris"
|
|
["System Status"]="Systeemstatus"
|
|
["Health Check"]="Gezondheidscheck"
|
|
["Service Manager"]="Servicebeheer"
|
|
["Database Tools"]="Database Tools"
|
|
["Configuration"]="Configuratie"
|
|
["Log Viewer"]="Logboekweergave"
|
|
["Cache Cleanup"]="Cache Opschonen"
|
|
["Git Commit History"]="Git Commit Geschiedenis"
|
|
["Branch Comparison"]="Branch Vergelijking"
|
|
["Cron Scheduler"]="Cron Planner"
|
|
["Git Stashes"]="Git Stashes"
|
|
# ── PROMPTS / CONFIRM ─────────────────────────────────
|
|
["Start update?"]="Update starten?"
|
|
["Continue?"]="Doorgaan?"
|
|
["Restore %s? OVERWRITES database!"]="Herstel %s? OVERSCHRIJFT database!"
|
|
["Select [1-%s]"]="Selecteer [1-%s]"
|
|
["Branch [1-%s]"]="Branch [1-%s]"
|
|
["Name:"]="Naam:"
|
|
["Custom"]="Aangepast"
|
|
["Select:"]="Selecteer:"
|
|
["SQL:"]="SQL:"
|
|
["Minute [0-59]:"]="Minuut [0-59]:"
|
|
["Hour [0-23]:"]="Uur [0-23]:"
|
|
["Branch A:"]="Branch A:"
|
|
["Branch B:"]="Branch B:"
|
|
["(current)"]="(huidig)"
|
|
# ── INFO / OK / WARN / FAIL ──────────────────────────
|
|
["All commands available"]="Alle commando's beschikbaar"
|
|
["All directories exist"]="Alle mappen bestaan"
|
|
["Database connected"]="Database verbonden"
|
|
["Emulator: already up to date"]="Emulator: al up-to-date"
|
|
["Renderer: already up to date"]="Renderer: al up-to-date"
|
|
["Octane: already up to date"]="Octane: al up-to-date"
|
|
["Emulator JAR backed up"]="Emulator JAR geback-upt"
|
|
["Octane dist backed up"]="Octane dist geback-upt"
|
|
["Emulator JAR restored"]="Emulator JAR hersteld"
|
|
["Octane dist restored"]="Octane dist hersteld"
|
|
["Renderer dependencies installed"]="Renderer afhankelijkheden geïnstalleerd"
|
|
["No repo updates; skipping parallel builds"]="Geen repo-updates; parallel bouwen overgeslagen"
|
|
["Dev mode: skipping builds, syncing configs only"]="Dev modus: builds overgeslagen, alleen configs synchroniseren"
|
|
["Config back-up: %s"]="Config-backup: %s"
|
|
["%s config file(s) synced"]="%s configuratiebestand(en) gesynchroniseerd"
|
|
["All config files valid"]="Alle configuratiebestanden geldig"
|
|
["%s config(s) invalid"]="%s configuratie(s) ongeldig"
|
|
["All config URLs synced"]="Alle config-URL's gesynchroniseerd"
|
|
["Redis cache flushed"]="Redis cache geleegd"
|
|
["Permissions set"]="Permissies ingesteld"
|
|
["Jar: %s"]="Jar: %s"
|
|
["Launch script updated"]="Startscript bijgewerkt"
|
|
["%s restarted"]="%s herstart"
|
|
["Emulator health check passed (service up + port listening)"]="Emulator gezondheidscheck geslaagd (service actief + poort luistert)"
|
|
["Emulator not healthy after %s retries"]="Emulator niet gezond na %s pogingen"
|
|
["Nginx reloaded"]="Nginx herladen"
|
|
["Redis flushed"]="Redis geleegd"
|
|
["Cleanup done (%s logs removed)"]="Opschonen voltooid (%s logs verwijderd)"
|
|
["Old config backups pruned"]="Oude config-backups opgeruimd"
|
|
["Dry-run: would update repos, run DB backups, build, sync configs, cleanup, restart services"]="Proefrun: zou repos updaten, DB-backups maken, bouwen, configs synchroniseren, opschonen, services herstarten"
|
|
["Backup: %s — %s"]="Backup: %s — %s"
|
|
["Restored"]="Hersteld"
|
|
["Cancelled"]="Geannuleerd"
|
|
["Started"]="Gestart"
|
|
["Stopped"]="Gestopt"
|
|
["Restarted"]="Herstart"
|
|
["Yarn cleaned"]="Yarn opgeschoond"
|
|
["Next.js cache cleared"]="Next.js cache gewist"
|
|
["Logs cleaned"]="Logs opgeschoond"
|
|
["Full cleanup"]="Volledig opgeschoond"
|
|
["Docker pruned"]="Docker opgeruimd"
|
|
["Optimized"]="Geoptimaliseerd"
|
|
["Old Maven artifacts cleaned"]="Oude Maven artefacten opgeruimd"
|
|
["Scheduled daily 03:00"]="Ingesteld dagelijks 03:00"
|
|
["Scheduled daily 04:00"]="Ingesteld dagelijks 04:00"
|
|
["Scheduled weekly"]="Ingesteld wekelijks"
|
|
["Scheduled %s"]="Ingesteld %s"
|
|
["Removed"]="Verwijderd"
|
|
["Branch: %s"]="Branch: %s"
|
|
["No backup dir"]="Geen backup-map"
|
|
["No backups"]="Geen backups"
|
|
["No logs"]="Geen logboeken"
|
|
["No log dir"]="Geen logboek-map"
|
|
["No stashes found"]="Geen stashes gevonden"
|
|
["Not a git repo"]="Geen git-repository"
|
|
["Invalid"]="Ongeldig"
|
|
["Invalid selection"]="Ongeldige selectie"
|
|
["%s updates, %s warnings"]="%s updates, %s waarschuwingen"
|
|
["Update had errors"]="Update had fouten"
|
|
["Dev mode had errors"]="Dev modus had fouten"
|
|
["Invalid, using: %s"]="Ongeldig, gebruik: %s"
|
|
["Backup: %s — %s"]="Backup: %s — %s"
|
|
["Script:"]="Script:"
|
|
["Bash syntax"]="Bash syntax"
|
|
["Configs:"]="Configuraties:"
|
|
["Config dir"]="Config map"
|
|
["All JSON valid"]="Alle JSON geldig"
|
|
["JSON errors:"]="JSON fouten:"
|
|
["Gamedata config"]="Gamedata config"
|
|
["Backup dir"]="Backup map"
|
|
["Network:"]="Netwerk:"
|
|
["Nginx config"]="Nginx configuratie"
|
|
["Redis PING"]="Redis PING"
|
|
["Site reachable"]="Site bereikbaar"
|
|
["Emulator port open"]="Emulator poort open"
|
|
["Emulator port closed"]="Emulator poort gesloten"
|
|
["Databases:"]="Databases:"
|
|
["MySQL connect"]="MySQL verbinding"
|
|
["Disk:"]="Schijf:"
|
|
["Memory:"]="Geheugen:"
|
|
["Load:"]="Belasting:"
|
|
["Commands:"]="Commando's:"
|
|
["Bashisms"]="Bashisms"
|
|
["Tables:"]="Tabellen:"
|
|
# ── DIE MESSAGES ──────────────────────────────────────
|
|
["Another instance is already running (lock: %s)"]="Er draait al een andere instantie (lock: %s)"
|
|
["Required commands missing"]="Vereiste commando's ontbreken"
|
|
["Missing: %s"]="Ontbreekt: %s"
|
|
["Missing dir: %s"]="Map ontbreekt: %s"
|
|
["Only %sGB free (min %sGB)"]="Slechts %sGB vrij (min %sGB)"
|
|
["Low disk: %sGB free"]="Weinig schijfruimte: %sGB vrij"
|
|
["%sGB disk available"]="%sGB schijf beschikbaar"
|
|
["DB connection failed"]="Databaseverbinding mislukt"
|
|
["Maven build failed (see: %s)"]="Maven build mislukt (zie: %s)"
|
|
["No jar found"]="Geen jar gevonden"
|
|
["yarn install failed"]="yarn install mislukt"
|
|
["yarn build failed (see: %s)"]="yarn build mislukt (zie: %s)"
|
|
["Build failure in: %s"]="Buildfout in: %s"
|
|
["Backup failed"]="Backup mislukt"
|
|
["Restore failed"]="Herstel mislukt"
|
|
# ── SECTIONS ──────────────────────────────────────────
|
|
[" UPDATE "]=" UPDATE "
|
|
[" TOOLS "]=" TOOLS "
|
|
[" ADVANCED "]=" GEAVANCEERD "
|
|
# ── SUBMENUS ──────────────────────────────────────────
|
|
["Start Emulator"]="Start Emulator"
|
|
["Stop Emulator"]="Stop Emulator"
|
|
["Restart Emulator"]="Herstart Emulator"
|
|
["Start Nginx"]="Start Nginx"
|
|
["Restart Nginx"]="Herstart Nginx"
|
|
["Status All"]="Status Alles"
|
|
["Emulator Logs"]="Emulator Logs"
|
|
["Emulator Journal"]="Emulator Journal"
|
|
["Return"]="Terug"
|
|
["DB Info"]="DB Info"
|
|
["List Tables"]="Toon Tabellen"
|
|
["Table Sizes"]="Tabelgroottes"
|
|
["Optimize"]="Optimaliseer"
|
|
["Custom Query"]="Eigen Query"
|
|
["Active Users"]="Actieve Gebruikers"
|
|
["Processlist"]="Proceslijst"
|
|
["Yarn"]="Yarn"
|
|
["Next.js"]="Next.js"
|
|
["Redis"]="Redis"
|
|
["Everything"]="Alles"
|
|
["Docker"]="Docker"
|
|
["Maven"]="Maven"
|
|
["Daily 03:00"]="Dagelijks 03:00"
|
|
["Daily 04:00"]="Dagelijks 04:00"
|
|
["Weekly Sun 03:00"]="Wekelijks Zo 03:00"
|
|
["Custom time"]="Eigen tijd"
|
|
["Remove"]="Verwijderen"
|
|
["Last 20 commits on %s (ahead: %s):"]="Laatste 20 commits op %s (vooruit: %s):"
|
|
["%s is %s ahead, %s behind %s"]="%s is %s voor, %s achter op %s"
|
|
["Current:"]="Huidig:"
|
|
["none"]="geen"
|
|
# ── BOX LABELS ────────────────────────────────────────
|
|
["SERVER"]="SERVER"
|
|
["RESOURCES"]="BRONNEN"
|
|
["SERVICES"]="SERVICES"
|
|
["GIT REPOS"]="GIT REPOS"
|
|
["SCORE"]="SCORE"
|
|
["DB INFO"]="DB INFO"
|
|
["CONFIGURATION"]="CONFIGURATIE"
|
|
["Host:"]="Host:"
|
|
["OS:"]="Besturingssysteem:"
|
|
["Uptime:"]="Uptime:"
|
|
["CPU:"]="CPU:"
|
|
["Memory:"]="Geheugen:"
|
|
["Disk:"]="Schijf:"
|
|
["Database:"]="Database:"
|
|
["Passed:"]="Geslaagd:"
|
|
["Failed:"]="Mislukt:"
|
|
["Warnings:"]="Waarschuwingen:"
|
|
["Name:"]="Naam:"
|
|
["Size:"]="Grootte:"
|
|
["Tables:"]="Tabellen:"
|
|
["Site:"]="Site:"
|
|
["Branch:"]="Branch:"
|
|
["Emulator:"]="Emulator:"
|
|
["Octane:"]="Octane:"
|
|
["Renderer:"]="Renderer:"
|
|
["Service:"]="Service:"
|
|
["Socket:"]="Socket:"
|
|
["API:"]="API:"
|
|
["Gamedata:"]="Gamedata:"
|
|
["Min Disk:"]="Min Schijf:"
|
|
# ── SPINNER ───────────────────────────────────────────
|
|
["Building emulator (parallel)..."]="Emulator aan het bouwen (parallel)..."
|
|
["Installing deps..."]="Afhankelijkheden installeren..."
|
|
["Building frontend..."]="Frontend aan het bouwen..."
|
|
["Backing up..."]="Backup aan het maken..."
|
|
["Restoring..."]="Aan het herstellen..."
|
|
["Optimizing..."]="Aan het optimaliseren..."
|
|
# ── NOTIFY ──────────────────────────────────────────
|
|
["SUCCESS"]="GELUKT"
|
|
["FAILED"]="MISLUKT"
|
|
["Completed in %s (%s warnings)"]="Voltooid in %s (%s waarschuwingen)"
|
|
# ── OTHER ───────────────────────────────────────────
|
|
["Total: %s backups — %s"]="Totaal: %s backups — %s"
|
|
["FILENAME"]="BESTANDSNAAM"
|
|
["SIZE"]="GROOTTE"
|
|
["All config files valid"]="Alle configuratiebestanden geldig"
|
|
# ── SUMMARY ──────────────────────────────────────────
|
|
["UPDATE COMPLETED SUCCESSFULLY"]="UPDATE SUCCESVOL VOLTOOID"
|
|
["COMPLETED WITH %s ERROR(S)"]="VOLTOOID MET %s FOUT(EN)"
|
|
["Duration:"]="Duur:"
|
|
["Updates:"]="Updates:"
|
|
["Octane build:"]="Octane build:"
|
|
["Warnings:"]="Waarschuwingen:"
|
|
["Repos:"]="Repos:"
|
|
["Yes"]="Ja"
|
|
["No"]="Nee"
|
|
) ;;
|
|
fr) _T=(
|
|
# ── MENU ──────────────────────────────────────────────
|
|
["UPDATE"]="MISE À JOUR"
|
|
["TOOLS"]="OUTILS"
|
|
["ADVANCED"]="AVANCÉ"
|
|
["Quick Update — current branch"]="Mise à jour rapide — branche actuelle"
|
|
["Full Update (choose branch)"]="Mise à jour complète (choisir branche)"
|
|
["Emulator Only"]="Émulateur uniquement"
|
|
["Octane Client Only"]="Client Octane uniquement"
|
|
["Renderer Only"]="Rendu uniquement"
|
|
["Configs Only"]="Configs uniquement"
|
|
["Dev Mode (skip builds, only configs)"]="Mode Dev (sans builds, configs seulement)"
|
|
["Switch Branch"]="Changer de branche"
|
|
["Database"]="Base de données"
|
|
["Backup / Restore"]="Sauvegarde / Restauration"
|
|
["Services"]="Services"
|
|
["Status"]="Statut"
|
|
["Health Check"]="Vérification de santé"
|
|
["Clean"]="Nettoyer"
|
|
["Logs"]="Journaux"
|
|
["Config"]="Configuration"
|
|
["Git Log"]="Journal Git"
|
|
["Compare Branches"]="Comparer les branches"
|
|
["Cron Scheduler"]="Planificateur Cron"
|
|
["View Git Stashes"]="Voir les stashs Git"
|
|
["Exit"]="Quitter"
|
|
["Flyaway Repair"]="Réparation Flyaway"
|
|
["Auto-setup missing repos"]="Installation auto des repos manquants"
|
|
["Cleanup old Nitro dirs"]="Nettoyer les anciens répertoires Nitro"
|
|
["Rebuild Frontend"]="Reconstruire le Frontend"
|
|
["Rebuild Emulator"]="Reconstruire l'Emulateur"
|
|
["Select [0-23]"]="Sélectionner [0-23]"
|
|
["Press Enter to continue..."]="Appuyez sur Entrée pour continuer..."
|
|
["Goodbye!"]="Au revoir !"
|
|
["Start update?"]="Lancer la mise à jour ?"
|
|
["Continue?"]="Continuer ?"
|
|
# ── HELP ──────────────────────────────────────────────
|
|
["USAGE:"]="UTILISATION :"
|
|
["COMMANDS:"]="COMMANDES :"
|
|
["OPTIONS:"]="OPTIONS :"
|
|
# ── STEP HEADERS ──────────────────────────────────────
|
|
["Update & Build Emulator"]="Mise à jour & Construction Émulateur"
|
|
["Update Octane-Renderer"]="Mise à jour Octane-Renderer"
|
|
["Update & Build Octane"]="Mise à jour & Construction Octane"
|
|
["Sync Configurations"]="Synchroniser les configurations"
|
|
["Cleanup"]="Nettoyage"
|
|
["Set Permissions"]="Définir les permissions"
|
|
["Restart Services"]="Redémarrer les services"
|
|
["System Diagnostics"]="Diagnostics système"
|
|
["Database Backup"]="Sauvegarde base de données"
|
|
["Database Restore"]="Restauration base de données"
|
|
["Backup Inventory"]="Inventaire des sauvegardes"
|
|
["System Status"]="Statut du système"
|
|
["Health Check"]="Vérification de santé"
|
|
["Service Manager"]="Gestionnaire de services"
|
|
["Database Tools"]="Outils base de données"
|
|
["Configuration"]="Configuration"
|
|
["Log Viewer"]="Visualisateur de journaux"
|
|
["Cache Cleanup"]="Nettoyage du cache"
|
|
["Git Commit History"]="Historique des commits Git"
|
|
["Branch Comparison"]="Comparaison de branches"
|
|
["Cron Scheduler"]="Planificateur Cron"
|
|
["Git Stashes"]="Stashs Git"
|
|
["Missing: %s"]="Manquant : %s"
|
|
["Missing dir: %s"]="Répertoire manquant : %s"
|
|
["All commands available"]="Toutes les commandes disponibles"
|
|
["All directories exist"]="Tous les répertoires existent"
|
|
["Database connected"]="Base de données connectée"
|
|
["Maven build failed (see: %s)"]="Build Maven échoué (voir : %s)"
|
|
["yarn install failed"]="yarn install échoué"
|
|
["yarn build failed (see: %s)"]="yarn build échoué (voir : %s)"
|
|
["Jar: %s"]="Jar : %s"
|
|
["Backup: %s — %s"]="Sauvegarde : %s — %s"
|
|
["Restored"]="Restauré"
|
|
["Cancelled"]="Annulé"
|
|
["Invalid"]="Invalide"
|
|
["Invalid selection"]="Sélection invalide"
|
|
["Scheduled daily 03:00"]="Planifié quotidien 03:00"
|
|
["Scheduled daily 04:00"]="Planifié quotidien 04:00"
|
|
["Scheduled weekly"]="Planifié hebdomadaire"
|
|
["Removed"]="Supprimé"
|
|
["Return"]="Retour"
|
|
["none"]="aucun"
|
|
["Yes"]="Oui"
|
|
["No"]="Non"
|
|
["Goodbye!"]="Au revoir !"
|
|
) ;;
|
|
es) _T=(
|
|
# ── MENU ──────────────────────────────────────────────
|
|
["UPDATE"]="ACTUALIZAR"
|
|
["TOOLS"]="HERRAMIENTAS"
|
|
["ADVANCED"]="AVANZADO"
|
|
["Quick Update — current branch"]="Actualización rápida — rama actual"
|
|
["Full Update (choose branch)"]="Actualización completa (elegir rama)"
|
|
["Emulator Only"]="Solo Emulador"
|
|
["Octane Client Only"]="Solo Cliente Octane"
|
|
["Renderer Only"]="Solo Renderizador"
|
|
["Configs Only"]="Solo Configuraciones"
|
|
["Dev Mode (skip builds, only configs)"]="Modo Dev (sin builds, solo configs)"
|
|
["Switch Branch"]="Cambiar Rama"
|
|
["Database"]="Base de Datos"
|
|
["Backup / Restore"]="Respaldo / Restaurar"
|
|
["Services"]="Servicios"
|
|
["Status"]="Estado"
|
|
["Health Check"]="Verificación de Salud"
|
|
["Clean"]="Limpiar"
|
|
["Logs"]="Registros"
|
|
["Config"]="Configuración"
|
|
["Git Log"]="Registro Git"
|
|
["Compare Branches"]="Comparar Ramas"
|
|
["Cron Scheduler"]="Programador Cron"
|
|
["View Git Stashes"]="Ver Stashes Git"
|
|
["Exit"]="Salir"
|
|
["Flyaway Repair"]="Reparación Flyaway"
|
|
["Auto-setup missing repos"]="Auto-instalar repos faltantes"
|
|
["Cleanup old Nitro dirs"]="Limpiar directorios Nitro antiguos"
|
|
["Rebuild Frontend"]="Reconstruir Frontend"
|
|
["Rebuild Emulator"]="Reconstruir Emulador"
|
|
["Select [0-23]"]="Seleccionar [0-23]"
|
|
["Press Enter to continue..."]="Presione Enter para continuar..."
|
|
["Goodbye!"]="¡Hasta luego!"
|
|
["Start update?"]="¿Iniciar actualización?"
|
|
["Continue?"]="¿Continuar?"
|
|
["Missing: %s"]="Falta: %s"
|
|
["Missing dir: %s"]="Directorio faltante: %s"
|
|
["All commands available"]="Todos los comandos disponibles"
|
|
["All directories exist"]="Todos los directorios existen"
|
|
["Database connected"]="Base de datos conectada"
|
|
["Maven build failed (see: %s)"]="Build Maven falló (ver: %s)"
|
|
["yarn install failed"]="yarn install falló"
|
|
["yarn build failed (see: %s)"]="yarn build falló (ver: %s)"
|
|
["Goodbye!"]="¡Hasta luego!"
|
|
["Return"]="Volver"
|
|
["none"]="ninguno"
|
|
["Yes"]="Sí"
|
|
["No"]="No"
|
|
) ;;
|
|
de) _T=(
|
|
# ── MENU ──────────────────────────────────────────────
|
|
["UPDATE"]="AKTUALISIEREN"
|
|
["TOOLS"]="WERKZEUGE"
|
|
["ADVANCED"]="ERWEITERT"
|
|
["Quick Update — current branch"]="Schnellupdate — aktueller Branch"
|
|
["Full Update (choose branch)"]="Vollständiges Update (Branch wählen)"
|
|
["Emulator Only"]="Nur Emulator"
|
|
["Octane Client Only"]="Nur Octane Client"
|
|
["Renderer Only"]="Nur Renderer"
|
|
["Configs Only"]="Nur Konfigurationen"
|
|
["Dev Mode (skip builds, only configs)"]="Dev-Modus (ohne Builds, nur Konfigs)"
|
|
["Switch Branch"]="Branch wechseln"
|
|
["Database"]="Datenbank"
|
|
["Backup / Restore"]="Sicherung / Wiederherstellung"
|
|
["Services"]="Dienste"
|
|
["Status"]="Status"
|
|
["Health Check"]="Gesundheitsprüfung"
|
|
["Clean"]="Bereinigen"
|
|
["Logs"]="Protokolle"
|
|
["Config"]="Konfiguration"
|
|
["Git Log"]="Git-Verlauf"
|
|
["Compare Branches"]="Branches vergleichen"
|
|
["Cron Scheduler"]="Cron-Planer"
|
|
["View Git Stashes"]="Git-Stashes anzeigen"
|
|
["Exit"]="Beenden"
|
|
["Flyaway Repair"]="Flyaway-Reparatur"
|
|
["Auto-setup missing repos"]="Fehlende Repos automatisch einrichten"
|
|
["Cleanup old Nitro dirs"]="Alte Nitro-Verzeichnisse aufräumen"
|
|
["Rebuild Frontend"]="Frontend neu bauen"
|
|
["Rebuild Emulator"]="Emulator neu bauen"
|
|
["Select [0-23]"]="Auswählen [0-23]"
|
|
["Press Enter to continue..."]="Drücken Sie Enter zum Fortfahren..."
|
|
["Goodbye!"]="Auf Wiedersehen!"
|
|
["Start update?"]="Update starten?"
|
|
["Continue?"]="Fortfahren?"
|
|
["Missing: %s"]="Fehlt: %s"
|
|
["Missing dir: %s"]="Verzeichnis fehlt: %s"
|
|
["All commands available"]="Alle Befehle verfügbar"
|
|
["All directories exist"]="Alle Verzeichnisse vorhanden"
|
|
["Database connected"]="Datenbank verbunden"
|
|
["Maven build failed (see: %s)"]="Maven-Build fehlgeschlagen (siehe: %s)"
|
|
["yarn install failed"]="yarn install fehlgeschlagen"
|
|
["yarn build failed (see: %s)"]="yarn build fehlgeschlagen (siehe: %s)"
|
|
["Goodbye!"]="Auf Wiedersehen!"
|
|
["Return"]="Zurück"
|
|
["none"]="keine"
|
|
["Yes"]="Ja"
|
|
["No"]="Nein"
|
|
) ;;
|
|
it) _T=(
|
|
# ── MENU ──────────────────────────────────────────────
|
|
["UPDATE"]="AGGIORNA"
|
|
["TOOLS"]="STRUMENTI"
|
|
["ADVANCED"]="AVANZATE"
|
|
["Quick Update — current branch"]="Aggiornamento rapido — ramo attuale"
|
|
["Full Update (choose branch)"]="Aggiornamento completo (scegli ramo)"
|
|
["Emulator Only"]="Solo Emulatore"
|
|
["Octane Client Only"]="Solo Client Octane"
|
|
["Renderer Only"]="Solo Renderer"
|
|
["Configs Only"]="Solo Configurazioni"
|
|
["Dev Mode (skip builds, only configs)"]="Modalità Dev (senza build, solo configs)"
|
|
["Switch Branch"]="Cambia Ramo"
|
|
["Database"]="Database"
|
|
["Backup / Restore"]="Backup / Ripristino"
|
|
["Services"]="Servizi"
|
|
["Status"]="Stato"
|
|
["Health Check"]="Controllo Salute"
|
|
["Clean"]="Pulisci"
|
|
["Logs"]="Registri"
|
|
["Config"]="Configurazione"
|
|
["Git Log"]="Registro Git"
|
|
["Compare Branches"]="Confronta Rami"
|
|
["Cron Scheduler"]="Pianificatore Cron"
|
|
["View Git Stashes"]="Vedi Stash Git"
|
|
["Exit"]="Esci"
|
|
["Flyaway Repair"]="Riparazione Flyaway"
|
|
["Auto-setup missing repos"]="Auto-installazione repo mancanti"
|
|
["Cleanup old Nitro dirs"]="Pulisci vecchie cartelle Nitro"
|
|
["Rebuild Frontend"]="Ricostruisci Frontend"
|
|
["Rebuild Emulator"]="Ricostruisci Emulatore"
|
|
["Select [0-23]"]="Seleziona [0-23]"
|
|
["Press Enter to continue..."]="Premi Invio per continuare..."
|
|
["Goodbye!"]="Arrivederci!"
|
|
["Start update?"]="Avviare aggiornamento?"
|
|
["Continue?"]="Continuare?"
|
|
["Missing: %s"]="Mancante: %s"
|
|
["Missing dir: %s"]="Directory mancante: %s"
|
|
["All commands available"]="Tutti i comandi disponibili"
|
|
["All directories exist"]="Tutte le directory esistono"
|
|
["Database connected"]="Database connesso"
|
|
["Maven build failed (see: %s)"]="Build Maven fallita (vedi: %s)"
|
|
["yarn install failed"]="yarn install fallito"
|
|
["yarn build failed (see: %s)"]="yarn build fallito (vedi: %s)"
|
|
["Goodbye!"]="Arrivederci!"
|
|
["Return"]="Indietro"
|
|
["none"]="nessuno"
|
|
["Yes"]="Sì"
|
|
["No"]="No"
|
|
) ;;
|
|
esac
|
|
}
|
|
|
|
# =============================================================================
|
|
# LOCKFILE
|
|
# =============================================================================
|
|
acquire_lock() {
|
|
exec 9>"$LOCK_FILE"
|
|
if ! flock -n 9; then
|
|
die "Another instance is already running (lock: %s)" "$LOCK_FILE"
|
|
fi
|
|
}
|
|
|
|
release_lock() {
|
|
# Parallel job subshells inherit fd 9; releasing it here would drop the
|
|
# main process's lock while the other jobs are still running.
|
|
[ -n "$PARALLEL_JOB" ] && return 0
|
|
flock -u 9 2>/dev/null || true
|
|
rm -f "$LOCK_FILE" 2>/dev/null || true
|
|
}
|
|
|
|
# =============================================================================
|
|
# UI HELPERS
|
|
# =============================================================================
|
|
log_write() { printf "[%s] [%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" "$2" >> "$LOG_FILE" 2>/dev/null || true; }
|
|
info() { local m; m=$(_t "$1" "${@:2}"); echo -e " ${C_BLUE}${E_ARROW}${C_RESET} $m"; log_write "INFO" "$m"; }
|
|
ok() { local m; m=$(_t "$1" "${@:2}"); echo -e " ${C_GREEN}${E_OK}${C_RESET} $m"; log_write "OK" "$m"; }
|
|
warn() { local m; m=$(_t "$1" "${@:2}"); echo -e " ${C_YELLOW}${E_WARN}${C_RESET} $m"; WARNINGS=$((WARNINGS + 1)); log_write "WARN" "$m"; }
|
|
fail() { local m; m=$(_t "$1" "${@:2}"); echo -e " ${C_RED}${E_FAIL}${C_RESET} $m"; log_write "FAIL" "$m"; }
|
|
|
|
die() {
|
|
local m; m=$(_t "$1" "${@:2}")
|
|
echo ""
|
|
echo -e " ${C_BG_RED}${C_WHITE}${C_BOLD} $(_t "ERROR") ${C_RESET} ${C_RED}$m${C_RESET}"
|
|
log_write "FATAL" "$m"
|
|
cleanup_notify_failure "$m"
|
|
release_lock
|
|
cursor_show
|
|
exit 1
|
|
}
|
|
cursor_hide() { tput civis 2>/dev/null || true; }
|
|
cursor_show() { tput cnorm 2>/dev/null || true; }
|
|
clear_line() { printf "\r\033[K"; }
|
|
repeat() { local ch="${1:-=}" n="${2:-1}"; printf '%*s' "$n" '' | tr ' ' "$ch"; }
|
|
list_sorted() { local d="$1" p="$2" m="${3:-20}"; find "$d" -name "$p" -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -n "$m" | sed 's/^[0-9.]* //'; }
|
|
|
|
service_active() {
|
|
local svc="$1"
|
|
if command -v systemctl &>/dev/null; then
|
|
systemctl is-active "$svc" &>/dev/null
|
|
elif command -v service &>/dev/null; then
|
|
service "$svc" status &>/dev/null
|
|
else
|
|
pgrep -x "$svc" &>/dev/null
|
|
fi
|
|
}
|
|
|
|
format_size() {
|
|
local bytes="${1:-0}"
|
|
if command -v numfmt &>/dev/null; then
|
|
numfmt --to=iec "$bytes" 2>/dev/null || echo "${bytes}B"
|
|
else
|
|
echo "${bytes}B"
|
|
fi
|
|
}
|
|
|
|
SPINNER_PID=""
|
|
spinner_start() {
|
|
local msg="${1:-Working...}"
|
|
local frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
|
|
cursor_hide
|
|
(
|
|
local i=0
|
|
while true; do
|
|
printf "\r ${C_CYAN}${frames[$i]}${C_RESET} %s " "$(_t "$msg")"
|
|
i=$(( (i + 1) % ${#frames[@]} ))
|
|
sleep 0.1
|
|
done
|
|
) &
|
|
SPINNER_PID=$!
|
|
}
|
|
spinner_stop() {
|
|
local st="${1:-ok}"
|
|
if [ -n "$SPINNER_PID" ]; then
|
|
kill "$SPINNER_PID" 2>/dev/null || true
|
|
wait "$SPINNER_PID" 2>/dev/null || true
|
|
SPINNER_PID=""
|
|
fi
|
|
clear_line; cursor_show
|
|
case "$st" in ok) printf "\r ${C_GREEN}${E_OK}${C_RESET} Done\n" ;; warn) printf "\r ${C_YELLOW}${E_WARN}${C_RESET} Done\n" ;; fail) printf "\r ${C_RED}${E_FAIL}${C_RESET} Failed\n" ;; esac
|
|
}
|
|
|
|
step() {
|
|
local num="${1}" total="${2}" title="${3}"
|
|
echo ""
|
|
echo -e " ${C_BG_CYAN}${C_WHITE}${C_BOLD} Step ${num}/${total} — $(_t "$title") ${C_RESET}"
|
|
echo ""
|
|
}
|
|
|
|
confirm() {
|
|
local msg="${1:-Continue?}" default="${2:-y}"
|
|
local hint="[Y/n]"; [ "$default" = "n" ] && hint="[y/N]"
|
|
echo -en " ${C_YELLOW}${E_ARROW}${C_RESET} ${C_BOLD}$(_t "$msg")${C_RESET} $hint "
|
|
local reply; read -rt 30 reply; reply="${reply:-$default}"
|
|
[[ "$reply" =~ ^[Yy] ]]
|
|
}
|
|
|
|
dry_run() {
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo -e " ${C_DIM}[DRY-RUN]${C_RESET} $*"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
box_top() {
|
|
local title="${1:-}" width="${2:-60}"
|
|
if [ -n "$title" ]; then
|
|
local tt; tt=$(_t "$title")
|
|
local pad=$((width - ${#tt} - 6)); [ $pad -lt 0 ] && pad=0
|
|
printf "${C_CYAN}╔═══ ${C_BOLD}%s${C_RESET}${C_CYAN}" "$tt"
|
|
repeat '═' "$pad"
|
|
echo -e "╗${C_RESET}"
|
|
else
|
|
printf "${C_CYAN}╔"; repeat '═' "$width"; echo -e "╗${C_RESET}"
|
|
fi
|
|
}
|
|
box_kv() {
|
|
local key="${1}" val="${2}" width="${3:-60}"
|
|
local kk; kk=$(_t "$key")
|
|
local ck=$(echo -e "$kk" | sed 's/\x1b\[[0-9;]*m//g')
|
|
local cv=$(echo -e "$val" | sed 's/\x1b\[[0-9;]*m//g')
|
|
local pad=$((width - ${#ck} - ${#cv} - 6)); [ $pad -lt 0 ] && pad=0
|
|
printf "${C_CYAN}║${C_RESET} %s%*s%s ${C_CYAN}║${C_RESET}\n" "$kk" $pad "" "$val"
|
|
}
|
|
box_line() {
|
|
local text="${1:-}" width="${2:-60}"
|
|
local ct=$(echo -e "$text" | sed 's/\x1b\[[0-9;]*m//g')
|
|
local pad=$((width - ${#ct} - 4)); [ $pad -lt 0 ] && pad=0
|
|
printf "${C_CYAN}║${C_RESET} %s%*s${C_CYAN}║${C_RESET}\n" "$text" $pad ""
|
|
}
|
|
box_bottom() { local w="${1:-60}"; printf "${C_CYAN}╚"; repeat '═' "$w"; echo -e "╝${C_RESET}"; }
|
|
|
|
# =============================================================================
|
|
# NOTIFICATIONS
|
|
# =============================================================================
|
|
notify() {
|
|
local status="$1" message="$2"
|
|
[ -z "$NOTIFY_URL" ] && return 0
|
|
local elapsed=$(($(date +%s) - START_TIME))
|
|
local ef; ef=$(printf '%dm %ds' $((elapsed / 60)) $((elapsed % 60)))
|
|
N_STATUS="$status" N_MESSAGE="$message" N_DURATION="$ef" \
|
|
python3 -c "
|
|
import json, os
|
|
payload = {
|
|
'text': '[Octane Update] ' + os.environ['N_STATUS'],
|
|
'attachments': [{
|
|
'color': 'good' if os.environ['N_STATUS'] == 'SUCCESS' else 'danger',
|
|
'fields': [
|
|
{'title': 'Status', 'value': os.environ['N_MESSAGE'], 'short': True},
|
|
{'title': 'Duration', 'value': os.environ['N_DURATION'], 'short': True}
|
|
]
|
|
}]
|
|
}
|
|
print(json.dumps(payload))
|
|
" | curl -s -o /dev/null -X POST "$NOTIFY_URL" -H "Content-Type: application/json" -d @- 2>/dev/null || true
|
|
}
|
|
cleanup_notify_failure() { $NOTIFY_FAILED && return; NOTIFY_FAILED=true; notify "FAILED" "$1"; }
|
|
|
|
cleanup_on_exit() {
|
|
local ec=$?
|
|
# Parallel job subshells must never run the full cleanup (rollback,
|
|
# lock release, notify); only the main process owns those. Their status
|
|
# is captured by parallel_wait instead.
|
|
if [ -n "$PARALLEL_JOB" ]; then return $ec; fi
|
|
cursor_show; release_lock
|
|
if [ $ec -ne 0 ] && [ "$ROLLBACK_NEEDED" = true ]; then
|
|
echo -e "\n ${C_BG_RED}${C_WHITE}${C_BOLD} $(_t "ROLLBACK") ${C_RESET}"
|
|
echo -e " $(_t "Restoring config back-ups from") $CONFIG_BACKUP_DIR/rollback..."
|
|
if [ -d "$CONFIG_BACKUP_DIR/rollback" ]; then
|
|
cp -r "$CONFIG_BACKUP_DIR/rollback/"* "$NITRO_SRC_DIR/" 2>/dev/null || true
|
|
ok "Config files restored from rollback"
|
|
fi
|
|
restore_binaries
|
|
fi
|
|
# Preserve the real exit status: the last command in an EXIT trap defines
|
|
# the script's exit code, so never let a guard/notify call overwrite it.
|
|
if [ $ec -ne 0 ]; then
|
|
cleanup_notify_failure "Exit code $ec"
|
|
return $ec
|
|
fi
|
|
return 0
|
|
}
|
|
trap cleanup_on_exit EXIT
|
|
trap 'release_lock; cursor_show; exit 1' SIGINT SIGTERM
|
|
|
|
# =============================================================================
|
|
# GIT HELPERS
|
|
# =============================================================================
|
|
detect_branches() {
|
|
local repos=("$EMULATOR_REPO" "$NITRO_CLIENT" "$NITRO_RENDERER")
|
|
for repo in "${repos[@]}"; do
|
|
[ -d "$repo/.git" ] || continue
|
|
(
|
|
cd "$repo" 2>/dev/null || exit
|
|
git branch -r 2>/dev/null | grep -v '\->' | sed 's/^[[:space:]]*//; s/^origin\///' | grep -v '^HEAD$'
|
|
)
|
|
done | sort -u
|
|
}
|
|
|
|
load_branches() {
|
|
local -n _arr="$1"
|
|
readarray -t _arr < <(detect_branches)
|
|
}
|
|
|
|
detect_best_branch() {
|
|
local repo="$1" preferred="$2"
|
|
(
|
|
cd "$repo" 2>/dev/null || { echo "main"; exit 0; }
|
|
local cur; cur=$(git branch --show-current 2>/dev/null || echo "")
|
|
for v in "$preferred" "${preferred^}" "$cur" "main" "master"; do
|
|
[ -n "$v" ] && git rev-parse --verify "$v" &>/dev/null && { echo "$v"; exit 0; }
|
|
done
|
|
echo "${cur:-main}"
|
|
)
|
|
}
|
|
|
|
git_update() {
|
|
local repo="$1" branch="$2"
|
|
local name; name=$(basename "$repo")
|
|
local rc=1
|
|
(
|
|
cd "$repo" || { warn "Cannot access $repo"; exit 2; }
|
|
# Only stash TRACKED changes (never untracked files — runtime config
|
|
# data in public/configuration/ must stay in place) and only when the
|
|
# working tree is dirty, to keep the pull fast-forwardable. The stash is
|
|
# left in the repo (recoverable via `git stash list`), never dropped.
|
|
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
|
|
git stash push -m "auto-update $(date +%F-%T)" 2>/dev/null \
|
|
&& warn "Local tracked changes in $(basename "$repo") stashed (see 'git stash list')"
|
|
fi
|
|
local old_head; old_head=$(git rev-parse HEAD 2>/dev/null || echo "")
|
|
local rb; rb=$(detect_best_branch "$repo" "$branch")
|
|
[ "$rb" != "$branch" ] && info "Branch '$branch' not in $(basename "$repo"), using '$rb'"
|
|
git checkout "$rb" 2>/dev/null || { warn "Cannot checkout '$rb' in $(basename "$repo")"; exit 2; }
|
|
local pulled=1
|
|
for attempt in 1 2 3; do
|
|
if git pull --ff-only origin "$rb" 2>/dev/null; then
|
|
pulled=0; break
|
|
elif git pull --ff-only 2>/dev/null; then
|
|
pulled=0; break
|
|
fi
|
|
warn "Pull attempt %s failed in %s, retrying..." "$attempt" "$(basename "$repo")" >&2
|
|
sleep 2
|
|
done
|
|
if [ "$pulled" -ne 0 ]; then
|
|
warn "Pull failed in $(basename "$repo")"
|
|
echo "git error: pull failed" > "$PARALLEL_STATE_DIR/giterror-$name"
|
|
exit 2
|
|
fi
|
|
# Remember the pre-update commit so a later build failure can reset the
|
|
# repo back to it; otherwise the next run would see HEAD==origin and
|
|
# silently skip the never-built change.
|
|
echo "$old_head" > "$PARALLEL_STATE_DIR/oldhead-$name"
|
|
[ "$(git rev-parse HEAD 2>/dev/null)" != "$old_head" ] && exit 0
|
|
exit 1
|
|
)
|
|
rc=$?
|
|
# Return 0 = new commits fetched, 1 = nothing to do, 2 = real git error.
|
|
return $rc
|
|
}
|
|
|
|
# Reverse a successful pull after a failed build, so the change is picked up
|
|
# again on the next run instead of being silently skipped (HEAD would otherwise
|
|
# equal origin and git_update would report "already up to date").
|
|
git_revert_repo() {
|
|
local repo="$1"
|
|
local name; name=$(basename "$repo")
|
|
[ -f "$PARALLEL_STATE_DIR/oldhead-$name" ] || return 0
|
|
local prev; prev=$(cat "$PARALLEL_STATE_DIR/oldhead-$name" 2>/dev/null)
|
|
[ -n "$prev" ] || return 0
|
|
if git -C "$repo" reset --hard "$prev" >/dev/null 2>&1; then
|
|
warn "Reverted $(basename "$repo") to $prev (build failed)"
|
|
else
|
|
warn "Could not revert $(basename "$repo") to $prev"
|
|
fi
|
|
rm -f "$PARALLEL_STATE_DIR/oldhead-$name" 2>/dev/null || true
|
|
}
|
|
|
|
# Run a yarn command inside a repo that pins an unavailable Yarn Berry version.
|
|
# Upstream package.json now declares e.g. "packageManager": "[email protected]",
|
|
# which the system Yarn 1 refuses to run ("current global version is 1.22.22")
|
|
# and which corepack cannot fetch (yarn 4 is not published on the registry).
|
|
# Drop the field for the duration of the command, then restore the pristine
|
|
# file so the repo stays clean for the next git pull. Runs inside the repo and
|
|
# always returns the caller to SCRIPT_DIR so cwd never leaks between repos.
|
|
yarn_run() {
|
|
local repo="$1"; shift
|
|
cd "$repo" 2>/dev/null || { warn "Cannot cd into %s" "$repo"; return 1; }
|
|
local modified=false
|
|
if python3 -c "
|
|
import json, sys
|
|
p = 'package.json'
|
|
d = json.load(open(p))
|
|
pm = d.get('packageManager', '')
|
|
if pm.startswith('yarn@') and not pm.startswith('yarn@1.'):
|
|
d.pop('packageManager', None)
|
|
json.dump(d, open(p, 'w'), indent=2)
|
|
open(p, 'a').write(chr(10))
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
" 2>/dev/null; then
|
|
modified=true
|
|
fi
|
|
yarn "$@"
|
|
local rc=$?
|
|
if [ "$modified" = true ]; then
|
|
git checkout -- package.json 2>/dev/null || true
|
|
fi
|
|
cd "$SCRIPT_DIR" 2>/dev/null || true
|
|
return $rc
|
|
}
|
|
|
|
# yarn install scoped to a per-repo cache folder. Parallel installs (renderer +
|
|
# client) must never share a cache — yarn 1 corrupts the shared cache, leaving
|
|
# node_modules incomplete (vite/pixi.js missing) while still exiting 0.
|
|
# $1 = repo dir; remaining args forwarded to yarn.
|
|
yarn_install_repo() {
|
|
local repo="$1"; shift
|
|
local cache_dir="$YARN_CACHE_DIR/$(basename "$repo")"
|
|
mkdir -p "$cache_dir" 2>/dev/null || true
|
|
# NODE_ENV may be exported as "production" via .env (the CMS runs in prod),
|
|
# which makes yarn 1 skip devDependencies — vite is a devDep, so builds
|
|
# would fail with "vite: not found". Force a non-production env so the full
|
|
# dependency tree (including dev deps) is always installed.
|
|
NODE_ENV=development yarn_run "$repo" install --cache-folder "$cache_dir" "$@" >> "$LOG_FILE" 2>&1
|
|
}
|
|
|
|
# Nuke a repo's own yarn cache and node_modules for a truly clean reinstall.
|
|
# Parallel installs may have corrupted the cache, so it must be discarded too.
|
|
yarn_reset_repo() {
|
|
local repo="$1"
|
|
rm -rf "$repo/node_modules" "$YARN_CACHE_DIR/$(basename "$repo")" 2>/dev/null || true
|
|
mkdir -p "$YARN_CACHE_DIR/$(basename "$repo")" 2>/dev/null || true
|
|
}
|
|
|
|
# =============================================================================
|
|
# PARALLEL HELPERS
|
|
# =============================================================================
|
|
PARALLEL_PIDS=()
|
|
|
|
PARALLEL_NAMES=()
|
|
PARALLEL_FAILED=()
|
|
PARALLEL_RESULTS=()
|
|
|
|
# Each parallel job runs in a subshell, so variable changes (HAD_UPDATES,
|
|
# UPDATED_REPOS, NITRO_BUILT) made inside update_renderer/update_client would
|
|
# otherwise be lost. Jobs persist their state to a per-job file which
|
|
# parallel_wait re-reads back into the parent shell.
|
|
PARALLEL_STATE_DIR="${TMPDIR:-/tmp}/nitro-parallel-$$"
|
|
mkdir -p "$PARALLEL_STATE_DIR" 2>/dev/null || true
|
|
|
|
parallel_run() {
|
|
local name="$1"; shift
|
|
PARALLEL_NAMES+=("$name")
|
|
(
|
|
# This subshell inherits the parent's EXIT trap and lock fd 9. Mark it
|
|
# so cleanup_on_exit/release_lock become no-ops here: the main process
|
|
# owns rollback, lock release and notifications.
|
|
PARALLEL_JOB=1
|
|
# Snapshot inherited state so we only persist the delta back: the
|
|
# emulator (run before the parallel phase) may already be in
|
|
# UPDATED_REPOS, so emitting the full array would duplicate it.
|
|
local _orig="${UPDATED_REPOS[*]:-}"
|
|
"$@" >/dev/null 2>&1
|
|
local rc=$?
|
|
{
|
|
# Note: exit status is captured via `wait`; only fold state vars back.
|
|
echo "HAD_UPDATES=$HAD_UPDATES"
|
|
echo "NITRO_BUILT=$NITRO_BUILT"
|
|
for r in "${UPDATED_REPOS[@]:-}"; do
|
|
case " $_orig " in
|
|
*" $r "*) ;;
|
|
*) printf 'UPDATED_REPOS+=(\047%s\047)\n' "$r" ;;
|
|
esac
|
|
done
|
|
} > "$PARALLEL_STATE_DIR/$name"
|
|
exit $rc
|
|
) &
|
|
PARALLEL_PIDS+=($!)
|
|
}
|
|
|
|
parallel_wait() {
|
|
# Collect per-job pass/fail based on the real exit status of each job.
|
|
PARALLEL_RESULTS=()
|
|
PARALLEL_FAILED=()
|
|
local i=0
|
|
for pid in "${PARALLEL_PIDS[@]}"; do
|
|
local name="${PARALLEL_NAMES[$i]}"
|
|
if wait "$pid" 2>/dev/null; then
|
|
PARALLEL_RESULTS+=("__PARALLEL_OK__${name}")
|
|
else
|
|
PARALLEL_RESULTS+=("__PARALLEL_FAIL__${name}")
|
|
PARALLEL_FAILED+=("$name")
|
|
fi
|
|
# Fold the job's persisted state back into the parent shell so the
|
|
# summary reflects renderer/client updates, not just the emulator.
|
|
if [ -f "$PARALLEL_STATE_DIR/$name" ]; then
|
|
. "$PARALLEL_STATE_DIR/$name"
|
|
rm -f "$PARALLEL_STATE_DIR/$name"
|
|
fi
|
|
i=$((i+1))
|
|
done
|
|
PARALLEL_PIDS=()
|
|
PARALLEL_NAMES=()
|
|
rm -rf "$PARALLEL_STATE_DIR" 2>/dev/null || true
|
|
}
|
|
|
|
parallel_failed() {
|
|
# Returns 0 (success) when no backgrounded job failed.
|
|
[ ${#PARALLEL_FAILED[@]} -eq 0 ]
|
|
}
|
|
|
|
# =============================================================================
|
|
# CONFIG BACKUP & VALIDATION
|
|
# =============================================================================
|
|
backup_configs() {
|
|
local ts=$(date +%Y%m%d-%H%M%S)
|
|
local bdir="$CONFIG_BACKUP_DIR/$ts"
|
|
mkdir -p "$bdir"
|
|
if [ -d "$NITRO_SRC_DIR" ]; then
|
|
cp -r "$NITRO_SRC_DIR"/*.json "$bdir/" 2>/dev/null || true
|
|
fi
|
|
# Keep a rolling 'rollback' pointer
|
|
rm -rf "$CONFIG_BACKUP_DIR/rollback"
|
|
cp -r "$bdir" "$CONFIG_BACKUP_DIR/rollback" 2>/dev/null || true
|
|
echo "$bdir"
|
|
}
|
|
|
|
validate_json() {
|
|
local file="$1"
|
|
# Strict JSON first...
|
|
if python3 -m json.tool "$file" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
# ...otherwise fall back to JSONC (the renderer configs legitimately carry
|
|
# // and /* */ comments — that is not an error, it is by design).
|
|
if command -v node &>/dev/null && \
|
|
node -e "require('jsonc-parser').parse(require('fs').readFileSync(process.argv[1],'utf-8'),[],{allowTrailingComma:true,allowEmptyContent:true});" "$file" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
validate_configs() {
|
|
local dir="$1"
|
|
local errors=0
|
|
for f in "$dir"/*.json; do
|
|
[ -f "$f" ] || continue
|
|
case "$(basename "$f")" in
|
|
*jsonc*|*.example*) continue ;;
|
|
esac
|
|
if ! validate_json "$f"; then
|
|
warn "Invalid JSON: %s" "$(basename "$f")"
|
|
errors=$((errors + 1))
|
|
fi
|
|
done
|
|
[ "$errors" -eq 0 ] && ok "All config files valid" || warn "$errors config(s) invalid"
|
|
}
|
|
|
|
# =============================================================================
|
|
# BINARY BACKUP & ROLLBACK
|
|
# =============================================================================
|
|
backup_binaries() {
|
|
[ -n "$BINARY_BACKUP_DIR" ] && return
|
|
BINARY_BACKUP_DIR="$CONFIG_BACKUP_DIR/binaries-$(date +%Y%m%d-%H%M%S)"
|
|
mkdir -p "$BINARY_BACKUP_DIR"
|
|
|
|
local jar_dir="$EMULATOR_MODULE/target"
|
|
if [ -d "$jar_dir" ] && ls "$jar_dir"/Polaris-*-jar-with-dependencies.jar &>/dev/null 2>&1; then
|
|
cp "$jar_dir"/Polaris-*-jar-with-dependencies.jar "$BINARY_BACKUP_DIR/" 2>/dev/null || true
|
|
ok "Emulator JAR backed up"
|
|
fi
|
|
|
|
local client_dist="$NITRO_CLIENT/dist"
|
|
if [ -d "$client_dist" ]; then
|
|
cp -r "$client_dist" "$BINARY_BACKUP_DIR/client-dist" 2>/dev/null || true
|
|
ok "Octane dist backed up"
|
|
fi
|
|
}
|
|
|
|
restore_binaries() {
|
|
[ -z "$BINARY_BACKUP_DIR" ] && return
|
|
[ ! -d "$BINARY_BACKUP_DIR" ] && return
|
|
echo ""
|
|
echo -e " ${C_BG_RED}${C_WHITE}${C_BOLD} $(_t "RESTORE BINARIES") ${C_RESET}"
|
|
|
|
if ls "$BINARY_BACKUP_DIR"/Polaris-*-jar-with-dependencies.jar &>/dev/null 2>&1; then
|
|
local jar_dir="$EMULATOR_MODULE/target"
|
|
mkdir -p "$jar_dir"
|
|
cp "$BINARY_BACKUP_DIR"/Polaris-*-jar-with-dependencies.jar "$jar_dir/" 2>/dev/null || true
|
|
ok "Emulator JAR restored"
|
|
fi
|
|
|
|
if [ -d "$BINARY_BACKUP_DIR/client-dist" ]; then
|
|
local client_dist="$NITRO_CLIENT/dist"
|
|
rm -rf "$client_dist" 2>/dev/null || true
|
|
cp -r "$BINARY_BACKUP_DIR/client-dist" "$client_dist" 2>/dev/null || true
|
|
ok "Octane dist restored"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# OLD NITRO CLEANUP
|
|
# =============================================================================
|
|
cmd_cleanup_old_nitro() {
|
|
step 1 1 "Cleanup Old Nitro"
|
|
echo ""
|
|
|
|
local www_dir="/var/www"
|
|
local old_dirs=()
|
|
local current_client="$NITRO_CLIENT"
|
|
local current_renderer="$NITRO_RENDERER"
|
|
|
|
# Scan for old Nitro directories that are NOT the current active ones
|
|
for candidate in "$www_dir"/Nitro-V3 "$www_dir"/Nitro_Render_V3 "$www_dir"/Nitro_Render "$www_dir"/Nitro "$www_dir"/Nitro-V3-*; do
|
|
[ -d "$candidate" ] || continue
|
|
[ "$(realpath "$candidate" 2>/dev/null)" = "$(realpath "$current_client" 2>/dev/null)" ] && continue
|
|
[ "$(realpath "$candidate" 2>/dev/null)" = "$(realpath "$current_renderer" 2>/dev/null)" ] && continue
|
|
old_dirs+=("$candidate")
|
|
done
|
|
|
|
if [ ${#old_dirs[@]} -eq 0 ]; then
|
|
ok "No old Nitro directories found"
|
|
else
|
|
echo -e " ${C_YELLOW}${C_BOLD}Found ${#old_dirs[@]} old Nitro director(y/ies):${C_RESET}"
|
|
echo ""
|
|
local total_size=0
|
|
for d in "${old_dirs[@]}"; do
|
|
local sz=$(du -sh "$d" 2>/dev/null | cut -f1)
|
|
local files=$(find "$d" -type f 2>/dev/null | wc -l)
|
|
echo -e " ${C_RED}✗${C_RESET} ${C_BOLD}$(basename "$d")${C_RESET} ${C_DIM}($sz, $files files)${C_RESET}"
|
|
local bytes=$(du -sb "$d" 2>/dev/null | cut -f1)
|
|
total_size=$((total_size + bytes))
|
|
done
|
|
echo ""
|
|
echo -e " ${C_CYAN}Total space: $(numfmt --to=iec "$total_size" 2>/dev/null || echo "${total_size}B")${C_RESET}"
|
|
echo ""
|
|
|
|
# ── STEP 1: Migrate symlinks pointing to old dirs ──
|
|
echo -e " ${C_BOLD}Checking symlinks...${C_RESET}"
|
|
local symlinks_fixed=0
|
|
local root_cmd=""
|
|
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
|
|
|
|
for old_dir in "${old_dirs[@]}"; do
|
|
local old_name=$(basename "$old_dir")
|
|
# Determine the correct new target
|
|
local new_target=""
|
|
case "$old_name" in
|
|
Nitro-V3|Nitro-V3-*) new_target="$current_client" ;;
|
|
Nitro_Render_V3|Nitro_Render) new_target="$current_renderer" ;;
|
|
esac
|
|
[ -z "$new_target" ] && continue
|
|
|
|
# Find all symlinks on the system pointing to the old dir
|
|
while IFS= read -r link; do
|
|
[ -z "$link" ] && continue
|
|
local link_target=$(readlink -f "$link" 2>/dev/null)
|
|
case "$link_target" in
|
|
*"$old_name"*|*"$old_dir"*)
|
|
echo -e " ${C_GREEN}${E_ARROW}${C_RESET} $link → $new_target"
|
|
$root_cmd ln -sfn "$new_target" "$link" 2>/dev/null && symlinks_fixed=$((symlinks_fixed + 1)) || warn "Could not update symlink: %s" "$link"
|
|
;;
|
|
esac
|
|
done < <(find /var/www /etc/nginx /etc/systemd /opt -maxdepth 4 -type l 2>/dev/null)
|
|
done
|
|
[ "$symlinks_fixed" -gt 0 ] && ok "Updated %s symlink(s)" "$symlinks_fixed" || ok "No symlinks to update"
|
|
|
|
# ── STEP 2: Update nginx configs referencing old paths ──
|
|
echo ""
|
|
echo -e " ${C_BOLD}Checking nginx configs...${C_RESET}"
|
|
local nginx_fixed=0
|
|
for conf in /etc/nginx/sites-enabled/*.conf /etc/nginx/sites-available/*.conf /etc/nginx/conf.d/*.conf; do
|
|
[ -f "$conf" ] || continue
|
|
local needs_update=false
|
|
for old_dir in "${old_dirs[@]}"; do
|
|
grep -q "$old_dir" "$conf" 2>/dev/null && needs_update=true && break
|
|
done
|
|
if [ "$needs_update" = true ]; then
|
|
local bak="$conf.bak-nitro-cleanup-$(date +%s)"
|
|
$root_cmd cp "$conf" "$bak" 2>/dev/null || true
|
|
for old_dir in "${old_dirs[@]}"; do
|
|
local old_name=$(basename "$old_dir")
|
|
local new_target=""
|
|
case "$old_name" in
|
|
Nitro-V3|Nitro-V3-*) new_target="$current_client" ;;
|
|
Nitro_Render_V3|Nitro_Render) new_target="$current_renderer" ;;
|
|
esac
|
|
[ -z "$new_target" ] && continue
|
|
$root_cmd sed -i "s|$old_dir|$new_target|g" "$conf" 2>/dev/null && nginx_fixed=$((nginx_fixed + 1))
|
|
done
|
|
echo -e " ${C_GREEN}${E_ARROW}${C_RESET} Updated: $(basename "$conf")"
|
|
fi
|
|
done
|
|
[ "$nginx_fixed" -gt 0 ] && ok "Updated %s nginx reference(s) (backups created)" "$nginx_fixed" || ok "No nginx configs to update"
|
|
|
|
# ── STEP 3: Update systemd service files referencing old paths ──
|
|
echo ""
|
|
echo -e " ${C_BOLD}Checking systemd services...${C_RESET}"
|
|
local svc_fixed=0
|
|
for svc_file in /etc/systemd/system/*.service /etc/systemd/system/emulator.service.d/*.conf; do
|
|
[ -f "$svc_file" ] || continue
|
|
local needs_update=false
|
|
for old_dir in "${old_dirs[@]}"; do
|
|
grep -q "$old_dir" "$svc_file" 2>/dev/null && needs_update=true && break
|
|
done
|
|
if [ "$needs_update" = true ]; then
|
|
local bak="$svc_file.bak-nitro-cleanup-$(date +%s)"
|
|
$root_cmd cp "$svc_file" "$bak" 2>/dev/null || true
|
|
for old_dir in "${old_dirs[@]}"; do
|
|
local old_name=$(basename "$old_dir")
|
|
local new_target=""
|
|
case "$old_name" in
|
|
Nitro-V3|Nitro-V3-*) new_target="$current_client" ;;
|
|
Nitro_Render_V3|Nitro_Render) new_target="$current_renderer" ;;
|
|
esac
|
|
[ -z "$new_target" ] && continue
|
|
$root_cmd sed -i "s|$old_dir|$new_target|g" "$svc_file" 2>/dev/null && svc_fixed=$((svc_fixed + 1))
|
|
done
|
|
echo -e " ${C_GREEN}${E_ARROW}${C_RESET} Updated: $(basename "$svc_file")"
|
|
fi
|
|
done
|
|
[ "$svc_fixed" -gt 0 ] && $root_cmd systemctl daemon-reload 2>/dev/null
|
|
[ "$svc_fixed" -gt 0 ] && ok "Updated %s service reference(s)" "$svc_fixed" || ok "No systemd services to update"
|
|
|
|
# ── STEP 4: Check for any other file references ──
|
|
echo ""
|
|
echo -e " ${C_BOLD}Checking other file references...${C_RESET}"
|
|
local other_refs=0
|
|
for old_dir in "${old_dirs[@]}"; do
|
|
local old_name=$(basename "$old_dir")
|
|
local refs=$(grep -rl "$old_name" /var/www/atom-nexst/*.sh /var/www/atom-nexst/.env 2>/dev/null | grep -v "update-Nitrov3.sh" | wc -l)
|
|
other_refs=$((other_refs + refs))
|
|
if [ "$refs" -gt 0 ]; then
|
|
echo -e " ${C_YELLOW}${E_WARN}${C_RESET} $refs file(s) still reference ${C_BOLD}$old_name${C_RESET}"
|
|
fi
|
|
done
|
|
[ "$other_refs" -gt 0 ] && warn "Some files still reference old names — check manually" || ok "No other references found"
|
|
|
|
# ── STEP 5: Remove old directories ──
|
|
echo ""
|
|
echo -e " ${C_BOLD}Summary:${C_RESET}"
|
|
echo -e " Symlinks updated: ${C_GREEN}$symlinks_fixed${C_RESET}"
|
|
echo -e " Nginx refs fixed: ${C_GREEN}$nginx_fixed${C_RESET}"
|
|
echo -e " Service refs fixed: ${C_GREEN}$svc_fixed${C_RESET}"
|
|
echo -e " Other refs: ${C_YELLOW}$other_refs${C_RESET}"
|
|
echo ""
|
|
|
|
if confirm "Now remove the old directories?"; then
|
|
for d in "${old_dirs[@]}"; do
|
|
local name=$(basename "$d")
|
|
spinner_start "Removing %s..." "$name"
|
|
if rm -rf "$d" 2>/dev/null; then
|
|
spinner_stop ok
|
|
ok "Removed %s" "$name"
|
|
else
|
|
spinner_stop fail
|
|
warn "Failed to remove %s" "$name"
|
|
fi
|
|
done
|
|
ok "Old Nitro cleanup completed"
|
|
else
|
|
info "Skipped — old directories kept"
|
|
fi
|
|
fi
|
|
|
|
# Also clean up old /tmp nitro files
|
|
local tmp_count=$(find /tmp -maxdepth 1 -name 'nitro-*' -o -name 'epicnext-*' 2>/dev/null | wc -l)
|
|
if [ "$tmp_count" -gt 0 ]; then
|
|
echo ""
|
|
echo -e " ${C_YELLOW}Found $tmp_count old temp file(s) in /tmp${C_RESET}"
|
|
if confirm "Clean /tmp nitro/epicnext files?"; then
|
|
rm -rf /tmp/nitro-* /tmp/epicnext-* 2>/dev/null || true
|
|
ok "Temp files cleaned"
|
|
fi
|
|
fi
|
|
|
|
# Clean up old log files (older than 30 days)
|
|
local old_logs=0
|
|
if [ -d "$LOG_DIR" ]; then
|
|
old_logs=$(find "$LOG_DIR" -name 'update-*.log' -mtime +30 2>/dev/null | wc -l)
|
|
if [ "$old_logs" -gt 0 ]; then
|
|
echo ""
|
|
echo -e " ${C_YELLOW}Found $old_logs old log file(s) (>30 days)${C_RESET}"
|
|
if confirm "Clean old logs?"; then
|
|
find "$LOG_DIR" -name 'update-*.log' -mtime +30 -exec rm -f {} \; 2>/dev/null || true
|
|
ok "Old logs cleaned"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Clean up old config backups (keep last 5)
|
|
if [ -d "$CONFIG_BACKUP_DIR" ]; then
|
|
local backup_count=$(find "$CONFIG_BACKUP_DIR" -maxdepth 1 -type d ! -name 'rollback' ! -name 'config-backups' 2>/dev/null | wc -l)
|
|
if [ "$backup_count" -gt 5 ]; then
|
|
local to_remove=$((backup_count - 5))
|
|
echo ""
|
|
echo -e " ${C_YELLOW}Found $backup_count config backups (keeping 5, removing $to_remove)${C_RESET}"
|
|
if confirm "Prune old config backups?"; then
|
|
find "$CONFIG_BACKUP_DIR" -maxdepth 1 -type d ! -name 'rollback' ! -name 'config-backups' -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +6 | sed 's/^[0-9.]* //' | xargs -r rm -rf || true
|
|
ok "Old config backups pruned"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Clean up old SQL backups (keep last 5)
|
|
if [ -d "$BACKUP_DIR" ]; then
|
|
local sql_count=$(find "$BACKUP_DIR" -maxdepth 1 -name '*.sql' 2>/dev/null | wc -l)
|
|
if [ "$sql_count" -gt 5 ]; then
|
|
local to_remove=$((sql_count - 5))
|
|
echo ""
|
|
echo -e " ${C_YELLOW}Found $sql_count SQL backups (keeping 5, removing $to_remove)${C_RESET}"
|
|
if confirm "Prune old SQL backups?"; 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
|
|
ok "Old SQL backups pruned"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
ok "Cleanup check completed"
|
|
}
|
|
|
|
# =============================================================================
|
|
# AUTO-SETUP (clone missing repos)
|
|
# =============================================================================
|
|
auto_setup_missing_repos() {
|
|
local cloned=false
|
|
local needs_link=false
|
|
|
|
if [ ! -d "$NITRO_CLIENT" ] || [ ! -d "$NITRO_CLIENT/.git" ]; then
|
|
echo ""
|
|
echo -e " ${C_YELLOW}${E_WARN}${C_RESET} ${C_BOLD}Octane client not found at $NITRO_CLIENT${C_RESET}"
|
|
info "Cloning Octane client from %s..." "$GIT_REPO_CLIENT"
|
|
spinner_start "Cloning Octane client..."
|
|
if git clone --depth 1 "$GIT_REPO_CLIENT" "$NITRO_CLIENT" >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
ok "Octane client cloned successfully"
|
|
cloned=true; needs_link=true
|
|
else
|
|
spinner_stop fail
|
|
die "Failed to clone Octane client from %s" "$GIT_REPO_CLIENT"
|
|
fi
|
|
fi
|
|
|
|
if [ ! -d "$NITRO_RENDERER" ] || [ ! -d "$NITRO_RENDERER/.git" ]; then
|
|
echo ""
|
|
echo -e " ${C_YELLOW}${E_WARN}${C_RESET} ${C_BOLD}Octane renderer not found at $NITRO_RENDERER${C_RESET}"
|
|
info "Cloning Octane renderer from %s..." "$GIT_REPO_RENDERER"
|
|
spinner_start "Cloning Octane renderer..."
|
|
if git clone --depth 1 "$GIT_REPO_RENDERER" "$NITRO_RENDERER" >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
ok "Octane renderer cloned successfully"
|
|
cloned=true; needs_link=true
|
|
else
|
|
spinner_stop fail
|
|
die "Failed to clone Octane renderer from %s" "$GIT_REPO_RENDERER"
|
|
fi
|
|
fi
|
|
|
|
# Also detect when either repo exists but was never linked
|
|
if [ "$needs_link" = false ]; then
|
|
if [ -d "$NITRO_RENDERER" ] && [ -d "$NITRO_RENDERER/.git" ]; then
|
|
# Check if yarn global link is registered — if not, we need to re-link
|
|
if [ ! -d "$(yarn global dir 2>/dev/null)/node_modules/@nitrots/nitro-renderer" ] 2>/dev/null && \
|
|
[ ! -L "$(yarn global dir 2>/dev/null)/node_modules/@nitrots/nitro-renderer" ] 2>/dev/null; then
|
|
needs_link=true
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ "$cloned" = true ] || [ "$needs_link" = true ]; then
|
|
echo ""
|
|
info "Installing dependencies..."
|
|
|
|
# Step 1: Install renderer deps
|
|
if [ -d "$NITRO_RENDERER" ] && [ -f "$NITRO_RENDERER/package.json" ]; then
|
|
spinner_start "Installing renderer deps..."
|
|
yarn_install_repo "$NITRO_RENDERER" --prefer-offline >> "$LOG_FILE" 2>&1 || \
|
|
yarn_install_repo "$NITRO_RENDERER" >> "$LOG_FILE" 2>&1 || warn "Renderer yarn install failed"
|
|
spinner_stop ok
|
|
ok "Renderer dependencies installed"
|
|
|
|
# Step 2: Register renderer as a global yarn link
|
|
spinner_start "Linking renderer package..."
|
|
cd "$NITRO_RENDERER"
|
|
yarn link >> "$LOG_FILE" 2>&1 || warn "yarn link in renderer failed (may already be linked)"
|
|
cd "$SCRIPT_DIR"
|
|
spinner_stop ok
|
|
ok "Renderer linked globally"
|
|
fi
|
|
|
|
# Step 3: Install client deps
|
|
if [ -d "$NITRO_CLIENT" ] && [ -f "$NITRO_CLIENT/package.json" ]; then
|
|
spinner_start "Installing client deps..."
|
|
yarn_install_repo "$NITRO_CLIENT" --prefer-offline >> "$LOG_FILE" 2>&1 || \
|
|
yarn_install_repo "$NITRO_CLIENT" >> "$LOG_FILE" 2>&1 || warn "Client yarn install failed"
|
|
spinner_stop ok
|
|
ok "Client dependencies installed"
|
|
|
|
# Step 4: Link renderer into client
|
|
spinner_start "Linking renderer to client..."
|
|
cd "$NITRO_CLIENT"
|
|
yarn link "@nitrots/nitro-renderer" >> "$LOG_FILE" 2>&1 || warn "yarn link to renderer failed (may already be linked)"
|
|
cd "$SCRIPT_DIR"
|
|
spinner_stop ok
|
|
ok "Renderer linked to client"
|
|
fi
|
|
|
|
# Step 5: Setup config files from examples
|
|
echo ""
|
|
info "Setting up configuration files..."
|
|
_setup_config_files
|
|
|
|
# Step 6: Create .nitro-build.json for JSONC mode
|
|
if [ ! -f "$NITRO_CLIENT/.nitro-build.json" ]; then
|
|
echo '{"jsonMode":"jsonc"}' > "$NITRO_CLIENT/.nitro-build.json"
|
|
ok "Created .nitro-build.json (jsonc mode)"
|
|
fi
|
|
|
|
# Step 7: Build client frontend
|
|
if [ -d "$NITRO_CLIENT" ] && [ -f "$NITRO_CLIENT/package.json" ]; then
|
|
spinner_start "Building client frontend..."
|
|
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
ok "Client frontend built"
|
|
else
|
|
spinner_stop fail
|
|
warn "Client build failed — run update to retry"
|
|
fi
|
|
fi
|
|
|
|
mkdir -p "$NITRO_SRC_DIR" 2>/dev/null || true
|
|
mkdir -p "$NITRO_CLIENT/dist/custom-themes" 2>/dev/null || true
|
|
[ ! -f "$NITRO_CLIENT/dist/custom-themes/index.json" ] && echo '{"themes":[]}' > "$NITRO_CLIENT/dist/custom-themes/index.json"
|
|
fix_perms
|
|
echo ""
|
|
ok "Auto-setup completed — all repos ready"
|
|
else
|
|
ok "All repos already present"
|
|
fi
|
|
}
|
|
|
|
# Copy example config files to their runtime names, handling renamed files
|
|
# in the Octane repo (renderer-config..jsonc.example.json etc.)
|
|
_setup_config_files() {
|
|
local src_dir="$NITRO_SRC_DIR"
|
|
[ ! -d "$src_dir" ] && mkdir -p "$src_dir" 2>/dev/null
|
|
local copied=0
|
|
|
|
# Standard *.example files (ui-config, client-mode, etc.)
|
|
for ef in "$src_dir"/*.example; do
|
|
[ -f "$ef" ] || continue
|
|
local b=$(basename "$ef"); local tn="${b%.example}"
|
|
case "$tn" in *.*) ;; *) tn="$tn.json" ;; esac
|
|
if [ ! -f "$src_dir/$tn" ]; then
|
|
cp "$ef" "$src_dir/$tn" 2>/dev/null && copied=$((copied + 1))
|
|
fi
|
|
done
|
|
|
|
# Handle renamed renderer config files
|
|
# Repo has: renderer-config..jsonc.example.json / renderer-config.json.example.json
|
|
# Runtime needs: renderer-config.jsonc (JSONC) or renderer-config.json
|
|
local rc_jsonc="$src_dir/renderer-config..jsonc.example.json"
|
|
local rc_json="$src_dir/renderer-config.json.example.json"
|
|
if [ -f "$rc_jsonc" ] && [ ! -f "$src_dir/renderer-config.jsonc" ]; then
|
|
cp "$rc_jsonc" "$src_dir/renderer-config.jsonc" 2>/dev/null && copied=$((copied + 1))
|
|
fi
|
|
if [ -f "$rc_json" ] && [ ! -f "$src_dir/renderer-config.json" ]; then
|
|
cp "$rc_json" "$src_dir/renderer-config.json" 2>/dev/null && copied=$((copied + 1))
|
|
fi
|
|
|
|
# Copy to gamedata config dir too
|
|
if [ -d "$GAMEDATA_CONF_DIR" ]; then
|
|
for f in "$src_dir"/*.json "$src_dir"/*.jsonc; do
|
|
[ -f "$f" ] || continue
|
|
local fn=$(basename "$f")
|
|
# Skip example/source files
|
|
case "$fn" in *.example*|*.example) continue ;; esac
|
|
[ ! -f "$GAMEDATA_CONF_DIR/$fn" ] && cp "$f" "$GAMEDATA_CONF_DIR/$fn" 2>/dev/null || true
|
|
done
|
|
fi
|
|
|
|
# Copy to dist/configuration if it exists
|
|
local dcd="$NITRO_CLIENT/dist/configuration"
|
|
if [ -d "$dcd" ]; then
|
|
for f in "$src_dir"/*.json "$src_dir"/*.jsonc; do
|
|
[ -f "$f" ] || continue
|
|
local fn=$(basename "$f")
|
|
case "$fn" in *.example*|*.example) continue ;; esac
|
|
cp "$f" "$dcd/$fn" 2>/dev/null || true
|
|
done
|
|
fi
|
|
|
|
ok "%s config file(s) copied" "$copied"
|
|
}
|
|
|
|
# =============================================================================
|
|
# PRE-FLIGHT CHECKS
|
|
# =============================================================================
|
|
preflight_check() {
|
|
local fatal=false
|
|
|
|
for cmd in git mvn node python3 yarn; do
|
|
command -v "$cmd" &>/dev/null || { fail "Missing: $cmd"; fatal=true; }
|
|
done
|
|
command -v "$MYSQL_CLIENT" &>/dev/null || { fail "Missing: $MYSQL_CLIENT"; fatal=true; }
|
|
command -v "$MYSQL_DUMP" &>/dev/null || { fail "Missing: $MYSQL_DUMP"; fatal=true; }
|
|
$fatal && die "Required commands missing"
|
|
|
|
ok "All commands available"
|
|
|
|
# Auto-clone missing repos instead of dying
|
|
local missing_repos=false
|
|
for d in "$NITRO_RENDERER" "$NITRO_CLIENT"; do
|
|
[ -d "$d" ] && [ -d "$d/.git" ] || missing_repos=true
|
|
done
|
|
if [ "$missing_repos" = true ]; then
|
|
info "Missing repos detected — running auto-setup..."
|
|
auto_setup_missing_repos
|
|
fi
|
|
|
|
for d in "$EMULATOR_REPO" "$EMULATOR_MODULE" "$NITRO_RENDERER" "$NITRO_CLIENT" "$NITRO_SRC_DIR"; do
|
|
[ -d "$d" ] || die "Missing dir: $d"
|
|
done
|
|
ok "All directories exist"
|
|
|
|
local total_kb=$(df --output=avail "$EMULATOR_DIR" 2>/dev/null | tail -1)
|
|
local total_gb=$((total_kb / 1024 / 1024))
|
|
[ "$total_gb" -lt "$MIN_DISK_GB" ] && die "Only ${total_gb}GB free (min ${MIN_DISK_GB}GB)"
|
|
if [ "$total_gb" -lt $((MIN_DISK_GB * 2)) ]; then
|
|
warn "Low disk: %sGB free" "$total_gb"
|
|
else
|
|
ok "%sGB disk available" "$total_gb"
|
|
fi
|
|
|
|
$MYSQL_CLIENT $MYSQL_CRED -e "SELECT 1" "$DB_NAME" &>/dev/null || die "DB connection failed"
|
|
ok "Database connected"
|
|
}
|
|
|
|
# =============================================================================
|
|
# UPDATE FUNCTIONS
|
|
# =============================================================================
|
|
update_emulator() {
|
|
step 2 8 "Update & Build Emulator"
|
|
local grc=1
|
|
if git_update "$EMULATOR_REPO" "$NITRO_BRANCH"; then
|
|
grc=0
|
|
else
|
|
grc=$?
|
|
fi
|
|
if [ $grc -eq 2 ]; then
|
|
die "Emulator update failed (git error)"
|
|
elif [ $grc -eq 0 ]; then
|
|
HAD_UPDATES=true; UPDATED_REPOS+=("Emulator")
|
|
cd "$EMULATOR_MODULE"
|
|
backup_binaries
|
|
ROLLBACK_NEEDED=true
|
|
spinner_start "Building emulator (parallel)..."
|
|
# Prefer offline mode when the local Maven repo is warm; fall back to
|
|
# online if offline resolution fails. ccache speeds up repeated JNI/C++
|
|
# native compiles if present.
|
|
command -v ccache >/dev/null 2>&1 && export CC="ccache gcc" CXX="ccache g++"
|
|
if mvn package -q -T 1C -o -Dmaven.test.skip=true >> "$LOG_FILE" 2>&1 || \
|
|
mvn package -q -T 1C -Dmaven.test.skip=true >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
else
|
|
spinner_stop fail
|
|
tail -30 "$LOG_FILE" | grep -E '(ERROR|FAILURE|BUILD)' || true
|
|
git_revert_repo "$EMULATOR_REPO"
|
|
die "Maven build failed (see: %s)" "$LOG_FILE"
|
|
fi
|
|
local jar=$(find target -maxdepth 1 -name 'Polaris-*-jar-with-dependencies.jar' -printf '%T@ %p\n' 2>/dev/null | sort -rn | sed -n '1s/^[0-9.]* //p' | xargs -r basename 2>/dev/null || echo "")
|
|
[ -z "$jar" ] && die "No jar found"
|
|
ok "Jar: %s" "$jar"
|
|
cd target/
|
|
cat > emulator << EOJ
|
|
#!/bin/sh
|
|
file_name_emulator=emulator.log
|
|
current_time=\$(date "+%H%M_%d-%m-%Y")
|
|
file_name=\$file_name_emulator.\$current_time
|
|
mv /var/log/emu/emulator.log /var/log/emu/\$file_name
|
|
java -Dfile.encoding=UTF8 -Xmx4G -jar $jar
|
|
EOJ
|
|
chmod +x emulator; ok "Launch script updated"
|
|
cd "$SCRIPT_DIR"
|
|
else
|
|
info "Emulator: already up to date"
|
|
fi
|
|
}
|
|
|
|
update_renderer() {
|
|
step 3 8 "Update Octane-Renderer"
|
|
local grc=1
|
|
if git_update "$NITRO_RENDERER" "$NITRO_BRANCH"; then
|
|
grc=0
|
|
else
|
|
grc=$?
|
|
fi
|
|
if [ $grc -eq 2 ]; then
|
|
# Let the caller fail the parallel group; the state file records it.
|
|
warn "Renderer update failed (git error)"
|
|
return 1
|
|
elif [ $grc -eq 0 ]; then
|
|
HAD_UPDATES=true; UPDATED_REPOS+=("Octane-Renderer")
|
|
cd "$NITRO_RENDERER"
|
|
spinner_start "Installing deps..."
|
|
if ! yarn_install_repo "$NITRO_RENDERER" --frozen-lockfile --prefer-offline; then
|
|
warn "Renderer: yarn install failed — cleaning node_modules and retrying"
|
|
yarn_reset_repo "$NITRO_RENDERER"
|
|
yarn_install_repo "$NITRO_RENDERER" || { spinner_stop fail; git_revert_repo "$NITRO_RENDERER"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
|
|
fi
|
|
cd "$NITRO_RENDERER"
|
|
if [ ! -d node_modules/pixi.js ]; then
|
|
warn "Renderer: node_modules incomplete — resetting repo cache and doing full install"
|
|
# Parallel yarn installs can corrupt a shared cache; resetting the
|
|
# repo's own cache folder guarantees a clean fetch from the registry.
|
|
yarn_reset_repo "$NITRO_RENDERER"
|
|
yarn_install_repo "$NITRO_RENDERER" || { spinner_stop fail; git_revert_repo "$NITRO_RENDERER"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
|
|
fi
|
|
spinner_stop ok; ok "Renderer dependencies installed"
|
|
# Ensure renderer is globally linked
|
|
cd "$NITRO_RENDERER"
|
|
yarn link >> "$LOG_FILE" 2>&1 || true
|
|
cd "$SCRIPT_DIR"
|
|
else
|
|
info "Renderer: already up to date"
|
|
fi
|
|
fix_perms
|
|
return 0
|
|
}
|
|
|
|
update_client() {
|
|
step 4 8 "Update & Build Octane"
|
|
local grc=1
|
|
if git_update "$NITRO_CLIENT" "$NITRO_BRANCH"; then
|
|
grc=0
|
|
else
|
|
grc=$?
|
|
fi
|
|
if [ $grc -eq 2 ]; then
|
|
warn "Octane update failed (git error)"
|
|
return 1
|
|
elif [ $grc -eq 0 ]; then
|
|
HAD_UPDATES=true; NITRO_BUILT=true; UPDATED_REPOS+=("Octane")
|
|
cd "$NITRO_CLIENT"
|
|
backup_binaries
|
|
ROLLBACK_NEEDED=true
|
|
spinner_start "Installing deps..."
|
|
# yarn 1 can "succeed" while leaving node_modules incomplete, so verify
|
|
# that the vite build binary actually exists before attempting a build.
|
|
if ! yarn_install_repo "$NITRO_CLIENT" --frozen-lockfile --prefer-offline; then
|
|
warn "Octane: yarn install failed — cleaning node_modules and retrying"
|
|
yarn_reset_repo "$NITRO_CLIENT"
|
|
yarn_install_repo "$NITRO_CLIENT" || { spinner_stop fail; git_revert_repo "$NITRO_CLIENT"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
|
|
fi
|
|
cd "$NITRO_CLIENT"
|
|
if [ ! -x node_modules/.bin/vite ]; then
|
|
warn "Octane: vite missing after install — resetting repo cache and doing full install"
|
|
# Parallel yarn installs can corrupt a shared cache; resetting the
|
|
# repo's own cache folder guarantees a clean fetch from the registry.
|
|
yarn_reset_repo "$NITRO_CLIENT"
|
|
yarn_install_repo "$NITRO_CLIENT" || { spinner_stop fail; git_revert_repo "$NITRO_CLIENT"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
|
|
cd "$NITRO_CLIENT"
|
|
fi
|
|
spinner_stop ok
|
|
# Ensure renderer is linked into client
|
|
cd "$NITRO_CLIENT"
|
|
yarn link "@nitrots/nitro-renderer" >> "$LOG_FILE" 2>&1 || true
|
|
cd "$SCRIPT_DIR"
|
|
spinner_start "Building frontend..."
|
|
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
else
|
|
spinner_stop fail
|
|
tail -30 "$LOG_FILE" || true
|
|
git_revert_repo "$NITRO_CLIENT"
|
|
die "yarn build failed (see: %s)" "$LOG_FILE"
|
|
fi
|
|
else
|
|
info "Octane: already up to date"
|
|
fi
|
|
mkdir -p "$NITRO_CLIENT/dist/custom-themes"
|
|
[ ! -f "$NITRO_CLIENT/dist/custom-themes/index.json" ] && echo '{"themes":[]}' > "$NITRO_CLIENT/dist/custom-themes/index.json"
|
|
fix_perms
|
|
return 0
|
|
}
|
|
|
|
sync_configs() {
|
|
step 5 8 "Sync Configurations"
|
|
mkdir -p "$GAMEDATA_CONF_DIR"
|
|
|
|
# Backup current configs before overwriting
|
|
local cb=$(backup_configs)
|
|
info "Config back-up: %s" "$(basename "$cb")"
|
|
|
|
local ms="$SCRIPT_DIR/scripts/merge-config.cjs"
|
|
local dcd="$NITRO_CLIENT/dist/configuration"
|
|
for d in "$NITRO_SRC_DIR" "$GAMEDATA_CONF_DIR"; do
|
|
[ -d "$d" ] && [ ! -w "$d" ] && sudo chown -R "$(whoami)":"$(whoami)" "$d" 2>/dev/null || true
|
|
done
|
|
local ec=0
|
|
# Standard *.example files (ui-config, client-mode, etc.)
|
|
for ef in "$NITRO_SRC_DIR"/*.example; do
|
|
[ -f "$ef" ] || continue
|
|
local b=$(basename "$ef"); local tn="${b%.example}"
|
|
case "$tn" in *.*) ;; *) tn="$tn.json" ;; esac
|
|
node "$ms" "$ef" "$NITRO_SRC_DIR/$tn" 2>/dev/null
|
|
node "$ms" "$ef" "$GAMEDATA_CONF_DIR/$tn" 2>/dev/null
|
|
[ -d "$dcd" ] && cp "$NITRO_SRC_DIR/$tn" "$dcd/$tn" 2>/dev/null || true
|
|
ec=$((ec+1))
|
|
done
|
|
# Repo ships renamed examples that don't match the standard *.example glob:
|
|
# renderer-config.json.example.json -> renderer-config.json
|
|
# renderer-config..jsonc.example.json -> renderer-config.jsonc
|
|
# (the ".." in that second name is a quirk of the upstream repo). Map them
|
|
# explicitly so we never emit garbage like renderer-config.jsonc.json.
|
|
local -A rc_map=(
|
|
["renderer-config.json.example.json"]="renderer-config.json"
|
|
["renderer-config..jsonc.example.json"]="renderer-config.jsonc"
|
|
)
|
|
for ex in "${!rc_map[@]}"; do
|
|
local src="$NITRO_SRC_DIR/$ex"
|
|
[ -f "$src" ] || continue
|
|
local tgt="${rc_map[$ex]}"
|
|
node "$ms" "$src" "$NITRO_SRC_DIR/$tgt" 2>/dev/null || true
|
|
node "$ms" "$src" "$GAMEDATA_CONF_DIR/$tgt" 2>/dev/null || true
|
|
[ -d "$dcd" ] && cp "$NITRO_SRC_DIR/$tgt" "$dcd/$tgt" 2>/dev/null || true
|
|
ec=$((ec+1))
|
|
done
|
|
ok "%s config file(s) synced" "$ec"
|
|
|
|
# Validate the generated configs
|
|
validate_configs "$NITRO_SRC_DIR"
|
|
|
|
# Remove legacy .json5 leftovers — the client only supports JSON/JSONC
|
|
for d in "$NITRO_SRC_DIR" "$dcd" "$GAMEDATA_CONF_DIR"; do
|
|
[ -d "$d" ] && find "$d" -maxdepth 1 -name '*.json5' -type f -delete 2>/dev/null || true
|
|
done
|
|
ok "Legacy .json5 files removed"
|
|
|
|
# Pre-flight: warn if NITRO_IMAGE_LIBRARY_URL looks wrong before applying configs
|
|
if [ -n "$NITRO_IMAGE_LIBRARY_URL" ]; then
|
|
case "$NITRO_IMAGE_LIBRARY_URL" in
|
|
*://*/nitro-client*|*://*/nitro-client/)
|
|
fail "NITRO_IMAGE_LIBRARY_URL points to the Nitro client path instead of /gamedata/c_images/ — images will 404"
|
|
;;
|
|
esac
|
|
case "$NITRO_IMAGE_LIBRARY_URL" in
|
|
*/c_images/) ;;
|
|
*://*) warn "NITRO_IMAGE_LIBRARY_URL does not contain /c_images/ — verify it points to the c_images directory" ;;
|
|
esac
|
|
fi
|
|
|
|
info "Applying critical config URLs..."
|
|
# Use the node-based sync so JSONC configs (which contain // comments and
|
|
# URLs like https://) are parsed and rewritten safely. The previous Python
|
|
# json.load choked on the comments and silently skipped the URL injection,
|
|
# leaving the example.com placeholders in place.
|
|
NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$dcd" NITRO_GAMEDATA_CONF_DIR="$GAMEDATA_CONF_DIR" \
|
|
NITRO_IMAGE_LIBRARY_URL="$NITRO_IMAGE_LIBRARY_URL" NITRO_HOF_FURNITURE_URL="$NITRO_HOF_FURNITURE_URL" \
|
|
NITRO_API_URL="$NITRO_API_URL" NITRO_SOCKET_URL="$NITRO_SOCKET_URL" \
|
|
NITRO_GAMEDATA_URL="$NITRO_GAMEDATA_URL" NITRO_ASSET_URL="$NITRO_ASSET_URL" \
|
|
NITRO_FURNI_ASSET_ICON_URL="$NITRO_FURNI_ASSET_ICON_URL" \
|
|
node "$SCRIPT_DIR/scripts/sync-nitro-urls.cjs" 2>/dev/null && ok "All config URLs synced" || warn "Config URL sync failed"
|
|
|
|
# Flush Redis so new config is picked up immediately
|
|
if command -v redis-cli &>/dev/null; then
|
|
redis-cli FLUSHALL 2>/dev/null && ok "Redis cache flushed" || true
|
|
fi
|
|
fix_perms
|
|
}
|
|
|
|
do_cleanup() {
|
|
step 6 8 "Cleanup"
|
|
local lc=$(find "$EMULATOR_DIR" -name "*.log" -mtime +14 2>/dev/null | wc -l)
|
|
find "$EMULATOR_DIR" -name "*.log" -mtime +14 -exec rm -f {} \; 2>/dev/null || true
|
|
# Clean old config backups (keep 10 most recent)
|
|
local cb_count=$(find "$CONFIG_BACKUP_DIR" -maxdepth 1 -type d ! -name 'rollback' 2>/dev/null | wc -l)
|
|
if [ "$cb_count" -gt 10 ]; then
|
|
find "$CONFIG_BACKUP_DIR" -maxdepth 1 -type d ! -name 'rollback' -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +11 | sed 's/^[0-9.]* //' | xargs -r rm -rf || true
|
|
ok "Old config backups pruned"
|
|
fi
|
|
[ -d "$BACKUP_DIR" ] && 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
|
|
# NOTE: the per-repo yarn cache ($YARN_CACHE_DIR) is intentionally kept
|
|
# between updates — it is what makes parallel installs fast and reliable.
|
|
# It is only removed on an explicit "Clean" (menu) action.
|
|
yarn cache clean 2>/dev/null || true
|
|
rm -rf /tmp/nitro-* /tmp/epicnext-* 2>/dev/null || true
|
|
# Clean old Maven build artifacts (older than 7 days)
|
|
find "$EMULATOR_MODULE" -maxdepth 3 -name '*.jar' -mtime +7 2>/dev/null -exec rm -f {} \; || true
|
|
ok "Cleanup done (%s logs removed)" "$lc"
|
|
}
|
|
|
|
fix_perms() {
|
|
# Make build output web-servable no matter which user ran the build.
|
|
# Running as root we chown directly; otherwise use `sudo -n` (no TTY, so it
|
|
# also works from cron). Never silently swallow failures.
|
|
local runner=""
|
|
[ "$(id -u)" -eq 0 ] || runner="sudo -n"
|
|
local changed=false
|
|
for d in "$NITRO_CLIENT" "$NITRO_RENDERER" "$GAMEDATA_CONF_DIR"; do
|
|
[ -d "$d" ] || continue
|
|
if $runner chown -R www-data:www-data "$d" 2>/dev/null; then
|
|
changed=true
|
|
else
|
|
warn "Could not chown %s to www-data:www-data" "$d"
|
|
fi
|
|
done
|
|
[ "$changed" = true ] && ok "Permissions set"
|
|
}
|
|
|
|
do_permissions() {
|
|
step 7 8 "Set Permissions"
|
|
fix_perms
|
|
}
|
|
|
|
# Wait for the emulator service to be active and its game port to be listening.
|
|
wait_for_emulator_health() {
|
|
local hc=0
|
|
while [ "$hc" -lt "$HEALTH_RETRIES" ]; do
|
|
if service_active "$EMULATOR_SERVICE" && emulator_port_open; then
|
|
ok "Emulator health check passed (service up + port listening)"
|
|
return 0
|
|
fi
|
|
hc=$((hc+1))
|
|
[ "$hc" -lt "$HEALTH_RETRIES" ] && sleep "$HEALTH_INTERVAL"
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Resolve the newest built emulator jar filename inside the target directory.
|
|
emulator_jar_name() {
|
|
find "$EMULATOR_MODULE/target" -maxdepth 1 -name 'Polaris-*-jar-with-dependencies.jar' \
|
|
-printf '%T@ %p\n' 2>/dev/null | sort -rn | sed -n '1s/^[0-9.]* //p' | xargs -r basename 2>/dev/null
|
|
}
|
|
|
|
# Run the emulator's built-in Flyway repair against the database. This realigns
|
|
# flyway_schema_history with the packaged migrations (e.g. clearing "applied
|
|
# migration not resolved locally" drift) without touching hotel data.
|
|
flyaway_repair_db() {
|
|
local jar; jar=$(emulator_jar_name)
|
|
if [ -z "$jar" ]; then
|
|
fail "No emulator jar found in %s" "$EMULATOR_MODULE/target"
|
|
return 1
|
|
fi
|
|
command -v java >/dev/null 2>&1 || { fail "Missing: java"; return 1; }
|
|
cd "$EMULATOR_MODULE/target"
|
|
spinner_start "Running Flyway repair..."
|
|
if timeout 300 java -Dfile.encoding=UTF8 -jar "$jar" --migrations=repair >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
ok "Flyway schema history repaired (see: %s)" "$LOG_FILE"
|
|
cd "$SCRIPT_DIR"
|
|
return 0
|
|
fi
|
|
spinner_stop fail
|
|
tail -30 "$LOG_FILE" | grep -iE 'repair|migrat|error|exception' || true
|
|
cd "$SCRIPT_DIR"
|
|
return 1
|
|
}
|
|
|
|
# After a build/rebuild, ensure nginx redirects /gamedata/ (and /gamedata)
|
|
# to /gamedata/config/ instead of serving the raw directory listing.
|
|
# Idempotent: injects exact-match location blocks in front of the /gamedata/
|
|
# prefix block, validates with `nginx -t` (restoring the pre-edit copy on
|
|
# failure) and keeps a timestamped backup. The actual reload happens at the
|
|
# end of do_restart.
|
|
ensure_gamedata_redirect() {
|
|
local root_cmd=""
|
|
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
|
|
local changed=false
|
|
local conf tmp snippet bak
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
if grep -qs "location = /gamedata" "$conf" 2>/dev/null; then
|
|
ok "Gamedata redirect already present in %s" "$(basename "$conf")"
|
|
continue
|
|
fi
|
|
tmp=$(mktemp)
|
|
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
|
|
snippet=" # Redirect /gamedata/ to /gamedata/config/
|
|
location = /gamedata { return 301 /gamedata/config/; }
|
|
location = /gamedata/ { return 301 /gamedata/config/; }
|
|
"
|
|
awk -v s="$snippet" '
|
|
/^[[:space:]]*location \/gamedata\/ \{/ && !done {
|
|
printf "%s", s
|
|
done = 1
|
|
}
|
|
{ print }
|
|
' "$conf" > "$tmp" 2>/dev/null
|
|
if [ ! -s "$tmp" ] || ! grep -qs "location = /gamedata" "$tmp" 2>/dev/null; then
|
|
rm -f "$tmp" 2>/dev/null || true
|
|
warn "Could not inject gamedata redirect into %s" "$conf"
|
|
continue
|
|
fi
|
|
bak="/var/backups/nginx-cms/$(basename "$conf").bak-$(date +%s)"
|
|
$root_cmd cp "$conf" "$bak" 2>/dev/null || { rm -f "$tmp"; warn "Cannot backup %s" "$conf"; continue; }
|
|
if ! $root_cmd install -m 640 "$tmp" "$conf" 2>/dev/null; then
|
|
rm -f "$tmp" 2>/dev/null || true
|
|
warn "Cannot update %s" "$conf"
|
|
continue
|
|
fi
|
|
rm -f "$tmp" 2>/dev/null || true
|
|
if ! sudo nginx -t >/dev/null 2>&1; then
|
|
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
|
|
warn "nginx -t failed — reverted gamedata redirect in %s" "$conf"
|
|
continue
|
|
fi
|
|
changed=true
|
|
ok "Added /gamedata/ → /gamedata/config/ redirect to %s" "$(basename "$conf")"
|
|
done
|
|
return 0
|
|
}
|
|
|
|
# Resolve the emulator's public game (WebSocket) port — the port the Nitro
|
|
# client actually connects to (socket.url -> wss://ws.<domain> -> here).
|
|
#
|
|
# NOTE: Emulator config.ini ships a stale `game.port=3000` that does NOT speak
|
|
# the game WebSocket (it is an unused legacy listener); the real game socket
|
|
# listens on 2096. We therefore do NOT trust config.ini and instead:
|
|
# 1. explicit NITRO_EMULATOR_GAME_PORT override
|
|
# 2. auto-detect a listening port of the emulator java process that is not a
|
|
# well-known non-game port (3000/3001/3002/3030/3306/5432/6379/80/443/...).
|
|
# This reliably yields 2096.
|
|
# 3. fall back to 2096 (the conventional Nitro/Polaris game port).
|
|
emulator_game_port() {
|
|
[ -n "${NITRO_EMULATOR_GAME_PORT:-}" ] && { echo "$NITRO_EMULATOR_GAME_PORT"; return; }
|
|
local pid
|
|
pid=$(pgrep -f 'jar.*[Pp]olaris' 2>/dev/null | head -1)
|
|
[ -z "$pid" ] && pid=$(pgrep -x java 2>/dev/null | head -1)
|
|
if [ -n "$pid" ] && command -v ss >/dev/null 2>&1; then
|
|
local ports
|
|
ports=$(ss -ltnp 2>/dev/null | grep -E "pid=$pid," | grep -oE ':[0-9]+ ' | tr -d ' :' | sort -n -u)
|
|
for cand in $ports; do
|
|
case "$cand" in 80|443|3000|3001|3002|3030|3306|5432|6379|8080|8081|8422|9443|9444|5000|22|53|38939) continue ;; *) echo "$cand"; return ;; esac
|
|
done
|
|
fi
|
|
echo 2096
|
|
}
|
|
|
|
# Ensure nginx terminates the game WebSocket. The client connects to
|
|
# wss://ws.<domain> (port 443) and that has to be proxied to the emulator's
|
|
# game port, otherwise the client loads but never joins the server. Idempotent:
|
|
# injects a server block (reusing the existing SSL cert), validates with
|
|
# `nginx -t`, and reloads. Derives the domain automatically so this self-heals
|
|
# on any hotel VPS.
|
|
ensure_ws_proxy() {
|
|
[ ${#NGINX_SITE_CONFS[@]} -eq 0 ] && return 0
|
|
local domain="${NITRO_DOMAIN:-}"
|
|
if [ -z "$domain" ]; then
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
domain=$(grep -oE 'server_name[^;]+;' "$conf" 2>/dev/null | head -1 \
|
|
| tr -s ' ' '\n' | grep -vE 'server_name|;|127\.0\.0\.1|172\.|10\.|192\.168|localhost|\[?' | head -1)
|
|
[ -n "$domain" ] && break
|
|
done
|
|
fi
|
|
[ -z "$domain" ] && { warn "Could not derive domain for ws proxy — skipping"; return 0; }
|
|
local ws_name="ws.${domain}"
|
|
local gport; gport=$(emulator_game_port)
|
|
local root_cmd=""
|
|
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
|
|
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
|
|
|
|
local cert="" key=""
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
cert=$(grep -m1 'ssl_certificate[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
|
|
key=$(grep -m1 'ssl_certificate_key[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
|
|
[ -n "$cert" ] && [ -n "$key" ] && break
|
|
done
|
|
[ -z "$cert" ] && cert="/etc/ssl/epicnabbo-backend.pem"
|
|
[ -z "$key" ] && key="/etc/ssl/epicnabbo-backend.key"
|
|
|
|
local conf
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
if grep -qs "$ws_name" "$conf" 2>/dev/null; then
|
|
ok "Game WebSocket proxy already present for %s" "$ws_name"
|
|
continue
|
|
fi
|
|
local tmp; tmp=$(mktemp)
|
|
local snippet
|
|
snippet="server {
|
|
listen 9443 ssl;
|
|
listen [::]:9443 ssl;
|
|
http2 on;
|
|
server_name $ws_name;
|
|
|
|
ssl_certificate $cert;
|
|
ssl_certificate_key $key;
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_prefer_server_ciphers off;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:$gport;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection \"upgrade\";
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
proxy_read_timeout 86400;
|
|
}
|
|
}
|
|
"
|
|
printf "%s\n" "$snippet" > "$tmp"
|
|
cat "$conf" >> "$tmp" 2>/dev/null
|
|
local bak="/var/backups/nginx-cms/$(basename "$conf").bak-ws-$(date +%s)"
|
|
$root_cmd cp "$conf" "$bak" 2>/dev/null || { rm -f "$tmp"; warn "Cannot backup %s" "$conf"; continue; }
|
|
if ! $root_cmd install -m 640 "$tmp" "$conf" 2>/dev/null; then
|
|
rm -f "$tmp" 2>/dev/null || true
|
|
warn "Cannot update %s" "$conf"
|
|
continue
|
|
fi
|
|
rm -f "$tmp" 2>/dev/null || true
|
|
if ! sudo nginx -t >/dev/null 2>&1; then
|
|
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
|
|
warn "nginx -t failed — reverted ws proxy in %s" "$conf"
|
|
continue
|
|
fi
|
|
ok "Added game WebSocket proxy %s -> 127.0.0.1:%s" "$ws_name" "$gport"
|
|
done
|
|
}
|
|
|
|
# Ensure the Octane client's emulator game API is reachable same-origin. The
|
|
# client calls /api/auth/* (login, server-key, sso-token, register, change-*,
|
|
# remember, refresh, ...) and /api/badges/custom via api.url (same origin =
|
|
# the CMS domain). The Polaris game server serves these on its game port (2096);
|
|
# the CMS does NOT. Proxying them here keeps api.url same-origin (no CORS) while
|
|
# still reaching the emulator. Inserted before the generic `location /api/`
|
|
# (which sends everything else to the CMS on :3002) inside the HTTPS vhost that
|
|
# listens on 9443. Idempotent: skips when the block is already present.
|
|
ensure_api_proxy() {
|
|
[ ${#NGINX_SITE_CONFS[@]} -eq 0 ] && return 0
|
|
local gport; gport=$(emulator_game_port)
|
|
local root_cmd=""
|
|
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
|
|
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
|
|
local snippet=" # --- Nitro/Emulator game API (managed by update-Nitrov3.sh: ensure_api_proxy) ---
|
|
# Octane client calls /api/auth/* and /api/badges/custom via api.url (same
|
|
# origin). The emulator serves these on its game port; proxy them so the
|
|
# client stays same-origin (no CORS). Inserted before the generic /api/.
|
|
location /api/auth/ {
|
|
proxy_pass http://127.0.0.1:${gport};
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
add_header Cache-Control \"no-cache, no-store, must-revalidate\";
|
|
}
|
|
location /api/badges/custom {
|
|
proxy_pass http://127.0.0.1:${gport};
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
add_header Cache-Control \"no-cache, no-store, must-revalidate\";
|
|
}
|
|
"
|
|
local conf
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
if grep -qs "location /api/auth/" "$conf" 2>/dev/null; then
|
|
ok "Emulator /api/auth proxy already present in %s" "$(basename "$conf")"
|
|
continue
|
|
fi
|
|
local bak="/var/backups/nginx-cms/$(basename "$conf").bak-api-$(date +%s)"
|
|
$root_cmd cp "$conf" "$bak" 2>/dev/null || { warn "Cannot backup %s" "$conf"; continue; }
|
|
# Insert the snippet before the first `location /api/ {` inside the
|
|
# server block that listens on 9443 (the HTTPS client vhost).
|
|
if ! SNIPPET="$snippet" $root_cmd perl -i -pe '
|
|
BEGIN { $https=0; $inserted=0; $snip=$ENV{SNIPPET}; }
|
|
if (/^\s*server\s*\{/) { $https=0; }
|
|
if (/^\s*listen\s+9443\b/) { $https=1; }
|
|
if ($https && !$inserted && /^\s*location\s+\/api\/\s*\{/) {
|
|
$_ = $snip . $_;
|
|
$inserted=1;
|
|
}
|
|
' "$conf" 2>/dev/null; then
|
|
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
|
|
warn "Failed to insert emulator /api proxy into %s" "$(basename "$conf")"
|
|
continue
|
|
fi
|
|
if ! sudo nginx -t >/dev/null 2>&1; then
|
|
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
|
|
warn "nginx -t failed — reverted emulator /api proxy in %s" "$(basename "$conf")"
|
|
continue
|
|
fi
|
|
ok "Added emulator /api proxy to %s" "$(basename "$conf")"
|
|
done
|
|
}
|
|
|
|
# Ensure a catch-all default_server exists on the nginx vhost port. L7 proxies
|
|
# such as Traefik health-check the upstream using the IP as the Host header,
|
|
# which matches no real vhost; without this block those checks hit the first
|
|
# server block (e.g. the ws proxy) and get a 400, marking the site unhealthy
|
|
# (HTTP 503). Answering /health with 200 keeps proxy health checks green.
|
|
ensure_default_server() {
|
|
[ ${#NGINX_SITE_CONFS[@]} -eq 0 ] && return 0
|
|
local found=false
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
if grep -qs "listen.*default_server" "$conf" 2>/dev/null; then found=true; break; fi
|
|
done
|
|
$found && { ok "Default-server health block already present"; return 0; }
|
|
local root_cmd=""
|
|
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
|
|
local cert="" key=""
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
cert=$(grep -m1 'ssl_certificate[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
|
|
key=$(grep -m1 'ssl_certificate_key[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
|
|
[ -n "$cert" ] && [ -n "$key" ] && break
|
|
done
|
|
[ -z "$cert" ] && cert="/etc/ssl/epicnabbo-backend.pem"
|
|
[ -z "$key" ] && key="/etc/ssl/epicnabbo-backend.key"
|
|
local conf
|
|
for conf in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$conf" ] || continue
|
|
local tmp; tmp=$(mktemp)
|
|
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
|
|
cat > "$tmp" <<EOF
|
|
server {
|
|
listen 9443 ssl default_server;
|
|
listen [::]:9443 ssl default_server;
|
|
server_name _;
|
|
ssl_certificate $cert;
|
|
ssl_certificate_key $key;
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_prefer_server_ciphers off;
|
|
location = /health { return 200 "OK"; }
|
|
location / { return 444; }
|
|
}
|
|
|
|
EOF
|
|
cat "$conf" >> "$tmp" 2>/dev/null
|
|
local bak="/var/backups/nginx-cms/$(basename "$conf").bak-default-$(date +%s)"
|
|
$root_cmd cp "$conf" "$bak" 2>/dev/null || { rm -f "$tmp"; warn "Cannot backup %s" "$conf"; continue; }
|
|
if ! $root_cmd install -m 640 "$tmp" "$conf" 2>/dev/null; then
|
|
rm -f "$tmp" 2>/dev/null || true
|
|
warn "Cannot update %s" "$conf"
|
|
continue
|
|
fi
|
|
rm -f "$tmp" 2>/dev/null || true
|
|
if ! sudo nginx -t >/dev/null 2>&1; then
|
|
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
|
|
warn "nginx -t failed — reverted default-server block in %s" "$conf"
|
|
continue
|
|
fi
|
|
ok "Added default-server /health block to %s" "$(basename "$conf")"
|
|
done
|
|
}
|
|
|
|
do_restart() {
|
|
step 8 8 "Restart Services"
|
|
ensure_gamedata_redirect
|
|
ensure_default_server
|
|
ensure_ws_proxy
|
|
ensure_api_proxy
|
|
if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then
|
|
sudo systemctl restart "$EMULATOR_SERVICE" 2>/dev/null && ok "$EMULATOR_SERVICE restarted" || warn "Restart failed"
|
|
if ! wait_for_emulator_health; then
|
|
warn "Emulator not healthy after %s retries" "$HEALTH_RETRIES"
|
|
sudo journalctl -u "$EMULATOR_SERVICE" --no-pager -n 20 2>/dev/null || true
|
|
# Flyway schema drift (an applied migration no longer resolvable
|
|
# locally) aborts startup. Repair the history table and retry once
|
|
# before giving up, so updates survive upstream migration renames.
|
|
local flyway_drift=false
|
|
if sudo journalctl -u "$EMULATOR_SERVICE" --no-pager -n 300 2>/dev/null \
|
|
| grep -qiE 'applied migration not resolved locally|FlywayValidateException|Migrations have failed validation'; then
|
|
flyway_drift=true
|
|
warn "Flyway schema drift detected — running repair and retrying"
|
|
# Halt the restart loop so repair cannot race migrate on the history table
|
|
sudo systemctl stop "$EMULATOR_SERVICE" 2>/dev/null || true
|
|
if flyaway_repair_db; then
|
|
sudo systemctl start "$EMULATOR_SERVICE" 2>/dev/null || true
|
|
if wait_for_emulator_health; then
|
|
ok "Emulator healthy after Flyway repair"
|
|
flyway_drift=false
|
|
fi
|
|
else
|
|
sudo systemctl start "$EMULATOR_SERVICE" 2>/dev/null || true
|
|
fi
|
|
fi
|
|
if [ "$flyway_drift" = true ]; then
|
|
ERRORS=$((ERRORS + 1))
|
|
ROLLBACK_NEEDED=true
|
|
fi
|
|
fi
|
|
elif command -v pm2 &>/dev/null; then
|
|
pm2 restart all 2>/dev/null && ok "PM2 restarted" || warn "PM2 restart failed"
|
|
else
|
|
warn "No service manager found"
|
|
fi
|
|
# Reload nginx. Prefer `nginx -s reload` (the running master picks it up
|
|
# immediately); fall back to systemctl for hosts where nginx is a unit.
|
|
if command -v nginx &>/dev/null && sudo nginx -t >/dev/null 2>&1; then
|
|
if sudo nginx -s reload >/dev/null 2>&1 || sudo systemctl reload nginx >/dev/null 2>&1; then
|
|
ok "Nginx reloaded"
|
|
else
|
|
warn "Nginx reload failed"
|
|
fi
|
|
fi
|
|
command -v redis-cli &>/dev/null && redis-cli FLUSHALL 2>/dev/null && ok "Redis flushed"
|
|
}
|
|
|
|
# Probe the emulator's configured TCP port to confirm it is actually serving,
|
|
# not merely that systemd reports the unit as active.
|
|
emulator_port_open() {
|
|
local port="${NITRO_EMULATOR_PORT:-}"
|
|
if [ -z "$port" ]; then
|
|
port=$(emulator_game_port)
|
|
fi
|
|
if command -v redis-cli >/dev/null 2>&1; then
|
|
local p; p=$(redis-cli GET nitro:emulator:port 2>/dev/null)
|
|
[ -n "$p" ] && port="$p"
|
|
fi
|
|
if command -v nc >/dev/null 2>&1; then
|
|
nc -z 127.0.0.1 "$port" -w 3 2>/dev/null && return 0
|
|
fi
|
|
if command -v ss >/dev/null 2>&1; then
|
|
ss -ltn 2>/dev/null | grep -q ":$port " && return 0
|
|
fi
|
|
timeout 3 bash -c "exec 3<>/dev/tcp/127.0.0.1/$port" 2>/dev/null && return 0
|
|
return 1
|
|
}
|
|
|
|
show_summary() {
|
|
local el=$(($(date +%s) - START_TIME)); local ef; ef=$(printf '%dm %ds' $((el/60)) $((el%60)))
|
|
echo ""
|
|
echo -e " ${C_CYAN}╔══════════════════════════════════════════════════════════════════╗${C_RESET}"
|
|
if [ "$ERRORS" -eq 0 ]; then
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}✔ $(_t "UPDATE COMPLETED SUCCESSFULLY")${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
else
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}⚠ $(_t "COMPLETED WITH") $ERRORS $(_t "ERROR(S)")${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
fi
|
|
echo -e " ${C_CYAN}╠══════════════════════════════════════════════════════════════════╣${C_RESET}"
|
|
printf " ${C_CYAN}║${C_RESET} $(_t "Duration:") %-46s${C_CYAN}║${C_RESET}\n" "$ef"
|
|
printf " ${C_CYAN}║${C_RESET} $(_t "Updates:") %-46s${C_CYAN}║${C_RESET}\n" "$([ "$HAD_UPDATES" = true ] && echo "$(_t "Yes")" || echo "$(_t "No")")"
|
|
printf " ${C_CYAN}║${C_RESET} $(_t "Octane build:") %-46s${C_CYAN}║${C_RESET}\n" "$([ "$NITRO_BUILT" = true ] && echo "$(_t "Yes")" || echo "$(_t "No")")"
|
|
printf " ${C_CYAN}║${C_RESET} $(_t "Warnings:") %-46s${C_CYAN}║${C_RESET}\n" "$WARNINGS"
|
|
[ ${#UPDATED_REPOS[@]} -gt 0 ] && printf " ${C_CYAN}║${C_RESET} $(_t "Repos:") %-46s${C_CYAN}║${C_RESET}\n" "${UPDATED_REPOS[*]}"
|
|
echo -e " ${C_CYAN}╚══════════════════════════════════════════════════════════════════╝${C_RESET}"
|
|
echo ""
|
|
notify "SUCCESS" "Completed in $ef (${WARNINGS} warnings)"
|
|
}
|
|
|
|
# =============================================================================
|
|
# FULL UPDATE
|
|
# =============================================================================
|
|
cmd_update() {
|
|
echo ""
|
|
acquire_lock
|
|
DRY_RUN=${DRY_RUN:-false}
|
|
local mode_label="${SELECTIVE_UPDATE:-full}"
|
|
[ "$DRY_RUN" = true ] && mode_label="$mode_label [DRY-RUN]"
|
|
info "Site: %s | Branch: %s | Mode: %s" "${NITRO_SITE_URL:-?}" "$NITRO_BRANCH" "$mode_label"
|
|
info "Log: %s" "$LOG_FILE"
|
|
if [ "$DRY_RUN" = true ]; then
|
|
info "Dry-run: would update repos, run DB backups, build, sync configs, cleanup, restart services"
|
|
show_summary
|
|
release_lock
|
|
return
|
|
fi
|
|
|
|
step 1 8 "System Diagnostics"
|
|
preflight_check
|
|
|
|
case "${SELECTIVE_UPDATE:-full}" in
|
|
emulator) update_emulator ;;
|
|
renderer) update_renderer ;;
|
|
client) update_client ;;
|
|
configs) sync_configs ;;
|
|
dev)
|
|
# Quick dev mode: skip builds, just sync configs and reload
|
|
info "Dev mode: skipping builds, syncing configs only"
|
|
sync_configs
|
|
do_restart
|
|
;;
|
|
*)
|
|
RUN_ROLLBACK=true
|
|
update_emulator
|
|
# Always attempt the renderer + client builds in parallel. Each
|
|
# update_* function runs git_update itself and skips when its repo
|
|
# is already up to date, so gating these on the emulator's update
|
|
# status would wrongly skip them when only client/renderer changed.
|
|
parallel_run "renderer" update_renderer
|
|
parallel_run "client" update_client
|
|
parallel_wait
|
|
if ! parallel_failed; then
|
|
warn "Parallel build(s) failed: %s — aborting before restart" "${PARALLEL_FAILED[*]}"
|
|
ROLLBACK_NEEDED=true
|
|
die "Build failure in: %s" "${PARALLEL_FAILED[*]}"
|
|
fi
|
|
sync_configs
|
|
do_cleanup
|
|
do_permissions
|
|
do_restart
|
|
RUN_ROLLBACK=false
|
|
[ -n "$BINARY_BACKUP_DIR" ] && rm -rf "$BINARY_BACKUP_DIR" 2>/dev/null || true
|
|
;;
|
|
esac
|
|
|
|
show_summary
|
|
fix_perms
|
|
release_lock
|
|
}
|
|
|
|
# =============================================================================
|
|
# BACKUP / RESTORE
|
|
# =============================================================================
|
|
cmd_backup() {
|
|
step 1 1 "Database Backup"
|
|
mkdir -p "$BACKUP_DIR"
|
|
local bf="$BACKUP_DIR/manual_$(date +%Y%m%d_%H%M%S).sql"
|
|
spinner_start "Backing up..."
|
|
if $MYSQL_DUMP $MYSQL_CRED --force --skip-lock-tables --routines --events --triggers "$DB_NAME" > "$bf" 2>/dev/null; then
|
|
local sz=$(stat -c%s "$bf" 2>/dev/null || echo 0); spinner_stop ok
|
|
ok "Backup: %s — %s" "$(format_size "$sz")" "$(basename "$bf")"
|
|
else
|
|
spinner_stop fail; die "Backup failed"
|
|
fi
|
|
}
|
|
|
|
cmd_restore() {
|
|
step 1 1 "Database Restore"
|
|
[ ! -d "$BACKUP_DIR" ] && die "No backup dir"
|
|
local backups=()
|
|
while IFS= read -r line; do backups+=("$line"); done < <(list_sorted "$BACKUP_DIR" '*.sql')
|
|
[ ${#backups[@]} -eq 0 ] && die "No backups"
|
|
echo ""
|
|
echo -e " ${C_BOLD}${C_CYAN}$(_t "Backups:")${C_RESET}"
|
|
local i=1
|
|
for bk in "${backups[@]}"; do
|
|
local sz=$(format_size "$(stat -c%s "$bk" 2>/dev/null || echo 0)")
|
|
printf " ${C_GREEN}${C_BOLD}%2d)${C_RESET} %-40s ${C_DIM}%s${C_RESET}\n" "$i" "$(basename "$bk")" "$sz"
|
|
i=$((i+1))
|
|
done
|
|
echo -en "\n $(_t "Select") [1-$((i-1))]: "
|
|
local c; read -rt 30 c
|
|
[[ "$c" =~ ^[0-9]+$ ]] && [ "$c" -ge 1 ] && [ "$c" -le ${#backups[@]} ] || die "$(_t "Invalid")"
|
|
local sel="${backups[$((c-1))]}"
|
|
if ! confirm "Restore $(basename "$sel")? OVERWRITES database!"; then info "Cancelled"; return; fi
|
|
spinner_start "Restoring..."
|
|
$MYSQL_CLIENT $MYSQL_CRED "$DB_NAME" < "$sel" 2>/dev/null && spinner_stop ok && ok "Restored" || { spinner_stop fail; die "Restore failed"; }
|
|
}
|
|
|
|
cmd_backups() {
|
|
step 1 1 "Backup Inventory"
|
|
[ ! -d "$BACKUP_DIR" ] && { warn "No backup dir"; return; }
|
|
echo ""
|
|
printf " ${C_DIM}%-4s %-38s %-10s${C_RESET}\n" "#" "FILENAME" "SIZE"
|
|
printf " ${C_DIM}%s${C_RESET}\n" "$(repeat '─' 56)"
|
|
local t=0 ts=0
|
|
while IFS= read -r l; do
|
|
t=$((t+1)); local n=$(basename "$l"); local s=$(stat -c%s "$l" 2>/dev/null || echo 0); ts=$((ts+s))
|
|
printf " ${C_GREEN}%2d${C_RESET} %-38s ${C_CYAN}%s${C_RESET}\n" "$t" "$n" "$(format_size "$s")"
|
|
done < <(list_sorted "$BACKUP_DIR" '*.sql')
|
|
printf " ${C_DIM}%s${C_RESET}\n" "$(repeat '─' 56)"
|
|
echo -e " ${C_BOLD}$(_t "Total:") $t $(_t "backups") — $(format_size "$ts")${C_RESET}\n"
|
|
}
|
|
|
|
# =============================================================================
|
|
# STATUS
|
|
# =============================================================================
|
|
cmd_status() {
|
|
step 1 1 "System Status"
|
|
echo ""
|
|
box_top "SERVER" 56; box_kv "Host:" "$(hostname)" 56
|
|
box_kv "OS:" "$(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"' || uname -s)" 56
|
|
box_kv "Uptime:" "$(uptime -p 2>/dev/null || uptime)" 56; box_bottom 56
|
|
echo ""
|
|
box_top "RESOURCES" 56
|
|
box_kv "CPU:" "$(awk '{printf "%.1f", $1}' /proc/loadavg 2>/dev/null || echo "?") / $(nproc 2>/dev/null || echo '?') cores" 56
|
|
box_kv "Memory:" "$(free -m 2>/dev/null | awk '/^Mem:/{printf "%dMB / %dMB", $3, $2}')" 56
|
|
box_kv "Disk:" "$(df -h "$SCRIPT_DIR" 2>/dev/null | tail -1 | awk '{printf "%s / %s (%s)", $3, $2, $5}')" 56
|
|
local dbs=$($MYSQL_CLIENT $MYSQL_CRED -N -e "SELECT ROUND(SUM(data_length+index_length)/1024/1024,1) FROM information_schema.tables WHERE table_schema='$DB_NAME'" "$DB_NAME" 2>/dev/null || echo "?")
|
|
local dbt=$($MYSQL_CLIENT $MYSQL_CRED -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME'" "$DB_NAME" 2>/dev/null || echo "?")
|
|
box_kv "Database:" "${dbs}MB / ${dbt} tables" 56; box_bottom 56
|
|
echo ""
|
|
box_top "SERVICES" 56
|
|
for svc in nginx mariadb redis-server "$EMULATOR_SERVICE" php-fpm cron; do
|
|
local st="${C_DIM}N/A${C_RESET}"
|
|
service_active "$svc" && st="${C_GREEN}ACTIVE${C_RESET}"
|
|
printf " ${C_CYAN}║${C_RESET} %-20s %b${C_CYAN} ║${C_RESET}\n" "$svc" "$st"
|
|
done; box_bottom 56
|
|
echo ""
|
|
box_top "GIT REPOS" 56
|
|
for re in "Emulator:$EMULATOR_REPO" "Octane:$NITRO_CLIENT" "Render:$NITRO_RENDERER"; do
|
|
local nm="${re%%:*}" pa="${re##*:}"
|
|
[ -d "$pa/.git" ] || continue
|
|
local br=$(cd "$pa" && git branch --show-current 2>/dev/null || echo "?")
|
|
local cm=$(cd "$pa" && git log --oneline -1 2>/dev/null | cut -c1-30 || echo "?")
|
|
local ah=$(cd "$pa" && git rev-list --count "origin/$(git branch --show-current 2>/dev/null)"..HEAD 2>/dev/null || echo "0")
|
|
local bh=$(cd "$pa" && git rev-list --count HEAD.."origin/$(git branch --show-current 2>/dev/null)" 2>/dev/null || echo "0")
|
|
printf " ${C_CYAN}║${C_RESET} %-12s ${C_CYAN}%-8s${C_RESET} %s" "$nm" "$br" "$cm"
|
|
[ "$ah" -gt 0 ] && printf " ${C_RED}+${ah}${C_RESET}" || printf " "
|
|
[ "$bh" -gt 0 ] && printf " ${C_YELLOW}-${bh}${C_RESET}" || printf " "
|
|
printf " ${C_CYAN}║${C_RESET}\n"
|
|
done; box_bottom 56
|
|
echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# HEALTH CHECK
|
|
# =============================================================================
|
|
cmd_health() {
|
|
step 1 1 "Health Check"
|
|
local t=0 p=0 f=0 w=0
|
|
check() { t=$((t+1)); local lbl="$1"; shift; "$@" &>/dev/null && { p=$((p+1)); ok "$(_t "$lbl")"; } || { f=$((f+1)); fail "$(_t "$lbl")"; }; }
|
|
checkw() { t=$((t+1)); local lbl="$1"; shift; "$@" &>/dev/null && { p=$((p+1)); ok "$(_t "$lbl")"; } || { w=$((w+1)); warn "$(_t "$lbl")"; }; }
|
|
echo ""
|
|
echo -e " ${C_BOLD}$(_t "Commands:")${C_RESET}"
|
|
checkw "Git" command -v git; checkw "Node.js" command -v node; checkw "Yarn" command -v yarn
|
|
checkw "Python3" command -v python3; checkw "Java" command -v java; checkw "Maven" command -v mvn
|
|
checkw "MariaDB" command -v "$MYSQL_CLIENT"
|
|
echo -e "\n ${C_BOLD}$(_t "Script:")${C_RESET}"
|
|
checkw "$(_t "Bash syntax")" bash -n "$SCRIPT_DIR/$SCRIPT_NAME"
|
|
if command -v shellcheck &>/dev/null; then checkw "ShellCheck" shellcheck --quiet "$SCRIPT_DIR/$SCRIPT_NAME"; fi
|
|
if command -v checkbashisms &>/dev/null; then checkw "$(_t "Bashisms")" checkbashisms "$SCRIPT_DIR/$SCRIPT_NAME"; fi
|
|
echo -e "\n ${C_BOLD}$(_t "Configs:")${C_RESET}"
|
|
checkw "$(_t "Config dir")" test -d "$NITRO_SRC_DIR"
|
|
local cerr=0
|
|
for cf in "$NITRO_SRC_DIR"/*.json; do
|
|
[ -f "$cf" ] || continue
|
|
case "$(basename "$cf")" in
|
|
*jsonc*|*.example*) continue ;;
|
|
esac
|
|
python3 -m json.tool "$cf" >/dev/null 2>&1 || cerr=$((cerr+1))
|
|
done
|
|
[ "$cerr" -eq 0 ] && ok "$(_t "All JSON valid")" || warn "$(_t "JSON errors:") $cerr"
|
|
echo -e "\n ${C_BOLD}$(_t "Config URLs:")${C_RESET}"
|
|
for key in NITRO_SITE_URL NITRO_API_URL NITRO_SOCKET_URL NITRO_GAMEDATA_URL NITRO_IMAGE_LIBRARY_URL; do
|
|
local val="${!key}"
|
|
if [ -z "$val" ]; then
|
|
warn "$key"
|
|
else
|
|
case "$key" in
|
|
NITRO_IMAGE_LIBRARY_URL)
|
|
case "$val" in
|
|
*/c_images/) ok "$key" ;;
|
|
*) warn "$key missing /c_images/ in path: $val" ;;
|
|
esac ;;
|
|
NITRO_SITE_URL)
|
|
case "$val" in
|
|
*/nitro-client*|*/nitro-client) warn "$key should be the site URL, not the Nitro client path" ;;
|
|
*) ok "$key" ;;
|
|
esac ;;
|
|
*) ok "$key" ;;
|
|
esac
|
|
fi
|
|
done
|
|
echo -e "\n ${C_BOLD}$(_t "Directories:")${C_RESET}"
|
|
checkw "Emulator" test -d "$EMULATOR_MODULE"; checkw "Octane" test -d "$NITRO_CLIENT"; checkw "Renderer" test -d "$NITRO_RENDERER"
|
|
checkw "$(_t "Gamedata config")" test -d "$GAMEDATA_CONF_DIR"; checkw "$(_t "Backup dir")" test -d "$BACKUP_DIR"
|
|
echo -e "\n ${C_BOLD}$(_t "Services:")${C_RESET}"
|
|
checkw "MariaDB" service_active mariadb; checkw "Nginx" service_active nginx; checkw "Redis" service_active redis-server
|
|
checkw "$EMULATOR_SERVICE" service_active "$EMULATOR_SERVICE"
|
|
echo -e "\n ${C_BOLD}$(_t "Network:")${C_RESET}"
|
|
checkw "$(_t "Nginx config")" nginx -t 2>/dev/null
|
|
if command -v redis-cli &>/dev/null; then checkw "Redis PING" redis-cli PING 2>/dev/null; fi
|
|
checkw "$(_t "Site reachable")" timeout 5 curl -sf "${NITRO_SITE_URL:-}" 2>/dev/null
|
|
if emulator_port_open; then ok "$(_t "Emulator port open")"; else warn "$(_t "Emulator port closed")"; fi
|
|
echo -e "\n ${C_BOLD}$(_t "Databases:")${C_RESET}"
|
|
checkw "$(_t "MySQL connect")" $MYSQL_CLIENT $MYSQL_CRED -e "SELECT 1" "$DB_NAME" 2>/dev/null
|
|
local dbt=$($MYSQL_CLIENT $MYSQL_CRED -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME'" "$DB_NAME" 2>/dev/null || echo "?")
|
|
t=$((t+1)); [ "$dbt" != "?" ] && { p=$((p+1)); ok "$(_t "Tables:") $dbt"; } || { f=$((f+1)); fail "$(_t "Tables:") ?"; }
|
|
echo -e "\n ${C_BOLD}$(_t "Resources:")${C_RESET}"
|
|
local ak=$(df --output=avail "$EMULATOR_DIR" 2>/dev/null | tail -1); local ag=$((ak/1024/1024))
|
|
t=$((t+1)); [ "$ag" -ge "$MIN_DISK_GB" ] && { p=$((p+1)); ok "$(_t "Disk:") ${ag}GB"; } || { f=$((f+1)); fail "$(_t "Disk:") ${ag}GB"; }
|
|
local mem=$(free -m 2>/dev/null | awk '/^Mem:/{printf "%dMB / %dMB", $3, $2}')
|
|
t=$((t+1)); ok "$(_t "Memory:") $mem"
|
|
local load=$(awk '{printf "%.1f", $1}' /proc/loadavg 2>/dev/null || echo "?")
|
|
local cores=$(nproc 2>/dev/null || echo "?")
|
|
t=$((t+1)); ok "$(_t "Load:") $load / $cores cores"
|
|
echo ""
|
|
local sc=$((p*100/t))
|
|
box_top "SCORE" 50
|
|
box_kv "Passed:" "$p / $t" 50
|
|
box_kv "Failed:" "$f" 50; box_kv "Warnings:" "$w" 50
|
|
if [ "$sc" -ge 90 ]; then box_line "${C_GREEN}${C_BOLD}${sc}% — $(_t "EXCELLENT")${C_RESET}" 50
|
|
elif [ "$sc" -ge 70 ]; then box_line "${C_YELLOW}${C_BOLD}${sc}% — $(_t "GOOD")${C_RESET}" 50
|
|
else box_line "${C_RED}${C_BOLD}${sc}% — $(_t "NEEDS ATTENTION")${C_RESET}" 50; fi
|
|
box_bottom 50; echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# FLYAWAY REPAIR
|
|
# =============================================================================
|
|
cmd_flyaway_repair() {
|
|
echo ""
|
|
acquire_lock
|
|
DRY_RUN=${DRY_RUN:-false}
|
|
info "Site: %s | Branch: %s | Mode: %s" "${NITRO_SITE_URL:-?}" "$NITRO_BRANCH" "flyaway-repair"
|
|
info "Log: %s" "$LOG_FILE"
|
|
if [ "$DRY_RUN" = true ]; then
|
|
info "Dry-run: would check repos, reinstall deps, fix permissions, sync configs, restart services"
|
|
show_summary
|
|
release_lock
|
|
return
|
|
fi
|
|
|
|
step 1 7 "System Diagnostics"
|
|
preflight_check
|
|
|
|
step 2 7 "$(_t "Flyaway Repair")"
|
|
local repaired=0
|
|
|
|
for repo in "$EMULATOR_REPO" "$NITRO_CLIENT" "$NITRO_RENDERER"; do
|
|
[ -d "$repo/.git" ] || continue
|
|
local name; name=$(basename "$repo")
|
|
info "Checking %s..." "$name"
|
|
|
|
# Reset any dirty/uncommitted changes that may cause issues
|
|
cd "$repo"
|
|
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
|
|
warn "Dirty working tree in %s — stashing changes" "$name"
|
|
git stash --include-untracked 2>/dev/null || true
|
|
fi
|
|
|
|
# Reset to clean latest of the current branch
|
|
local branch; branch=$(git branch --show-current 2>/dev/null || echo "main")
|
|
if git checkout "$branch" 2>/dev/null; then
|
|
git checkout . 2>/dev/null || true
|
|
git clean -fd 2>/dev/null || true
|
|
ok "Cleaned %s working tree" "$name"
|
|
fi
|
|
cd "$SCRIPT_DIR"
|
|
|
|
# Reinstall dependencies if package.json exists
|
|
if [ -f "$repo/package.json" ]; then
|
|
spinner_start "Installing deps for %s..." "$name"
|
|
if yarn_install_repo "$repo" --frozen-lockfile --prefer-offline 2>/dev/null; then
|
|
spinner_stop ok
|
|
ok "Deps reinstalled for %s" "$name"
|
|
else
|
|
spinner_stop fail
|
|
warn "yarn install failed for %s — resetting repo and retrying" "$name"
|
|
yarn_reset_repo "$repo"
|
|
yarn_install_repo "$repo" --frozen-lockfile --prefer-offline 2>/dev/null || warn "yarn install still failed for %s" "$name"
|
|
fi
|
|
fi
|
|
|
|
# Re-link renderer: register global link + link into client
|
|
if [ "$repo" = "$NITRO_RENDERER" ] && [ -d "$repo" ]; then
|
|
spinner_start "Re-linking renderer..."
|
|
cd "$NITRO_RENDERER"
|
|
yarn link >> "$LOG_FILE" 2>&1 || warn "yarn link in renderer failed"
|
|
cd "$SCRIPT_DIR"
|
|
cd "$NITRO_CLIENT"
|
|
yarn link "@nitrots/nitro-renderer" >> "$LOG_FILE" 2>&1 || warn "yarn link to renderer failed"
|
|
cd "$SCRIPT_DIR"
|
|
spinner_stop ok
|
|
ok "Renderer re-linked"
|
|
fi
|
|
|
|
repaired=$((repaired + 1))
|
|
done
|
|
|
|
# Ensure .nitro-build.json exists
|
|
if [ ! -f "$NITRO_CLIENT/.nitro-build.json" ]; then
|
|
echo '{"jsonMode":"jsonc"}' > "$NITRO_CLIENT/.nitro-build.json"
|
|
ok "Created .nitro-build.json (jsonc mode)"
|
|
fi
|
|
|
|
# Realign Flyway schema history with the packaged migrations
|
|
step 3 7 "$(_t "Flyway DB Repair")"
|
|
if flyaway_repair_db; then
|
|
ok "$(_t "Flyway schema history repaired")"
|
|
else
|
|
fail "$(_t "Flyway DB repair failed — the emulator will retry on restart")"
|
|
fi
|
|
|
|
# Fix permissions
|
|
step 4 7 "$(_t "Set Permissions")"
|
|
fix_perms
|
|
ok "Permissions fixed"
|
|
|
|
# Sync / validate configs
|
|
step 5 7 "$(_t "Sync Configurations")"
|
|
sync_configs
|
|
|
|
# Rebuild frontend if dist is missing or empty
|
|
if [ ! -d "$NITRO_CLIENT/dist" ] || [ -z "$(ls -A "$NITRO_CLIENT/dist" 2>/dev/null)" ]; then
|
|
step 6 7 "$(_t "Rebuild Frontend")"
|
|
spinner_start "$(_t "Building frontend...")"
|
|
cd "$NITRO_CLIENT"
|
|
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
ok "Frontend rebuilt"
|
|
else
|
|
spinner_stop fail
|
|
fail "Frontend rebuild failed"
|
|
fi
|
|
cd "$SCRIPT_DIR"
|
|
else
|
|
ok "Frontend dist intact — skip rebuild"
|
|
fi
|
|
|
|
# Rebuild emulator if jar is missing
|
|
if [ ! -d "$EMULATOR_MODULE/target" ] || ! ls "$EMULATOR_MODULE"/target/Polaris-*-jar-with-dependencies.jar &>/dev/null 2>&1; then
|
|
step 7 7 "$(_t "Rebuild Emulator")"
|
|
cd "$EMULATOR_MODULE"
|
|
backup_binaries
|
|
ROLLBACK_NEEDED=true
|
|
spinner_start "$(_t "Building emulator (parallel)...")"
|
|
command -v ccache >/dev/null 2>&1 && export CC="ccache gcc" CXX="ccache g++"
|
|
if mvn package -q -T 1C -Dmaven.test.skip=true >> "$LOG_FILE" 2>&1; then
|
|
spinner_stop ok
|
|
ok "Emulator rebuilt"
|
|
else
|
|
spinner_stop fail
|
|
fail "Emulator rebuild failed"
|
|
fi
|
|
cd "$SCRIPT_DIR"
|
|
else
|
|
ok "Emulator jar intact — skip rebuild"
|
|
fi
|
|
|
|
# Restart services
|
|
do_restart
|
|
|
|
show_summary
|
|
fix_perms
|
|
release_lock
|
|
}
|
|
|
|
# =============================================================================
|
|
# AUTO-SETUP COMMAND
|
|
# =============================================================================
|
|
cmd_setup() {
|
|
step 1 1 "Setup"
|
|
echo ""
|
|
acquire_lock
|
|
info "Site: %s | Branch: %s | Mode: %s" "${NITRO_SITE_URL:-?}" "$NITRO_BRANCH" "auto-setup"
|
|
info "Log: %s" "$LOG_FILE"
|
|
|
|
auto_setup_missing_repos
|
|
|
|
release_lock
|
|
}
|
|
|
|
# =============================================================================
|
|
# SERVICES
|
|
# =============================================================================
|
|
cmd_services() {
|
|
step 1 1 "Service Manager"
|
|
echo ""
|
|
echo -e " ${C_BOLD}1)${C_RESET} $(_t "Start Emulator") ${C_BOLD}2)${C_RESET} $(_t "Stop Emulator") ${C_BOLD}3)${C_RESET} $(_t "Restart Emulator")"
|
|
echo -e " ${C_BOLD}4)${C_RESET} $(_t "Start Nginx") ${C_BOLD}5)${C_RESET} $(_t "Restart Nginx") ${C_BOLD}6)${C_RESET} $(_t "Status All")"
|
|
echo -e " ${C_BOLD}7)${C_RESET} $(_t "Emulator Logs") ${C_BOLD}8)${C_RESET} $(_t "Emulator Journal") ${C_BOLD}0)${C_RESET} $(_t "Return")"
|
|
echo -en "\n $(_t "Select:") "; local c; read -rt 30 c
|
|
case "$c" in
|
|
1) sudo systemctl start "$EMULATOR_SERVICE" && ok "Started" || fail "Failed" ;;
|
|
2) sudo systemctl stop "$EMULATOR_SERVICE" && ok "Stopped" || fail "Failed" ;;
|
|
3) sudo systemctl restart "$EMULATOR_SERVICE" && ok "Restarted" || fail "Failed" ;;
|
|
4) sudo systemctl start nginx && ok "Started" || fail "Failed" ;;
|
|
5) sudo systemctl restart nginx && ok "Restarted" || fail "Failed" ;;
|
|
6) for s in nginx mariadb redis-server "$EMULATOR_SERVICE" php-fpm cron; do
|
|
local st="${C_RED}$(_t "DOWN")${C_RESET}"; service_active "$s" && st="${C_GREEN}$(_t "UP")${C_RESET}"
|
|
printf " %-25s %b\n" "$s" "$st"
|
|
done ;;
|
|
7) sudo journalctl -u "$EMULATOR_SERVICE" --no-pager -n 30 ;;
|
|
8) sudo journalctl -u "$EMULATOR_SERVICE" --no-pager -n 100 --output=short-full ;;
|
|
esac
|
|
}
|
|
|
|
# =============================================================================
|
|
# DATABASE
|
|
# =============================================================================
|
|
cmd_database() {
|
|
step 1 1 "Database Tools"
|
|
echo ""
|
|
echo -e " ${C_BOLD}1)${C_RESET} $(_t "DB Info") ${C_BOLD}2)${C_RESET} $(_t "List Tables") ${C_BOLD}3)${C_RESET} $(_t "Table Sizes")"
|
|
echo -e " ${C_BOLD}4)${C_RESET} $(_t "Optimize") ${C_BOLD}5)${C_RESET} $(_t "Custom Query") ${C_BOLD}6)${C_RESET} $(_t "Active Users")"
|
|
echo -e " ${C_BOLD}7)${C_RESET} $(_t "Processlist") ${C_BOLD}0)${C_RESET} $(_t "Return")"
|
|
echo -en "\n $(_t "Select:") "; local c; read -rt 30 c
|
|
case "$c" in
|
|
1) box_top "DB INFO" 50
|
|
local dbs=$($MYSQL_CLIENT $MYSQL_CRED -N -e "SELECT ROUND(SUM(data_length+index_length)/1024/1024,1) FROM information_schema.tables WHERE table_schema='$DB_NAME'" "$DB_NAME" 2>/dev/null || echo "?")
|
|
box_kv "Name:" "$DB_NAME" 50; box_kv "Host:" "$DB_HOST:$DB_PORT" 50; box_kv "Size:" "${dbs}MB" 50
|
|
box_kv "Tables:" "$($MYSQL_CLIENT $MYSQL_CRED -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME'" "$DB_NAME" 2>/dev/null || echo '?')" 50
|
|
box_bottom 50 ;;
|
|
2) $MYSQL_CLIENT $MYSQL_CRED -e "SHOW TABLES FROM $DB_NAME" 2>/dev/null ;;
|
|
3) $MYSQL_CLIENT $MYSQL_CRED -e "SELECT table_name AS 'Table', ROUND(data_length/1024/1024,2) AS 'Data MB', ROUND(index_length/1024/1024,2) AS 'Index MB' FROM information_schema.tables WHERE table_schema='$DB_NAME' ORDER BY (data_length+index_length) DESC" 2>/dev/null ;;
|
|
4) spinner_start "Optimizing..."
|
|
for tbl in $($MYSQL_CLIENT $MYSQL_CRED -N -e "SELECT table_name FROM information_schema.tables WHERE table_schema='$DB_NAME' AND engine='InnoDB'" "$DB_NAME" 2>/dev/null); do
|
|
$MYSQL_CLIENT $MYSQL_CRED -e "OPTIMIZE TABLE $DB_NAME.\`$tbl\`" "$DB_NAME" &>/dev/null || true
|
|
done; spinner_stop ok; ok "Optimized" ;;
|
|
5) echo -en " SQL: "; local q; read -rt 30 q; [ -n "$q" ] && $MYSQL_CLIENT $MYSQL_CRED "$DB_NAME" -e "$q" 2>/dev/null ;;
|
|
6) $MYSQL_CLIENT $MYSQL_CRED -e "SELECT name, motto FROM $DB_NAME.users WHERE online='1'" 2>/dev/null || warn "Could not query" ;;
|
|
7) $MYSQL_CLIENT $MYSQL_CRED -e "SHOW FULL PROCESSLIST" 2>/dev/null || warn "Could not query" ;;
|
|
esac
|
|
}
|
|
|
|
# =============================================================================
|
|
# CONFIG
|
|
# =============================================================================
|
|
cmd_config() {
|
|
step 1 1 "Configuration"
|
|
echo ""
|
|
box_top "CONFIGURATION" 56
|
|
box_kv "Site:" "${NITRO_SITE_URL:-?}" 56
|
|
box_kv "Branch:" "$NITRO_BRANCH" 56
|
|
box_kv "Emulator:" "$EMULATOR_DIR" 56
|
|
box_kv "Octane:" "$NITRO_CLIENT" 56
|
|
box_kv "Renderer:" "$NITRO_RENDERER" 56
|
|
box_kv "Database:" "$DB_NAME @ $DB_HOST:$DB_PORT" 56
|
|
box_kv "Service:" "$EMULATOR_SERVICE" 56
|
|
box_kv "Socket:" "${NITRO_SOCKET_URL:-?}" 56
|
|
box_kv "API:" "${NITRO_API_URL:-?}" 56
|
|
box_kv "Gamedata:" "${NITRO_GAMEDATA_URL:-?}" 56
|
|
box_kv "Min Disk:" "${MIN_DISK_GB}GB" 56
|
|
box_bottom 56; echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# LOGS
|
|
# =============================================================================
|
|
cmd_logs() {
|
|
step 1 1 "Log Viewer"
|
|
[ ! -d "$LOG_DIR" ] && { warn "No log dir"; return; }
|
|
local logs=()
|
|
while IFS= read -r l; do logs+=("$l"); done < <(list_sorted "$LOG_DIR" 'update-*.log')
|
|
[ ${#logs[@]} -eq 0 ] && { warn "No logs"; return; }
|
|
echo ""
|
|
local i=1
|
|
for l in "${logs[@]}"; do
|
|
local sz=$(format_size "$(stat -c%s "$l" 2>/dev/null || echo 0)")
|
|
printf " ${C_GREEN}${C_BOLD}%2d)${C_RESET} %-35s ${C_DIM}%s${C_RESET}\n" "$i" "$(basename "$l")" "$sz"
|
|
i=$((i+1))
|
|
done
|
|
echo -en "\n $(_t "Select") [1-$((i-1))]: "; local c; read -rt 30 c
|
|
if [[ "$c" =~ ^[0-9]+$ ]] && [ "$c" -ge 1 ] && [ "$c" -le ${#logs[@]} ]; then
|
|
less -R "${logs[$((c-1))]}" 2>/dev/null || cat "${logs[$((c-1))]}"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# CLEAN
|
|
# =============================================================================
|
|
cmd_clean() {
|
|
step 1 1 "Cache Cleanup"
|
|
echo ""
|
|
echo -e " ${C_BOLD}1)${C_RESET} $(_t "Yarn") ${C_BOLD}2)${C_RESET} $(_t "Next.js") ${C_BOLD}3)${C_RESET} $(_t "Redis")"
|
|
echo -e " ${C_BOLD}4)${C_RESET} $(_t "Logs") ${C_BOLD}5)${C_RESET} $(_t "Everything") ${C_BOLD}6)${C_RESET} $(_t "Docker")"
|
|
echo -e " ${C_BOLD}7)${C_RESET} $(_t "Maven") ${C_BOLD}0)${C_RESET} $(_t "Return")"
|
|
echo -en "\n $(_t "Select:") "; local c; read -rt 30 c
|
|
case "$c" in
|
|
1) yarn cache clean 2>/dev/null; rm -rf "$YARN_CACHE_DIR" 2>/dev/null; ok "Yarn cleaned" ;;
|
|
2) cd "$SCRIPT_DIR" && rm -rf .next 2>/dev/null && ok "Next.js cache cleared" ;;
|
|
3) redis-cli FLUSHALL 2>/dev/null && ok "Redis flushed" || warn "Redis N/A" ;;
|
|
4) find "$EMULATOR_DIR" -name "*.log" -mtime +14 -exec rm -f {} \; 2>/dev/null; ok "Logs cleaned" ;;
|
|
5) yarn cache clean 2>/dev/null; rm -rf "$YARN_CACHE_DIR" 2>/dev/null; find "$EMULATOR_DIR" -name "*.log" -mtime +14 -exec rm -f {} \; 2>/dev/null; rm -rf /tmp/nitro-* /tmp/epicnext-* 2>/dev/null; ok "Full cleanup" ;;
|
|
6) command -v docker &>/dev/null && docker system prune -f 2>/dev/null && ok "Docker pruned" || warn "Docker N/A" ;;
|
|
7) find "$EMULATOR_MODULE" -maxdepth 3 -name '*.jar' -mtime +7 -exec rm -f {} \; 2>/dev/null; ok "Old Maven artifacts cleaned" ;;
|
|
esac
|
|
}
|
|
|
|
# =============================================================================
|
|
# GIT LOG
|
|
# =============================================================================
|
|
cmd_gitlog() {
|
|
step 1 1 "Git Commit History"
|
|
echo ""
|
|
echo -e " ${C_BOLD}1)${C_RESET} $(_t "Emulator") ${C_BOLD}2)${C_RESET} $(_t "Octane") ${C_BOLD}3)${C_RESET} $(_t "Renderer")"
|
|
echo -en "\n $(_t "Select:") "; local c; read -rt 30 c
|
|
local repo=""
|
|
case "$c" in 1) repo="$EMULATOR_REPO" ;; 2) repo="$NITRO_CLIENT" ;; 3) repo="$NITRO_RENDERER" ;; *) return ;; esac
|
|
[ ! -d "$repo/.git" ] && { warn "Not a git repo"; return; }
|
|
echo ""
|
|
local branch
|
|
branch=$(cd "$repo" && git branch --show-current 2>/dev/null || echo "?")
|
|
local ahead
|
|
ahead=$(cd "$repo" && git rev-list --count "origin/${branch}"..HEAD 2>/dev/null || echo "0")
|
|
echo -e " ${C_BOLD}$(_t "Last 20 commits on") ${C_CYAN}${branch}${C_RESET} ($(_t "ahead:") ${C_RED}${ahead}${C_RESET}):"
|
|
echo ""
|
|
cd "$repo" && git log --pretty=format:" ${C_CYAN}%h${C_RESET} %C(yellow)%cr${C_RESET} %s" -20 2>/dev/null
|
|
echo -e "\n"
|
|
}
|
|
|
|
# =============================================================================
|
|
# BRANCH COMPARE
|
|
# =============================================================================
|
|
cmd_compare() {
|
|
step 1 1 "Branch Comparison"
|
|
echo ""
|
|
local branches=()
|
|
load_branches branches
|
|
[ ${#branches[@]} -lt 2 ] && { warn "Need 2+ branches (found ${#branches[@]})"; return; }
|
|
echo -e " ${C_BOLD}$(_t "Branches:")${C_RESET}"
|
|
local i=1
|
|
for b in "${branches[@]}"; do echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} $b"; i=$((i+1)); done
|
|
echo -en "\n $(_t "Branch A:") "; local ba; read -rt 30 ba
|
|
echo -en " $(_t "Branch B:") "; local bb; read -rt 30 bb
|
|
[[ "$ba" =~ ^[0-9]+$ ]] && [[ "$bb" =~ ^[0-9]+$ ]] && [ "$ba" -ge 1 ] && [ "$bb" -ge 1 ] && [ "$ba" -le ${#branches[@]} ] && [ "$bb" -le ${#branches[@]} ] || { warn "Invalid"; return; }
|
|
local bA="${branches[$((ba-1))]}" bB="${branches[$((bb-1))]}"
|
|
echo ""
|
|
for repo in "$EMULATOR_REPO" "$NITRO_CLIENT" "$NITRO_RENDERER"; do
|
|
[ -d "$repo/.git" ] || continue
|
|
echo -e " ${C_BOLD}$(basename "$repo"):${C_RESET}"
|
|
cd "$repo" 2>/dev/null || continue
|
|
if git rev-parse --verify "$bA" &>/dev/null && git rev-parse --verify "$bB" &>/dev/null; then
|
|
local ahead=$(git rev-list --count "$bB".."$bA" 2>/dev/null || echo "0")
|
|
local behind=$(git rev-list --count "$bA".."$bB" 2>/dev/null || echo "0")
|
|
echo -e " $bA $(_t "is") ${C_GREEN}$ahead${C_RESET} $(_t "ahead,") ${C_YELLOW}$behind${C_RESET} $(_t "behind") $bB"
|
|
fi
|
|
done; echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# CRON SCHEDULER
|
|
# =============================================================================
|
|
cmd_schedule() {
|
|
step 1 1 "Cron Scheduler"
|
|
echo ""
|
|
local cc=$(crontab -l 2>/dev/null | grep -F "$SCRIPT_NAME" || echo "")
|
|
echo -e " ${C_BOLD}$(_t "Current:")${C_RESET} ${cc:-${C_DIM}$(_t "none")${C_RESET}}"
|
|
echo ""
|
|
echo -e " ${C_BOLD}1)${C_RESET} $(_t "Daily 03:00") ${C_BOLD}2)${C_RESET} $(_t "Daily 04:00") ${C_BOLD}3)${C_RESET} $(_t "Weekly Sun 03:00")"
|
|
echo -e " ${C_BOLD}4)${C_RESET} $(_t "Custom time") ${C_BOLD}5)${C_RESET} $(_t "Remove") ${C_BOLD}0)${C_RESET} $(_t "Return")"
|
|
echo -en "\n $(_t "Select:") "; local c; read -rt 30 c
|
|
case "$c" in
|
|
1) (crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME"; echo "0 3 * * * cd $SCRIPT_DIR && ./$SCRIPT_NAME update --branch $NITRO_BRANCH >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled daily 03:00" ;;
|
|
2) (crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME"; echo "0 4 * * * cd $SCRIPT_DIR && ./$SCRIPT_NAME update --branch $NITRO_BRANCH >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled daily 04:00" ;;
|
|
3) (crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME"; echo "0 3 * * 0 cd $SCRIPT_DIR && ./$SCRIPT_NAME update --branch $NITRO_BRANCH >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled weekly" ;;
|
|
4) echo -en " $(_t "Minute") [0-59]: "; local m; read -rt 30 m
|
|
echo -en " $(_t "Hour") [0-23]: "; local h; read -rt 30 h
|
|
(crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME"; echo "$m $h * * * cd $SCRIPT_DIR && ./$SCRIPT_NAME update --branch $NITRO_BRANCH >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled $m $h * * *" ;;
|
|
5) crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME" | crontab - && ok "Removed" ;;
|
|
esac
|
|
}
|
|
|
|
# =============================================================================
|
|
# GIT STASH VIEWER
|
|
# =============================================================================
|
|
cmd_stash() {
|
|
step 1 1 "Git Stashes"
|
|
local found=false
|
|
for repo in "$EMULATOR_REPO" "$NITRO_CLIENT" "$NITRO_RENDERER"; do
|
|
[ -d "$repo/.git" ] || continue
|
|
local list
|
|
list=$(cd "$repo" && git stash list 2>/dev/null)
|
|
if [ -n "$list" ]; then
|
|
found=true
|
|
echo ""
|
|
echo -e " ${C_BOLD}$(basename "$repo"):${C_RESET}"
|
|
echo "$list" | while IFS= read -r line; do echo -e " ${C_CYAN}→${C_RESET} $line"; done
|
|
fi
|
|
done
|
|
$found || warn "No stashes found"
|
|
echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# MAIN MENU
|
|
# =============================================================================
|
|
pick_branch_and_update() {
|
|
local mode_label="Full"
|
|
case "${SELECTIVE_UPDATE:-full}" in
|
|
emulator) mode_label="Emulator Only" ;; client) mode_label="Client Only" ;;
|
|
renderer) mode_label="Renderer Only" ;; configs) mode_label="Configs Only" ;;
|
|
dev) mode_label="Dev Mode" ;;
|
|
esac
|
|
local branches=()
|
|
load_branches branches
|
|
echo ""
|
|
echo -e " ${C_BOLD}${C_CYAN}$(_t "Update:") ${C_GREEN}$mode_label${C_RESET}"
|
|
echo ""
|
|
local i=1
|
|
for b in "${branches[@]}"; do
|
|
local m=""; [ "$b" = "$NITRO_BRANCH" ] && m=" ${C_GREEN}(current)${C_RESET}"
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} ${C_CYAN}$b${C_RESET}$m"
|
|
i=$((i+1))
|
|
done
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} $(_t "Custom")"
|
|
echo -en "\n $(_t "Branch") [1-$i]: "
|
|
local bc; read -rt 30 bc
|
|
if [[ "$bc" =~ ^[0-9]+$ ]] && [ "$bc" -ge 1 ] && [ "$bc" -lt "$i" ]; then
|
|
NITRO_BRANCH="${branches[$((bc-1))]}"
|
|
elif [ "$bc" = "$i" ]; then
|
|
echo -en " $(_t "Name:") "; read -rt 30 NITRO_BRANCH; [ -z "$NITRO_BRANCH" ] && NITRO_BRANCH="main"
|
|
else
|
|
warn "Invalid, using: %s" "$NITRO_BRANCH"
|
|
fi
|
|
echo -e "\n $(_t "Branch:") ${C_CYAN}${C_BOLD}$NITRO_BRANCH${C_RESET} $(_t "Mode:") ${C_GREEN}$mode_label${C_RESET}"
|
|
if ! confirm "Start update?"; then info "Cancelled"; return; fi
|
|
cmd_update
|
|
}
|
|
|
|
cmd_interactive() {
|
|
local branches=()
|
|
load_branches branches
|
|
while true; do
|
|
clear
|
|
echo -e "${C_CYAN}"
|
|
echo ""
|
|
echo " ╔═══════════════════════════════════════════════════════════════════════════╗"
|
|
echo " ║ ║"
|
|
echo " ║ ██████╗ ██████╗ ██████╗ ████████╗██╗ ██╗ ██████╗ ██████╗ ║"
|
|
echo " ║ ██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗ ║"
|
|
echo " ║ ██████╔╝██████╔╝██████╔╝ ██║ ███████║██║ ██║██████╔╝ ║"
|
|
echo " ║ ██╔═══╝ ██╔══██╗██╔══██╗ ██║ ██╔══██║██║ ██║██╔══██╗ ║"
|
|
echo " ║ ██║ ██║ ██║██████╔╝ ██║ ██║ ██║╚██████╔╝██║ ██║ ║"
|
|
echo " ║ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ║"
|
|
echo " ║ ║"
|
|
echo " ╠═══════════════════════════════════════════════════════════════════════════╣"
|
|
echo -e " ║ ${C_GOLD}${C_BOLD} SMaRT v${SCRIPT_VERSION} — Emulator / Octane / Octane-Renderer${C_CYAN} ║"
|
|
echo -e " ║ ${C_WHITE}${C_BOLD} Updater & Manager by Remco — epicnabbo.nl${C_CYAN} ║"
|
|
echo " ╠═══════════════════════════════════════════════════════════════════════════╣"
|
|
echo -e " ║ ${C_DIM}$(_t "Branch:") ${C_CYAN}${NITRO_BRANCH}${C_DIM} (${#branches[@]} $(_t "branches")) │ $(date '+%H:%M')${C_CYAN} ║"
|
|
echo " ╚═══════════════════════════════════════════════════════════════════════════╝"
|
|
echo -e "${C_RESET}"
|
|
|
|
echo -e " ${C_BG_GREEN}${C_WHITE}${C_BOLD} $(_t "UPDATE") ${C_RESET}"
|
|
echo -e " ${C_GREEN}${C_BOLD}1)${C_RESET} ${E_ROCKET} $(_t "Quick Update — current branch")"
|
|
echo -e " ${C_GREEN}${C_BOLD}2)${C_RESET} ${E_BOLT} $(_t "Full Update (choose branch)")"
|
|
echo -e " ${C_GREEN}${C_BOLD}3)${C_RESET} ${E_GEAR} $(_t "Emulator Only")"
|
|
echo -e " ${C_GREEN}${C_BOLD}4)${C_RESET} ${E_GEAR} $(_t "Octane Client Only")"
|
|
echo -e " ${C_GREEN}${C_BOLD}5)${C_RESET} ${E_GEAR} $(_t "Renderer Only")"
|
|
echo -e " ${C_GREEN}${C_BOLD}6)${C_RESET} ${E_GEAR} $(_t "Configs Only")"
|
|
echo -e " ${C_GREEN}${C_BOLD}19)${C_RESET} ${E_BOLT} $(_t "Dev Mode (skip builds, only configs)")"
|
|
echo ""
|
|
echo -e " ${C_BG_MAGENTA}${C_WHITE}${C_BOLD} $(_t "TOOLS") ${C_RESET}"
|
|
echo -e " ${C_MAGENTA}${C_BOLD}7)${C_RESET} ${E_GIT} $(_t "Switch Branch")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}8)${C_RESET} ${E_DB} $(_t "Database")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}9)${C_RESET} ${E_SHIELD} $(_t "Backup / Restore")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}10)${C_RESET} ${E_WRENCH} $(_t "Services")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}11)${C_RESET} ${E_CHART} $(_t "Status")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}12)${C_RESET} ${E_HEART} $(_t "Health Check")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}13)${C_RESET} ${E_BROOM} $(_t "Clean")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}14)${C_RESET} ${E_EYE} $(_t "Logs")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}15)${C_RESET} ${E_GEAR} $(_t "Config")"
|
|
echo ""
|
|
echo -e " ${C_BG_BLUE}${C_WHITE}${C_BOLD} $(_t "ADVANCED") ${C_RESET}"
|
|
echo -e " ${C_BLUE}${C_BOLD}16)${C_RESET} ${E_CONSOLE} $(_t "Git Log")"
|
|
echo -e " ${C_BLUE}${C_BOLD}17)${C_RESET} ${E_GLOBE} $(_t "Compare Branches")"
|
|
echo -e " ${C_BLUE}${C_BOLD}18)${C_RESET} ${E_CLOCK} $(_t "Cron Scheduler")"
|
|
echo -e " ${C_BLUE}${C_BOLD}20)${C_RESET} ${E_GIT} $(_t "View Git Stashes")"
|
|
echo -e " ${C_RED}${C_BOLD}21)${C_RESET} ${E_WRENCH} $(_t "Flyaway Repair")"
|
|
echo -e " ${C_GREEN}${C_BOLD}22)${C_RESET} ${E_ROCKET} $(_t "Auto-setup missing repos")"
|
|
echo -e " ${C_YELLOW}${C_BOLD}23)${C_RESET} ${E_BROOM} $(_t "Cleanup old Nitro dirs")"
|
|
echo ""
|
|
echo -e " ${C_RED}${C_BOLD}0)${C_RESET} $(_t "Exit")"
|
|
echo ""
|
|
echo -en " ${C_CYAN}${C_BOLD}$(_t "Select [0-23]")${C_RESET}: "
|
|
local choice; read -rt 30 choice
|
|
|
|
case "$choice" in
|
|
1) SELECTIVE_UPDATE=""; (cmd_update) || warn "Update had errors" ;;
|
|
2) SELECTIVE_UPDATE=""; (pick_branch_and_update) || warn "Update had errors" ;;
|
|
3) SELECTIVE_UPDATE="emulator"; (pick_branch_and_update) || warn "Update had errors" ;;
|
|
4) SELECTIVE_UPDATE="client"; (pick_branch_and_update) || warn "Update had errors" ;;
|
|
5) SELECTIVE_UPDATE="renderer"; (pick_branch_and_update) || warn "Update had errors" ;;
|
|
6) SELECTIVE_UPDATE="configs"; (pick_branch_and_update) || warn "Update had errors" ;;
|
|
7)
|
|
echo ""
|
|
local i=1
|
|
for b in "${branches[@]}"; do
|
|
local m=""; [ "$b" = "$NITRO_BRANCH" ] && m=" ${C_GREEN}*${C_RESET}"
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} $b$m"; i=$((i+1))
|
|
done
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} $(_t "Custom")"
|
|
echo -en "\n $(_t "Branch") [1-$i]: "; local bc; read -rt 30 bc
|
|
if [[ "$bc" =~ ^[0-9]+$ ]] && [ "$bc" -ge 1 ] && [ "$bc" -lt "$i" ]; then
|
|
NITRO_BRANCH="${branches[$((bc-1))]}"
|
|
elif [ "$bc" = "$i" ]; then echo -en " $(_t "Name:") "; read -rt 30 NITRO_BRANCH
|
|
fi
|
|
load_branches branches
|
|
ok "Branch: %s" "$NITRO_BRANCH" ;;
|
|
8) (cmd_database) ;;
|
|
9) (cmd_backup); echo ""; (cmd_backups) ;;
|
|
10) (cmd_services) ;;
|
|
11) (cmd_status) ;;
|
|
12) (cmd_health) ;;
|
|
13) (cmd_clean) ;;
|
|
14) (cmd_logs) ;;
|
|
15) (cmd_config) ;;
|
|
16) (cmd_gitlog) ;;
|
|
17) (cmd_compare) ;;
|
|
18) (cmd_schedule) ;;
|
|
19) SELECTIVE_UPDATE="dev"; (pick_branch_and_update) || warn "Dev mode had errors" ;;
|
|
20) (cmd_stash) ;;
|
|
21) (cmd_flyaway_repair) ;;
|
|
22) (cmd_setup) ;;
|
|
23) (cmd_cleanup_old_nitro) ;;
|
|
0|q|Q) echo -e "\n ${C_GREEN}$(_t "Goodbye!")${C_RESET}"; cursor_show; exit 0 ;;
|
|
*) warn "Invalid selection" ;;
|
|
esac
|
|
|
|
echo ""
|
|
echo -en " ${C_DIM}$(_t "Press Enter to continue...")${C_RESET}"
|
|
read -rt 30
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# HELP
|
|
# =============================================================================
|
|
show_help() {
|
|
echo ""
|
|
echo -e " ${C_GOLD}${C_BOLD}$(_t "SMaRT — Emulator / Octane / Octane-Renderer Updater & Manager")${C_RESET}"
|
|
echo -e " ${C_DIM}by Remco — epicnabbo.nl v${SCRIPT_VERSION} (build ${SCRIPT_BUILD})${C_RESET}"
|
|
echo ""
|
|
echo -e " ${C_BOLD}$(_t "USAGE:")${C_RESET} $SCRIPT_NAME <command> [options]"
|
|
echo ""
|
|
echo -e " ${C_BOLD}$(_t "COMMANDS:")${C_RESET}"
|
|
echo -e " ${C_GREEN}interactive${C_RESET} $(_t "Open menu (default)")"
|
|
echo -e " ${C_GREEN}update${C_RESET} $(_t "Run update")"
|
|
echo -e " ${C_GREEN}backup${C_RESET} $(_t "Create database backup")"
|
|
echo -e " ${C_GREEN}restore${C_RESET} $(_t "Restore from backup")"
|
|
echo -e " ${C_GREEN}backups${C_RESET} $(_t "List backups")"
|
|
echo -e " ${C_GREEN}status${C_RESET} $(_t "System dashboard")"
|
|
echo -e " ${C_GREEN}health${C_RESET} $(_t "Health check")"
|
|
echo -e " ${C_GREEN}services${C_RESET} $(_t "Service manager")"
|
|
echo -e " ${C_GREEN}database${C_RESET} $(_t "Database tools")"
|
|
echo -e " ${C_GREEN}config${C_RESET} $(_t "Show config")"
|
|
echo -e " ${C_GREEN}logs${C_RESET} $(_t "View logs")"
|
|
echo -e " ${C_GREEN}clean${C_RESET} $(_t "Cache cleanup")"
|
|
echo -e " ${C_GREEN}gitlog${C_RESET} $(_t "Git history")"
|
|
echo -e " ${C_GREEN}compare${C_RESET} $(_t "Branch diff")"
|
|
echo -e " ${C_GREEN}schedule${C_RESET} $(_t "Cron setup")"
|
|
echo -e " ${C_GREEN}stash${C_RESET} $(_t "View git stashes")"
|
|
echo -e " ${C_GREEN}repair${C_RESET} $(_t "Flyaway Repair")"
|
|
echo -e " ${C_GREEN}setup${C_RESET} $(_t "Auto-setup missing repos")"
|
|
echo -e " ${C_GREEN}cleanup-nitro${C_RESET} $(_t "Cleanup old Nitro dirs")"
|
|
echo ""
|
|
echo -e " ${C_BOLD}$(_t "OPTIONS:")${C_RESET}"
|
|
echo -e " ${C_CYAN}--branch, -b${C_RESET} $(_t "Branch (auto-detects)")"
|
|
echo -e " ${C_CYAN}--url, -u${C_RESET} $(_t "Site URL")"
|
|
echo -e " ${C_CYAN}--only=${C_RESET} $(_t "emulator|client|renderer|configs|dev")"
|
|
echo -e " ${C_CYAN}--dry-run, -n${C_RESET} $(_t "Preview only (no changes)")"
|
|
echo -e " ${C_CYAN}--help, -h${C_RESET} $(_t "Show this help")"
|
|
echo -e " ${C_CYAN}--version, -V${C_RESET} $(_t "Show version")"
|
|
echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# MAIN
|
|
# =============================================================================
|
|
main() {
|
|
load_language
|
|
local cmd=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
update|up) cmd="update" ;;
|
|
interactive|menu|ui) cmd="interactive" ;;
|
|
status|st) cmd="status" ;;
|
|
health|check) cmd="health" ;;
|
|
backup|bk) cmd="backup" ;;
|
|
restore|rs) cmd="restore" ;;
|
|
backups|bl) cmd="backups" ;;
|
|
gitlog|gl) cmd="gitlog" ;;
|
|
compare|diff) cmd="compare" ;;
|
|
schedule|cron) cmd="schedule" ;;
|
|
services|svc) cmd="services" ;;
|
|
database|db) cmd="database" ;;
|
|
config|cfg) cmd="config" ;;
|
|
logs|log) cmd="logs" ;;
|
|
clean|cl) cmd="clean" ;;
|
|
stash|stashes) cmd="stash" ;;
|
|
repair|flyaway) cmd="repair" ;;
|
|
setup|init) cmd="setup" ;;
|
|
cleanup-nitro|oldnitro) cmd="cleanup-nitro" ;;
|
|
help|-h|--help) cmd="help" ;;
|
|
version|-V|--version) echo "v${SCRIPT_VERSION} (build ${SCRIPT_BUILD})"; exit 0 ;;
|
|
--branch|-b) shift; NITRO_BRANCH="$1" ;;
|
|
--url|-u) shift; NITRO_SITE_URL="$1" ;;
|
|
--only=*) SELECTIVE_UPDATE="${1#*=}" ;;
|
|
--dry-run|-n) DRY_RUN=true ;;
|
|
-*) echo "$(_t "Unknown:") $1"; exit 1 ;;
|
|
*) [ -z "$cmd" ] && cmd="interactive" ;;
|
|
esac
|
|
shift
|
|
done
|
|
[ -z "$cmd" ] && cmd="interactive"
|
|
|
|
# Auto-detect the branch from the emulator repo when none was configured,
|
|
# so "Quick Update — current branch" actually updates the current branch.
|
|
if [ -z "${NITRO_BRANCH:-}" ]; then
|
|
NITRO_BRANCH="$(cd "$EMULATOR_REPO" 2>/dev/null && git branch --show-current 2>/dev/null || echo "main")"
|
|
[ -z "$NITRO_BRANCH" ] && NITRO_BRANCH="main"
|
|
info "Auto-detected branch: %s" "$NITRO_BRANCH"
|
|
fi
|
|
|
|
if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then NITRO_SITE_URL="$APP_URL"; fi
|
|
# Auto-detect the site domain from the nginx vhost when no explicit URL is
|
|
# configured (APP_URL / NITRO_SITE_URL / --url). This lets the update script
|
|
# self-configure the client URLs and the ws.<domain> proxy on a fresh hotel
|
|
# VPS without manual .env edits.
|
|
if [ -z "${NITRO_SITE_URL:-}" ]; then
|
|
for _c in "${NGINX_SITE_CONFS[@]}"; do
|
|
[ -f "$_c" ] || continue
|
|
NITRO_SITE_URL=$(grep -oE 'server_name[^;]+;' "$_c" 2>/dev/null | head -1 \
|
|
| tr -s ' ' '\n' | grep -vE 'server_name|;|127\.0\.0\.1|172\.|10\.|192\.168|localhost|\[?' | head -1)
|
|
[ -n "$NITRO_SITE_URL" ] && NITRO_SITE_URL="https://$NITRO_SITE_URL" && break
|
|
done
|
|
fi
|
|
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
|
|
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:-}}"
|
|
NITRO_SOCKET_URL="${NITRO_SOCKET_URL:-${NITRO_WS_PROTO}ws.${NITRO_DOMAIN}}"
|
|
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}"
|
|
|
|
# Validate computed URLs for common misconfigurations.
|
|
_nitro_url_warn() {
|
|
local _key="$1" _val="$2" _hint="$3"
|
|
case "$_val" in
|
|
"") warn "$_key not set ($_hint)" ;;
|
|
*/nitro-client/*|*/nitro-client) fail "$_key points at the Nitro client path. It must point to the gamedata/c_images directory." ;;
|
|
*://*/nitro-client/) fail "$_key points at the Nitro client URL. Set NITRO_IMAGE_LIBRARY_URL=/gamedata/c_images/ or correct NITRO_SITE_URL." ;;
|
|
esac
|
|
case "$_key" in
|
|
NITRO_IMAGE_LIBRARY_URL)
|
|
case "$_val" in
|
|
*/c_images/) ok "$_key correctly includes /c_images/" ;;
|
|
*://*) warn "$_key does not end with /c_images/ — current: $_val" ;;
|
|
esac ;;
|
|
esac
|
|
}
|
|
_nitro_url_warn "NITRO_SITE_URL" "$NITRO_SITE_URL" "site URL"
|
|
_nitro_url_warn "NITRO_IMAGE_LIBRARY_URL" "$NITRO_IMAGE_LIBRARY_URL" "image library URL"
|
|
_nitro_url_warn "NITRO_GAMEDATA_URL" "$NITRO_GAMEDATA_URL" "gamedata URL"
|
|
|
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
|
|
case "$cmd" in
|
|
interactive) cmd_interactive ;;
|
|
update) cmd_update ;;
|
|
backup) cmd_backup ;;
|
|
restore) cmd_restore ;;
|
|
backups) cmd_backups ;;
|
|
status) cmd_status ;;
|
|
health) cmd_health ;;
|
|
services) cmd_services ;;
|
|
database) cmd_database ;;
|
|
config) cmd_config ;;
|
|
logs) cmd_logs ;;
|
|
clean) cmd_clean ;;
|
|
gitlog) cmd_gitlog ;;
|
|
compare) cmd_compare ;;
|
|
schedule) cmd_schedule ;;
|
|
stash) cmd_stash ;;
|
|
repair) cmd_flyaway_repair ;;
|
|
setup) cmd_setup ;;
|
|
cleanup-nitro) cmd_cleanup_old_nitro ;;
|
|
help) show_help ;;
|
|
esac
|
|
|
|
cursor_show
|
|
}
|
|
|
|
main "$@"
|