diff --git a/update-Nitrov3.sh b/update-Nitrov3.sh index 473a0ef0..73804d60 100755 --- a/update-Nitrov3.sh +++ b/update-Nitrov3.sh @@ -2,27 +2,19 @@ set -Eeo pipefail # ╔══════════════════════════════════════════════════════════════════════════════╗ -# ║ ║ -# ║ ███████╗███╗ ███╗ █████╗ ██████╗ ████████╗ ║ -# ║ ██╔════╝████╗ ████║██╔══██╗██╔══██╗╚══██╔══╝ ║ -# ║ █████╗ ██╔████╔██║███████║██████╔╝ ██║ ║ -# ║ ██╔══╝ ██║╚██╔╝██║██╔══██║██╔══██╗ ██║ ║ -# ║ ███████╗██║ ╚═╝ ██║██║ ██║██║ ██║ ██║ ║ -# ║ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ║ -# ║ ║ -# ║ Emulator / Nitro-V3 / Nitro-V3-Render ║ -# ║ Updater by Remco — epicnabbo.nl ║ -# ║ ║ +# ║ SMaRT — Emulator / Nitro-V3 / Nitro-V3-Render ║ +# ║ Updater & Manager by Remco ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # ============================================================================= # CONSTANTS # ============================================================================= -readonly SCRIPT_VERSION="7.0.0" -readonly SCRIPT_BUILD="20260625" +readonly SCRIPT_VERSION="8.0.0" +readonly SCRIPT_BUILD="20260720" 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 @@ -48,13 +40,13 @@ if locale charmap 2>/dev/null | grep -qi utf; then 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_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_CONSOLE="[M]" E_CLOCK="[T]" E_DOCKER="[D]" E_CHECK="[C]" fi # ============================================================================= @@ -70,13 +62,14 @@ UPDATED_REPOS=() LAST_BACKUP="" ROLLBACK_NEEDED=false 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 -# Parse DATABASE_URL as fallback for DB credentials (Next.js / Prisma style) if [ -z "${NITRO_DB_NAME:-}" ] && [ -n "${DATABASE_URL:-}" ]; then eval "$( python3 -c " @@ -122,6 +115,9 @@ 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 + 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" @@ -133,6 +129,21 @@ else MYSQL_CLIENT="mariadb"; MYSQL_DUMP="mariadb-dump" fi +# ============================================================================= +# LOCKFILE +# ============================================================================= +acquire_lock() { + exec 9>"$LOCK_FILE" + if ! flock -n 9; then + die "Another instance is already running (lock: $LOCK_FILE)" + fi +} + +release_lock() { + flock -u 9 2>/dev/null || true + rm -f "$LOCK_FILE" 2>/dev/null || true +} + # ============================================================================= # UI HELPERS # ============================================================================= @@ -147,6 +158,7 @@ die() { echo -e " ${C_BG_RED}${C_WHITE}${C_BOLD} ERROR ${C_RESET} ${C_RED}$1${C_RESET}" log_write "FATAL" "$1" cleanup_notify_failure "$1" + release_lock cursor_show exit 1 } @@ -156,6 +168,7 @@ 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 @@ -166,6 +179,7 @@ service_active() { pgrep -x "$svc" &>/dev/null fi } + format_size() { local bytes="${1:-0}" if command -v numfmt &>/dev/null; then @@ -277,15 +291,19 @@ print(json.dumps(payload)) cleanup_notify_failure() { $NOTIFY_FAILED && return; NOTIFY_FAILED=true; notify "FAILED" "$1"; } cleanup_on_exit() { - local ec=$?; cursor_show + local ec=$?; cursor_show; release_lock if [ $ec -ne 0 ] && [ "$ROLLBACK_NEEDED" = true ]; then echo -e "\n ${C_BG_RED}${C_WHITE}${C_BOLD} ROLLBACK ${C_RESET}" - : + echo -e " 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 fi [ $ec -ne 0 ] && cleanup_notify_failure "Exit code $ec" } trap cleanup_on_exit EXIT -trap 'cursor_show; exit 1' SIGINT SIGTERM +trap 'release_lock; cursor_show; exit 1' SIGINT SIGTERM # ============================================================================= # GIT HELPERS @@ -341,6 +359,101 @@ clean_node_modules() { rm -rf "node_modules" 2>/dev/null || sudo rm -rf "$(pwd)/node_modules" 2>/dev/null || true } +# ============================================================================= +# PARALLEL HELPERS +# ============================================================================= +PARALLEL_PIDS=() + +parallel_run() { + local name="$1"; shift + ( + if "$@" 2>&1; then + echo "__PARALLEL_OK__${name}" + else + echo "__PARALLEL_FAIL__${name}" + fi + ) & + PARALLEL_PIDS+=($!) +} + +parallel_wait() { + local failed=() + for pid in "${PARALLEL_PIDS[@]}"; do + wait "$pid" 2>/dev/null || true + done + PARALLEL_PIDS=() +} + +# ============================================================================= +# 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" + if ! python3 -m json.tool "$file" >/dev/null 2>&1; then + return 1 + fi + return 0 +} + +validate_configs() { + local dir="$1" + local errors=0 + for f in "$dir"/*.json; do + [ -f "$f" ] || continue + if ! validate_json "$f"; then + warn "Invalid JSON: $(basename "$f")" + errors=$((errors + 1)) + fi + done + [ "$errors" -eq 0 ] && ok "All config files valid" || warn "$errors config(s) invalid" +} + +# ============================================================================= +# 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" + + for d in "$EMULATOR_DIR/Emulator" "$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: ${total_gb}GB free" + else + ok "${total_gb}GB disk available" + fi + + $MYSQL_CLIENT $MYSQL_CRED -e "SELECT 1" "$DB_NAME" &>/dev/null || die "DB connection failed" + ok "Database connected" +} + # ============================================================================= # UPDATE FUNCTIONS # ============================================================================= @@ -349,12 +462,12 @@ update_emulator() { if git_update "$EMULATOR_DIR/Emulator" "$NITRO_BRANCH"; then HAD_UPDATES=true; UPDATED_REPOS+=("Emulator") cd "$EMULATOR_DIR/Emulator" - spinner_start "Building emulator..." - if mvn package -q >> "$LOG_FILE" 2>&1; then + spinner_start "Building emulator (parallel)..." + if mvn package -q -T 1C -Dmaven.test.skip=true >> "$LOG_FILE" 2>&1; then spinner_stop ok else spinner_stop fail - tail -10 "$LOG_FILE" | grep -E '(ERROR|FAILURE|BUILD)' || true + tail -20 "$LOG_FILE" | grep -E '(ERROR|FAILURE|BUILD)' || true die "Maven build failed (see: $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 "") @@ -382,7 +495,7 @@ update_renderer() { HAD_UPDATES=true; UPDATED_REPOS+=("Nitro_Render_V3") cd "$NITRO_RENDERER" spinner_start "Installing deps..." - yarn install --frozen-lockfile 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; } + yarn install --frozen-lockfile --prefer-offline 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; } spinner_stop ok; ok "Renderer dependencies installed" else info "Renderer: already up to date" @@ -395,7 +508,7 @@ update_client() { HAD_UPDATES=true; NITRO_BUILT=true; UPDATED_REPOS+=("Nitro-V3") cd "$NITRO_CLIENT" spinner_start "Installing deps..." - yarn install --frozen-lockfile 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; } + yarn install --frozen-lockfile --prefer-offline 2>&1 || { clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; } spinner_stop ok spinner_start "Building frontend..." if yarn build >> "$LOG_FILE" 2>&1; then @@ -415,6 +528,11 @@ update_client() { 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: $(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 @@ -432,6 +550,9 @@ sync_configs() { done ok "$ec config file(s) synced" + # Validate the generated configs + validate_configs "$NITRO_SRC_DIR" + info "Applying critical config URLs..." NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$dcd" \ NITRO_IMAGE_LIBRARY_URL="$NITRO_IMAGE_LIBRARY_URL" NITRO_HOF_FURNITURE_URL="$NITRO_HOF_FURNITURE_URL" \ @@ -463,15 +584,28 @@ for path in paths: with open(path, "w") as f: json.dump(data, f, indent=(4 if "dist" not in path else None), separators=(",", ":") if "dist" in path else (",", ": ")) print(f" [OK] Fixed: {os.path.basename(path)}") ' && ok "All config URLs synced" + + # 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 } 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 yarn cache clean 2>/dev/null || true - rm -rf /tmp/nitro-* 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_DIR/Emulator" -maxdepth 3 -name '*.jar' -mtime +7 2>/dev/null -exec rm -f {} \; || true ok "Cleanup done ($lc logs removed)" } @@ -531,42 +665,33 @@ show_summary() { # ============================================================================= cmd_update() { echo "" + acquire_lock DRY_RUN=${DRY_RUN:-false} info "Site: ${NITRO_SITE_URL:-?} | Branch: $NITRO_BRANCH | Mode: ${SELECTIVE_UPDATE:-full}${DRY_RUN:+ [DRY-RUN]}" info "Log: $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" - for cmd in git mvn node python3 yarn; do - command -v "$cmd" &>/dev/null || die "Missing: $cmd" - done - command -v "$MYSQL_CLIENT" &>/dev/null || die "Missing: $MYSQL_CLIENT" - command -v "$MYSQL_DUMP" &>/dev/null || die "Missing: $MYSQL_DUMP" - ok "All commands available" - - for d in "$EMULATOR_DIR/Emulator" "$NITRO_RENDERER" "$NITRO_CLIENT" "$NITRO_SRC_DIR"; do - [ -d "$d" ] || die "Missing dir: $d" - done - ok "All directories exist" - - local ak=$(df --output=avail "$EMULATOR_DIR" 2>/dev/null | tail -1) - local ag=$((ak / 1024 / 1024)) - [ "$ag" -lt "$MIN_DISK_GB" ] && die "Only ${ag}GB free (min ${MIN_DISK_GB}GB)" - ok "${ag}GB disk available" - - $MYSQL_CLIENT $MYSQL_CRED -e "SELECT 1" "$DB_NAME" &>/dev/null || die "DB connection failed" - ok "Database connected" + 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 update_renderer & update_client & @@ -575,10 +700,12 @@ cmd_update() { do_cleanup do_permissions do_restart + RUN_ROLLBACK=false ;; esac show_summary + release_lock } # ============================================================================= @@ -666,7 +793,12 @@ cmd_status() { [ -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 "?") - printf " ${C_CYAN}║${C_RESET} %-12s ${C_CYAN}%-8s${C_RESET} %s\n" "$nm" "$br" "$cm" + 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 "" } @@ -712,7 +844,7 @@ cmd_services() { echo "" echo -e " ${C_BOLD}1)${C_RESET} Start Emulator ${C_BOLD}2)${C_RESET} Stop Emulator ${C_BOLD}3)${C_RESET} Restart Emulator" echo -e " ${C_BOLD}4)${C_RESET} Start Nginx ${C_BOLD}5)${C_RESET} Restart Nginx ${C_BOLD}6)${C_RESET} Status All" - echo -e " ${C_BOLD}7)${C_RESET} Emulator Logs ${C_BOLD}0)${C_RESET} Return" + echo -e " ${C_BOLD}7)${C_RESET} Emulator Logs ${C_BOLD}8)${C_RESET} Emulator Journal ${C_BOLD}0)${C_RESET} Return" echo -en "\n Select: "; local c; read -rt 30 c case "$c" in 1) sudo systemctl start "$EMULATOR_SERVICE" && ok "Started" || fail "Failed" ;; @@ -725,6 +857,7 @@ cmd_services() { 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 } @@ -736,7 +869,7 @@ cmd_database() { echo "" echo -e " ${C_BOLD}1)${C_RESET} DB Info ${C_BOLD}2)${C_RESET} List Tables ${C_BOLD}3)${C_RESET} Table Sizes" echo -e " ${C_BOLD}4)${C_RESET} Optimize ${C_BOLD}5)${C_RESET} Custom Query ${C_BOLD}6)${C_RESET} Active Users" - echo -e " ${C_BOLD}0)${C_RESET} Return" + echo -e " ${C_BOLD}7)${C_RESET} Processlist ${C_BOLD}0)${C_RESET} Return" echo -en "\n Select: "; local c; read -rt 30 c case "$c" in 1) box_top "DB INFO" 50 @@ -752,6 +885,7 @@ cmd_database() { 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 } @@ -804,15 +938,18 @@ cmd_logs() { cmd_clean() { step 1 1 "Cache Cleanup" echo "" - echo -e " ${C_BOLD}1)${C_RESET} Yarn ${C_BOLD}2)${C_RESET} Laravel ${C_BOLD}3)${C_RESET} Redis" - echo -e " ${C_BOLD}4)${C_RESET} Logs ${C_BOLD}5)${C_RESET} Everything ${C_BOLD}0)${C_RESET} Return" + echo -e " ${C_BOLD}1)${C_RESET} Yarn ${C_BOLD}2)${C_RESET} Next.js ${C_BOLD}3)${C_RESET} Redis" + echo -e " ${C_BOLD}4)${C_RESET} Logs ${C_BOLD}5)${C_RESET} Everything ${C_BOLD}6)${C_RESET} Docker" + echo -e " ${C_BOLD}7)${C_RESET} Maven ${C_BOLD}0)${C_RESET} Return" echo -en "\n Select: "; local c; read -rt 30 c case "$c" in 1) yarn cache clean 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; find "$EMULATOR_DIR" -name "*.log" -mtime +14 -exec rm -f {} \; 2>/dev/null; rm -rf /tmp/nitro-* 2>/dev/null; ok "Full cleanup" ;; + 5) yarn cache clean 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_DIR/Emulator" -maxdepth 3 -name '*.jar' -mtime +7 -exec rm -f {} \; 2>/dev/null; ok "Old Maven artifacts cleaned" ;; esac } @@ -828,7 +965,11 @@ cmd_gitlog() { case "$c" in 1) repo="$EMULATOR_DIR/Emulator" ;; 2) repo="$NITRO_CLIENT" ;; 3) repo="$NITRO_RENDERER" ;; *) return ;; esac [ ! -d "$repo/.git" ] && { warn "Not a git repo"; return; } echo "" - echo -e " ${C_BOLD}Last 20 commits on ${C_CYAN}$(cd "$repo" && git branch --show-current 2>/dev/null)${C_RESET}:" + 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}Last 20 commits on ${C_CYAN}${branch}${C_RESET} (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" @@ -886,6 +1027,27 @@ cmd_schedule() { esac } +# ============================================================================= +# GIT STASH VIEWER +# ============================================================================= +cmd_stash() { + step 1 1 "Git Stashes" + local found=false + for repo in "$EMULATOR_DIR/Emulator" "$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 # ============================================================================= @@ -894,6 +1056,7 @@ pick_branch_and_update() { 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 @@ -930,28 +1093,29 @@ cmd_interactive() { echo "" echo " ╔═══════════════════════════════════════════════════════════════════════════╗" echo " ║ ║" - echo " ║ ███████╗███╗ ███╗ █████╗ ██████╗ ████████╗ ║" - echo " ║ ██╔════╝████╗ ████║██╔══██╗██╔══██╗╚══██╔══╝ ║" - echo " ║ █████╗ ██╔████╔██║███████║██████╔╝ ██║ ║" - echo " ║ ██╔══╝ ██║╚██╔╝██║██╔══██║██╔══██╗ ██║ ║" - echo " ║ ███████╗██║ ╚═╝ ██║██║ ██║██║ ██║ ██║ ║" - echo " ║ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ║" + echo " ║ ███████╗███╗ ███╗ █████╗ ██████╗ ████████╗ ║" + echo " ║ ██╔════╝████╗ ████║██╔══██╗██╔══██╗╚══██╔══╝ ║" + echo " ║ █████╗ ██╔████╔██║███████║██████╔╝ ██║ ║" + echo " ║ ██╔══╝ ██║╚██╔╝██║██╔══██║██╔══██╗ ██║ ║" + echo " ║ ███████╗██║ ╚═╝ ██║██║ ██║██║ ██║ ██║ ║" + echo " ║ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ║" echo " ║ ║" echo " ╠═══════════════════════════════════════════════════════════════════════════╣" - echo -e " ║ ${C_GOLD}${C_BOLD} Emulator / Nitro-V3 / Nitro-V3-Render${C_CYAN} ║" - echo -e " ║ ${C_WHITE}${C_BOLD} Updater by Remco — epicnabbo.nl${C_CYAN} ║" + echo -e " ║ ${C_GOLD}${C_BOLD} SMaRT v${SCRIPT_VERSION} — Emulator / Nitro-V3 / Nitro-V3-Render${C_CYAN} ║" + echo -e " ║ ${C_WHITE}${C_BOLD} Updater & Manager by Remco — epicnabbo.nl${C_CYAN} ║" echo " ╠═══════════════════════════════════════════════════════════════════════════╣" - echo -e " ║ ${C_DIM}Branch: ${C_CYAN}${NITRO_BRANCH}${C_DIM} (${#branches[@]} detected) │ $(date '+%H:%M')${C_CYAN} ║" + echo -e " ║ ${C_DIM}Branch: ${C_CYAN}${NITRO_BRANCH}${C_DIM} (${#branches[@]} branches) │ $(date '+%H:%M')${C_CYAN} ║" echo " ╚═══════════════════════════════════════════════════════════════════════════╝" echo -e "${C_RESET}" echo -e " ${C_BG_GREEN}${C_WHITE}${C_BOLD} UPDATE ${C_RESET}" - echo -e " ${C_GREEN}${C_BOLD}1)${C_RESET} ${E_ROCKET} Quick Update — branch: ${C_CYAN}${NITRO_BRANCH}${C_RESET}" + echo -e " ${C_GREEN}${C_BOLD}1)${C_RESET} ${E_ROCKET} Quick Update — current branch" echo -e " ${C_GREEN}${C_BOLD}2)${C_RESET} ${E_BOLT} Full Update (choose branch)" echo -e " ${C_GREEN}${C_BOLD}3)${C_RESET} ${E_GEAR} Emulator Only" echo -e " ${C_GREEN}${C_BOLD}4)${C_RESET} ${E_GEAR} Nitro-V3 Client Only" echo -e " ${C_GREEN}${C_BOLD}5)${C_RESET} ${E_GEAR} Renderer Only" echo -e " ${C_GREEN}${C_BOLD}6)${C_RESET} ${E_GEAR} Configs Only" + echo -e " ${C_GREEN}${C_BOLD}19)${C_RESET} ${E_BOLT} Dev Mode (skip builds, only configs)" echo "" echo -e " ${C_BG_MAGENTA}${C_WHITE}${C_BOLD} TOOLS ${C_RESET}" echo -e " ${C_MAGENTA}${C_BOLD}7)${C_RESET} ${E_GIT} Switch Branch" @@ -968,10 +1132,11 @@ cmd_interactive() { echo -e " ${C_BLUE}${C_BOLD}16)${C_RESET} ${E_CONSOLE} Git Log" echo -e " ${C_BLUE}${C_BOLD}17)${C_RESET} ${E_GLOBE} Compare Branches" echo -e " ${C_BLUE}${C_BOLD}18)${C_RESET} ${E_CLOCK} Cron Scheduler" + echo -e " ${C_BLUE}${C_BOLD}20)${C_RESET} ${E_GIT} View Git Stashes" echo "" echo -e " ${C_RED}${C_BOLD}0)${C_RESET} Exit" echo "" - echo -en " ${C_CYAN}${C_BOLD}Select [0-18]${C_RESET}: " + echo -en " ${C_CYAN}${C_BOLD}Select [0-20]${C_RESET}: " local choice; read -rt 30 choice case "$choice" in @@ -1007,6 +1172,8 @@ cmd_interactive() { 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) ;; 0|q|Q) echo -e "\n ${C_GREEN}Goodbye!${C_RESET}"; cursor_show; exit 0 ;; *) warn "Invalid selection" ;; esac @@ -1022,16 +1189,16 @@ cmd_interactive() { # ============================================================================= show_help() { echo "" - echo -e " ${C_GOLD}${C_BOLD}Emulator / Nitro-V3 / Nitro-V3-Render Updater${C_RESET}" - echo -e " ${C_DIM}by Remco — epicnabbo.nl v${SCRIPT_VERSION}${C_RESET}" + echo -e " ${C_GOLD}${C_BOLD}SMaRT — Emulator / Nitro-V3 / Nitro-V3-Render 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}USAGE:${C_RESET} $SCRIPT_NAME [options]" echo "" echo -e " ${C_BOLD}COMMANDS:${C_RESET}" echo -e " ${C_GREEN}interactive${C_RESET} Open menu (default)" - echo -e " ${C_GREEN}update${C_RESET} Run update (--only=emulator|client|renderer|configs)" - echo -e " ${C_GREEN}backup${C_RESET} Create backup" - echo -e " ${C_GREEN}restore${C_RESET} Restore backup" + echo -e " ${C_GREEN}update${C_RESET} Run update" + echo -e " ${C_GREEN}backup${C_RESET} Create database backup" + echo -e " ${C_GREEN}restore${C_RESET} Restore from backup" echo -e " ${C_GREEN}backups${C_RESET} List backups" echo -e " ${C_GREEN}status${C_RESET} System dashboard" echo -e " ${C_GREEN}health${C_RESET} Health check" @@ -1043,15 +1210,15 @@ show_help() { echo -e " ${C_GREEN}gitlog${C_RESET} Git history" echo -e " ${C_GREEN}compare${C_RESET} Branch diff" echo -e " ${C_GREEN}schedule${C_RESET} Cron setup" - echo -e " ${C_GREEN}help${C_RESET} This help" + echo -e " ${C_GREEN}stash${C_RESET} View git stashes" echo "" echo -e " ${C_BOLD}OPTIONS:${C_RESET}" echo -e " ${C_CYAN}--branch, -b${C_RESET} Branch (auto-detects)" echo -e " ${C_CYAN}--url, -u${C_RESET} Site URL" - echo -e " ${C_CYAN}--only=${C_RESET} emulator|client|renderer|configs" + echo -e " ${C_CYAN}--only=${C_RESET} emulator|client|renderer|configs|dev" echo -e " ${C_CYAN}--dry-run, -n${C_RESET} Preview only (no changes)" - echo -e " ${C_CYAN}--help, -h${C_RESET} Help" - echo -e " ${C_CYAN}--version, -V${C_RESET} Version" + echo -e " ${C_CYAN}--help, -h${C_RESET} Show this help" + echo -e " ${C_CYAN}--version, -V${C_RESET} Show version" echo "" } @@ -1077,8 +1244,9 @@ main() { config|cfg) cmd="config" ;; logs|log) cmd="logs" ;; clean|cl) cmd="clean" ;; + stash|stashes) cmd="stash" ;; help|-h|--help) cmd="help" ;; - version|-V|--version) echo "v${SCRIPT_VERSION}"; exit 0 ;; + 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#*=}" ;; @@ -1090,7 +1258,6 @@ main() { done [ -z "$cmd" ] && cmd="interactive" - # Derive URLs after CLI parsing (--url may have changed NITRO_SITE_URL) if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then NITRO_SITE_URL="$APP_URL"; fi case "${NITRO_SITE_URL:-}" in https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;; @@ -1105,7 +1272,6 @@ main() { 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}" - # Log redirect exec > >(tee -a "$LOG_FILE") 2>&1 case "$cmd" in @@ -1124,6 +1290,7 @@ main() { gitlog) cmd_gitlog ;; compare) cmd_compare ;; schedule) cmd_schedule ;; + stash) cmd_stash ;; help) show_help ;; esac