You've already forked Atomcms-edit
2c079c125c
- Add IN_INTERACTIVE flag to detect interactive mode - die() now returns to menu instead of exiting in interactive mode - Update options (1-6) run with 'set +e' to prevent crash on errors - 'set -e' restored after update completes - Errors are logged and shown as warnings, menu keeps running
1601 lines
81 KiB
Bash
Executable File
1601 lines
81 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ╔══════════════════════════════════════════════════════════════════════════════╗
|
|
# ║ ║
|
|
# ║ ███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗ ███████╗██╗ ██╗ ║
|
|
# ║ ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗██╔════╝██║ ██║ ║
|
|
# ║ ██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║███████╗██║ ██║ ║
|
|
# ║ ██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║ ██║╚════██║██║ ██║ ║
|
|
# ║ ██║ ╚████║███████╗╚██████╔╝██║ ██║╚██████╔╝███████║╚██████╔╝ ║
|
|
# ║ ╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ║
|
|
# ║ ║
|
|
# ║ NITRO V3 — Enterprise Deployment & Management System ║
|
|
# ║ Version 6.0.0 — Build 20260625 ║
|
|
# ║ ║
|
|
# ╚══════════════════════════════════════════════════════════════════════════════╝
|
|
|
|
# =============================================================================
|
|
# CONFIGURATION & CONSTANTS
|
|
# =============================================================================
|
|
readonly SCRIPT_VERSION="6.0.0"
|
|
readonly SCRIPT_BUILD="20260625"
|
|
readonly SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly SCRIPT_PID=$$
|
|
readonly NITRO_MIN_BASH_VERSION="4.0"
|
|
|
|
# --- 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_UNDERLINE="\033[4m"
|
|
C_BLINK="\033[5m"
|
|
C_ITALIC="\033[3m"
|
|
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_BG_RED="\033[41m"
|
|
C_BG_GREEN="\033[42m"
|
|
C_BG_YELLOW="\033[43m"
|
|
C_BG_BLUE="\033[44m"
|
|
C_BG_MAGENTA="\033[45m"
|
|
C_BG_CYAN="\033[46m"
|
|
C_BG_WHITE="\033[47m"
|
|
C_GOLD="\033[38;5;220m"
|
|
C_ORANGE="\033[38;5;208m"
|
|
C_PURPLE="\033[38;5;135m"
|
|
C_TEAL="\033[38;5;30m"
|
|
else
|
|
C_RESET="" C_BOLD="" C_DIM="" C_UNDERLINE="" C_BLINK="" C_ITALIC=""
|
|
C_RED="" C_GREEN="" C_YELLOW="" C_BLUE="" C_MAGENTA="" C_CYAN="" C_WHITE=""
|
|
C_BG_RED="" C_BG_GREEN="" C_BG_YELLOW="" C_BG_BLUE="" C_BG_MAGENTA="" C_BG_CYAN="" C_BG_WHITE=""
|
|
C_GOLD="" C_ORANGE="" C_PURPLE="" C_TEAL=""
|
|
fi
|
|
|
|
# --- Emojis (fallback to ASCII on non-UTF8) ----------------------------------
|
|
if locale charmap 2>/dev/null | grep -qi utf; then
|
|
E_CHECK="✔" E_CROSS="✘" E_WARN="⚠" E_ARROW="➜" E_DOT="●" E_STAR="★"
|
|
E_CLOCK="⏱" E_GEAR="⚙" E_ROCKET="🚀" E_SHIELD="🛡" E_DB="🗄"
|
|
E_FOLDER="📁" E_GIT="🔀" E_WRENCH="🔧" E_CHART="📊" E_LIGHTNING="⚡"
|
|
E_CLOCK2="🕐" E_BROOM="🧹" E_EYE="👁" E_LOCK="🔒" E_HEART="♥"
|
|
E_CLOUD="☁" E_FIRE="🔥" E_PACKAGE="📦" E_BULLET="•" E_GLOBE="🌐"
|
|
E_CONSOLE="🖥" E_SERVER="🖧" E_DATABASE="💾" E_DIAG="🩺"
|
|
else
|
|
E_CHECK="[OK]" E_CROSS="[FAIL]" E_WARN="[!]" E_ARROW="->" E_DOT="*"
|
|
E_STAR="*" E_CLOCK="[T]" E_GEAR="[G]" E_ROCKET="[R]" E_SHIELD="[S]" E_DB="[DB]"
|
|
E_FOLDER="[D]" E_GIT="[G]" E_WRENCH="[W]" E_CHART="[C]" E_LIGHTNING="[!]"
|
|
E_CLOCK2="[T]" E_BROOM="[C]" E_EYE="[E]" E_LOCK="[L]" E_HEART="[H]"
|
|
E_CLOUD="[C]" E_FIRE="[F]" E_PACKAGE="[P]" E_BULLET="-" E_GLOBE="[W]"
|
|
E_CONSOLE="[M]" E_SERVER="[S]" E_DATABASE="[D]" E_DIAG="[X]"
|
|
fi
|
|
|
|
# --- Timing -------------------------------------------------------------------
|
|
readonly START_TIME=$(date +%s)
|
|
|
|
# --- State --------------------------------------------------------------------
|
|
STEP_NUM=0
|
|
STEP_TOTAL=8
|
|
HAD_UPDATES=false
|
|
NITRO_BUILT=false
|
|
DRY_RUN=false
|
|
VERBOSE=false
|
|
QUIET=false
|
|
FORCE=false
|
|
SELECTIVE_UPDATE=""
|
|
ROLLBACK_NEEDED=false
|
|
LAST_BACKUP=""
|
|
NOTIFY_FAILED=false
|
|
ERRORS=0
|
|
WARNINGS=0
|
|
UPDATED_REPOS=()
|
|
|
|
# --- Load .env ----------------------------------------------------------------
|
|
if [ -f "$SCRIPT_DIR/.env" ]; then
|
|
set -a
|
|
. "$SCRIPT_DIR/.env"
|
|
set +a
|
|
fi
|
|
|
|
# --- Logging ------------------------------------------------------------------
|
|
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"
|
|
|
|
# --- Notifications ------------------------------------------------------------
|
|
NOTIFY_URL="${NITRO_NOTIFY_URL:-}"
|
|
|
|
# =============================================================================
|
|
# TERMINAL CAPABILITIES
|
|
# =============================================================================
|
|
TERM_COLS=$(tput cols 2>/dev/null || echo 80)
|
|
TERM_LINES=$(tput lines 2>/dev/null || echo 24)
|
|
|
|
cursor_hide() { tput civis 2>/dev/null || true; }
|
|
cursor_show() { tput cnorm 2>/dev/null || true; }
|
|
clear_line() { printf "\r\033[K"; }
|
|
|
|
# =============================================================================
|
|
# UI COMPONENTS
|
|
# =============================================================================
|
|
|
|
show_banner() {
|
|
clear
|
|
echo -e "${C_CYAN}"
|
|
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 " ║ ║"
|
|
echo -e " ║ ${C_WHITE}${C_BOLD} Updater by Remco — epicnabbo.nl${C_CYAN} ║"
|
|
echo " ║ ║"
|
|
echo " ╠═══════════════════════════════════════════════════════════════════════════╣"
|
|
echo -e " ║ ${C_DIM}Enterprise Deployment & Management System v${SCRIPT_VERSION}${C_CYAN} ║"
|
|
echo " ╚═══════════════════════════════════════════════════════════════════════════╝"
|
|
echo -e "${C_RESET}"
|
|
echo -e " ${C_DIM} $(date '+%A, %d %B %Y — %H:%M:%S')${C_RESET}"
|
|
echo ""
|
|
}
|
|
|
|
show_mini_banner() {
|
|
echo -e "${C_CYAN}╔══════════════════════════════════════════════════════════════════╗${C_RESET}"
|
|
echo -e "${C_CYAN}║${C_RESET} ${E_ROCKET} ${C_GOLD}${C_BOLD}Emulator/Nitro-V3/Nitro-V3-Render Updater${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e "${C_CYAN}║${C_RESET} ${C_DIM}by Remco — epicnabbo.nl v${SCRIPT_VERSION}${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e "${C_CYAN}╚══════════════════════════════════════════════════════════════════╝${C_RESET}"
|
|
echo ""
|
|
}
|
|
|
|
box_top() {
|
|
local title="${1:-}" width="${2:-60}"
|
|
if [ -n "$title" ]; then
|
|
local pad=$((width - ${#title} - 6))
|
|
[ $pad -lt 0 ] && pad=0
|
|
printf "${C_CYAN}╔═══ ${C_BOLD}%s${C_RESET}${C_CYAN}" "$title"
|
|
printf '═%.0s' $(seq 1 $pad 2>/dev/null || echo 1)
|
|
echo -e "╗${C_RESET}"
|
|
else
|
|
printf "${C_CYAN}╔"
|
|
printf '═%.0s' $(seq 1 $width)
|
|
echo -e "╗${C_RESET}"
|
|
fi
|
|
}
|
|
|
|
box_line() {
|
|
local text="${1:-}" width="${2:-60}"
|
|
local clean_text=$(echo -e "$text" | sed 's/\x1b\[[0-9;]*m//g')
|
|
local pad=$((width - ${#clean_text} - 4))
|
|
[ $pad -lt 0 ] && pad=0
|
|
printf "${C_CYAN}║${C_RESET} %s%*s${C_CYAN}║${C_RESET}\n" "$text" $pad ""
|
|
}
|
|
|
|
box_kv() {
|
|
local key="${1}" val="${2}" width="${3:-60}"
|
|
local clean_key=$(echo -e "$key" | sed 's/\x1b\[[0-9;]*m//g')
|
|
local clean_val=$(echo -e "$val" | sed 's/\x1b\[[0-9;]*m//g')
|
|
local pad=$((width - ${#clean_key} - ${#clean_val} - 6))
|
|
[ $pad -lt 0 ] && pad=0
|
|
printf "${C_CYAN}║${C_RESET} %s%*s%s ${C_CYAN}║${C_RESET}\n" "$key" $pad "" "$val"
|
|
}
|
|
|
|
box_bottom() {
|
|
local width="${1:-60}"
|
|
printf "${C_CYAN}╚"
|
|
printf '═%.0s' $(seq 1 $width)
|
|
echo -e "╝${C_RESET}"
|
|
}
|
|
|
|
box_sep() {
|
|
local width="${1:-60}"
|
|
printf "${C_CYAN}╠${C_RESET}"
|
|
printf '═%.0s' $(seq 1 $width)
|
|
echo -e "╣${C_RESET}"
|
|
}
|
|
|
|
step() {
|
|
STEP_NUM=$((STEP_NUM + 1))
|
|
echo ""
|
|
echo -e " ${C_BG_CYAN}${C_WHITE}${C_BOLD} Step ${STEP_NUM}/${STEP_TOTAL} │ $1 ${C_RESET}"
|
|
echo ""
|
|
}
|
|
|
|
info() { echo -e " ${C_BLUE}${E_ARROW}${C_RESET} $1"; log_write "INFO" "$1"; }
|
|
info_indent() { echo -e " ${C_DIM}$1${C_RESET}"; log_write "INFO" "$1"; }
|
|
ok() { echo -e " ${C_GREEN}${E_CHECK}${C_RESET} $1"; log_write "OK" "$1"; }
|
|
warn() { echo -e " ${C_YELLOW}${E_WARN}${C_RESET} $1"; WARNINGS=$((WARNINGS + 1)); log_write "WARN" "$1"; }
|
|
fail() { echo -e " ${C_RED}${E_CROSS}${C_RESET} $1"; log_write "FAIL" "$1"; }
|
|
debug() { [ "$VERBOSE" = true ] && echo -e " ${C_DIM}${E_DOT} [DEBUG] $1${C_RESET}"; log_write "DEBUG" "$1"; }
|
|
|
|
IN_INTERACTIVE=false
|
|
|
|
die() {
|
|
echo ""
|
|
echo -e " ${C_BG_RED}${C_WHITE}${C_BOLD} FATAL ERROR ${C_RESET}"
|
|
echo -e " ${C_RED}${E_CROSS} $1${C_RESET}"
|
|
log_write "FATAL" "$1"
|
|
if [ "$IN_INTERACTIVE" = true ]; then
|
|
WARNINGS=$((WARNINGS + 1))
|
|
echo -e " ${C_YELLOW}Returning to menu...${C_RESET}"
|
|
return 1 2>/dev/null || true
|
|
fi
|
|
cleanup_notify_failure "$1"
|
|
cursor_show
|
|
exit 1
|
|
}
|
|
|
|
log_write() {
|
|
local level="$1" msg="$2"
|
|
printf "[%s] [%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$level" "$msg" >> "$LOG_FILE" 2>/dev/null || true
|
|
}
|
|
|
|
# --- Spinner ------------------------------------------------------------------
|
|
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} ${C_BOLD}%s${C_RESET} " "$msg"
|
|
i=$(( (i + 1) % ${#frames[@]} ))
|
|
sleep 0.1
|
|
done
|
|
) &
|
|
SPINNER_PID=$!
|
|
disown "$SPINNER_PID" 2>/dev/null || true
|
|
}
|
|
|
|
spinner_stop() {
|
|
local status="${1:-ok}"
|
|
if [ -n "$SPINNER_PID" ] && kill -0 "$SPINNER_PID" 2>/dev/null; then
|
|
kill "$SPINNER_PID" 2>/dev/null || true
|
|
wait "$SPINNER_PID" 2>/dev/null || true
|
|
SPINNER_PID=""
|
|
fi
|
|
clear_line
|
|
cursor_show
|
|
case "$status" in
|
|
ok) printf "\r ${C_GREEN}${E_CHECK}${C_RESET} Done\n" ;;
|
|
warn) printf "\r ${C_YELLOW}${E_WARN}${C_RESET} Done with warnings\n" ;;
|
|
fail) printf "\r ${C_RED}${E_CROSS}${C_RESET} Failed\n" ;;
|
|
skip) printf "\r ${C_DIM}— Skipped${C_RESET}\n" ;;
|
|
esac
|
|
}
|
|
|
|
# --- Progress Bar -------------------------------------------------------------
|
|
progress_bar() {
|
|
local current="${1:-0}" total="${2:-100}" width="${3:-40}"
|
|
local pct=$((current * 100 / total))
|
|
local filled=$((current * width / total))
|
|
local empty=$((width - filled))
|
|
printf "\r ${C_CYAN}[${C_RESET}"
|
|
printf '%0.s█' $(seq 1 $filled 2>/dev/null || echo 1)
|
|
printf '%0.s░' $(seq 1 $empty 2>/dev/null || echo 1)
|
|
printf "${C_CYAN}]${C_RESET} ${C_BOLD}%3d%%${C_RESET}" "$pct"
|
|
}
|
|
|
|
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}$msg${C_RESET} $hint "
|
|
read -r reply
|
|
reply="${reply:-$default}"
|
|
[[ "$reply" =~ ^[Yy] ]]
|
|
}
|
|
|
|
select_menu() {
|
|
local title="$1"
|
|
shift
|
|
local options=("$@")
|
|
echo ""
|
|
echo -e " ${C_BOLD}${C_CYAN}$title${C_RESET}"
|
|
echo ""
|
|
local i=1
|
|
for opt in "${options[@]}"; do
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} $opt"
|
|
i=$((i + 1))
|
|
done
|
|
echo ""
|
|
echo -en " ${C_CYAN}Select [1-$((i-1))]${C_RESET}: "
|
|
local choice
|
|
read -r choice
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -lt "$i" ]; then
|
|
echo "$choice"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# --- Notifications -----------------------------------------------------------
|
|
notify() {
|
|
local status="$1" message="$2"
|
|
[ -z "$NOTIFY_URL" ] && return 0
|
|
local elapsed=$(($(date +%s) - START_TIME))
|
|
local elapsed_fmt
|
|
elapsed_fmt=$(printf '%dm %ds' $((elapsed / 60)) $((elapsed % 60)))
|
|
local color="good"
|
|
[ "$status" != "SUCCESS" ] && color="danger"
|
|
[ "$status" = "WARNING" ] && color="warning"
|
|
curl -s -o /dev/null -X POST "$NOTIFY_URL" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"text\":\"[Nitro Update v${SCRIPT_VERSION}] $status\",\"attachments\":[{\"color\":\"$color\",\"fields\":[{\"title\":\"Status\",\"value\":\"$message\",\"short\":true},{\"title\":\"Duration\",\"value\":\"$elapsed_fmt\",\"short\":true},{\"title\":\"Server\",\"value\":\"$(hostname)\",\"short\":true},{\"title\":\"Log\",\"value\":\"$LOG_FILE\",\"short\":false}]}]}" 2>/dev/null || true
|
|
}
|
|
|
|
cleanup_notify_failure() {
|
|
$NOTIFY_FAILED && return
|
|
NOTIFY_FAILED=true
|
|
notify "FAILED" "$1"
|
|
}
|
|
|
|
# =============================================================================
|
|
# ROLLBACK SYSTEM
|
|
# =============================================================================
|
|
rollback_db() {
|
|
[ -z "$LAST_BACKUP" ] && { warn "No backup file known — cannot rollback DB."; return 1; }
|
|
[ ! -f "$LAST_BACKUP" ] && { warn "Backup file not found: $LAST_BACKUP"; return 1; }
|
|
info "Rolling back database from: $LAST_BACKUP"
|
|
mariadb $MYSQL_CRED "$DB_NAME" < "$LAST_BACKUP" && ok "Database restored." || warn "Rollback failed."
|
|
}
|
|
|
|
cleanup_on_exit() {
|
|
local exit_code=$?
|
|
cursor_show
|
|
if [ $exit_code -ne 0 ] && [ "$ROLLBACK_NEEDED" = true ]; then
|
|
echo ""
|
|
echo -e " ${C_BG_RED}${C_WHITE}${C_BOLD} UPDATE FAILED — INITIATING ROLLBACK ${C_RESET}"
|
|
echo ""
|
|
rollback_db
|
|
fi
|
|
[ $exit_code -ne 0 ] && cleanup_notify_failure "Script exited with code $exit_code"
|
|
}
|
|
|
|
trap cleanup_on_exit EXIT
|
|
trap 'echo ""; cursor_show; die "Interrupted by user (SIGINT)"' SIGINT
|
|
trap 'cursor_show; die "Terminated (SIGTERM)"' SIGTERM
|
|
|
|
# =============================================================================
|
|
# PREFLIGHT
|
|
# =============================================================================
|
|
preflight() {
|
|
if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then
|
|
NITRO_SITE_URL="$APP_URL"
|
|
info "Using APP_URL from .env: $NITRO_SITE_URL"
|
|
fi
|
|
if [ -z "${NITRO_SITE_URL:-}" ]; then
|
|
read -r -p " Enter your site URL (e.g. https://example.com): " NITRO_SITE_URL
|
|
NITRO_SITE_URL="${NITRO_SITE_URL%/}"
|
|
[ -z "$NITRO_SITE_URL" ] && die "NITRO_SITE_URL is required"
|
|
export NITRO_SITE_URL
|
|
fi
|
|
if [ -z "${NITRO_BRANCH:-}" ]; then
|
|
read -r -p " Enter branch [main]: " NITRO_BRANCH
|
|
NITRO_BRANCH="${NITRO_BRANCH:-main}"
|
|
export NITRO_BRANCH
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# PRE-FLIGHT CHECKS
|
|
# =============================================================================
|
|
preflight_checks() {
|
|
step "System Diagnostics"
|
|
|
|
info "Checking required commands..."
|
|
local REQUIRED_CMDS=(git mariadb mariadb-dump mvn node python3 yarn)
|
|
local MISSING_CMDS=()
|
|
for cmd in "${REQUIRED_CMDS[@]}"; do
|
|
if command -v "$cmd" &>/dev/null; then
|
|
debug " Found: $cmd ($(command -v "$cmd"))"
|
|
else
|
|
MISSING_CMDS+=("$cmd")
|
|
fi
|
|
done
|
|
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
|
|
die "Missing required commands: ${MISSING_CMDS[*]}"
|
|
fi
|
|
ok "All ${#REQUIRED_CMDS[@]} required commands available"
|
|
|
|
info "Checking directories..."
|
|
REQUIRED_DIRS=(
|
|
"$EMULATOR_DIR/Emulator"
|
|
"$NITRO_RENDERER"
|
|
"$NITRO_CLIENT"
|
|
"$NITRO_SRC_DIR"
|
|
)
|
|
for dir in "${REQUIRED_DIRS[@]}"; do
|
|
[ -d "$dir" ] || die "Required directory not found: $dir"
|
|
done
|
|
ok "All ${#REQUIRED_DIRS[@]} directories exist"
|
|
|
|
info "Checking disk space..."
|
|
AVAIL_KB=$(df --output=avail "$EMULATOR_DIR" 2>/dev/null | tail -1)
|
|
AVAIL_GB=$(( AVAIL_KB / 1024 / 1024 ))
|
|
[ "$AVAIL_GB" -lt "$MIN_DISK_GB" ] && die "Only ${AVAIL_GB}GB free (min ${MIN_DISK_GB}GB required)"
|
|
ok "${AVAIL_GB}GB disk space available"
|
|
|
|
info "Checking system memory..."
|
|
TOTAL_MEM=$(free -m 2>/dev/null | awk '/^Mem:/{print $2}' || echo "0")
|
|
AVAIL_MEM=$(free -m 2>/dev/null | awk '/^Mem:/{print $7}' || echo "0")
|
|
[ "$TOTAL_MEM" -gt 0 ] && ok "Memory: ${AVAIL_MEM}MB available / ${TOTAL_MEM}MB total" || warn "Could not determine memory"
|
|
|
|
info "Checking CPU load..."
|
|
CPU_LOAD=$(awk '{printf "%.1f", $1}' /proc/loadavg 2>/dev/null || echo "N/A")
|
|
CPU_CORES=$(nproc 2>/dev/null || echo "?")
|
|
ok "CPU Load: $CPU_LOAD / $CPU_CORES cores"
|
|
|
|
info "Checking database connection..."
|
|
mariadb $MYSQL_CRED -e "SELECT 1" "$DB_NAME" &>/dev/null || die "Cannot connect to database $DB_NAME on $DB_HOST:$DB_PORT"
|
|
DB_SIZE=$(mariadb $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 "?")
|
|
DB_TABLES=$(mariadb $MYSQL_CRED -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME'" "$DB_NAME" 2>/dev/null || echo "?")
|
|
ok "Database OK — ${DB_SIZE}MB, ${DB_TABLES} tables"
|
|
|
|
info "Checking Laravel..."
|
|
if [ -f "$SCRIPT_DIR/artisan" ]; then
|
|
LARAVEL_VER=$(php "$SCRIPT_DIR/artisan" --version 2>/dev/null | head -1 || echo "unknown")
|
|
ok "Laravel: $LARAVEL_VER"
|
|
else
|
|
warn "Laravel artisan not found"
|
|
fi
|
|
|
|
info "Checking Redis..."
|
|
if command -v redis-cli &>/dev/null; then
|
|
redis-cli ping 2>/dev/null | grep -q PONG && ok "Redis: Connected" || warn "Redis: Not responding"
|
|
else
|
|
warn "redis-cli not found"
|
|
fi
|
|
|
|
info "Checking Nginx..."
|
|
if command -v nginx &>/dev/null; then
|
|
nginx -t 2>/dev/null && ok "Nginx config valid" || warn "Nginx config has issues"
|
|
fi
|
|
|
|
if [[ "${NITRO_SITE_URL:-}" == https://* ]]; then
|
|
info "Checking SSL certificate..."
|
|
SSL_DOMAIN=$(echo "$NITRO_SITE_URL" | sed 's|https://||' | sed 's|/.*||')
|
|
SSL_EXPIRY=$(echo | openssl s_client -servername "$SSL_DOMAIN" -connect "$SSL_DOMAIN:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2 || echo "")
|
|
if [ -n "$SSL_EXPIRY" ]; then
|
|
SSL_EXPIRY_EPOCH=$(date -d "$SSL_EXPIRY" +%s 2>/dev/null || echo "0")
|
|
SSL_DAYS_LEFT=$(( (SSL_EXPIRY_EPOCH - $(date +%s)) / 86400 ))
|
|
if [ "$SSL_DAYS_LEFT" -gt 30 ]; then
|
|
ok "SSL valid for $SSL_DAYS_LEFT days"
|
|
elif [ "$SSL_DAYS_LEFT" -gt 0 ]; then
|
|
warn "SSL expires in $SSL_DAYS_LEFT days!"
|
|
else
|
|
fail "SSL certificate EXPIRED!"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
info "Checking git repositories..."
|
|
local REPOS=("Emulator:$EMULATOR_DIR/Emulator" "Nitro-V3:$NITRO_CLIENT" "Nitro_Render_V3:$NITRO_RENDERER")
|
|
for repo_entry in "${REPOS[@]}"; do
|
|
local name="${repo_entry%%:*}" path="${repo_entry##*:}"
|
|
if [ -d "$path/.git" ]; then
|
|
local branch=$(cd "$path" && git branch --show-current 2>/dev/null || echo "?")
|
|
local commit=$(cd "$path" && git log --oneline -1 2>/dev/null | cut -c1-8 || echo "?")
|
|
local dirty=$(cd "$path" && git status --porcelain 2>/dev/null | wc -l)
|
|
[ "$dirty" -gt 0 ] && warn "$name has $dirty uncommitted changes" || ok "$name is clean ($branch @ $commit)"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# DRY-RUN DISPLAY
|
|
# =============================================================================
|
|
dry_run_display() {
|
|
echo ""
|
|
box_top "DRY-RUN SUMMARY" 56
|
|
box_line "Site: ${NITRO_SITE_URL:-?}" 56
|
|
box_line "Branch: ${NITRO_BRANCH:-main}" 56
|
|
box_line "Emulator: $EMULATOR_DIR/Emulator" 56
|
|
box_line "Nitro-V3: $NITRO_CLIENT" 56
|
|
box_line "Renderer: $NITRO_RENDERER" 56
|
|
box_line "Mode: ${SELECTIVE_UPDATE:-full}" 56
|
|
box_line "Log: $LOG_FILE" 56
|
|
box_bottom 56
|
|
echo ""
|
|
info "Dry-run complete. No changes were made."
|
|
}
|
|
|
|
# =============================================================================
|
|
# GIT HELPERS
|
|
# =============================================================================
|
|
detect_available_branches() {
|
|
local repos=("$EMULATOR_DIR/Emulator" "$NITRO_CLIENT" "$NITRO_RENDERER")
|
|
local result=()
|
|
for repo in "${repos[@]}"; do
|
|
[ -d "$repo/.git" ] || continue
|
|
local branches
|
|
branches=$(cd "$repo" && git branch -r 2>/dev/null | \
|
|
grep -v '\->' | sed 's/^[[:space:]]*//' | sed 's/^origin\///' | grep -v '^HEAD$' | sort -u)
|
|
while IFS= read -r b; do
|
|
[ -z "$b" ] && continue
|
|
local found=false
|
|
for ab in "${result[@]}"; do
|
|
[ "$ab" = "$b" ] && found=true && break
|
|
done
|
|
$found || result+=("$b")
|
|
done <<< "$branches"
|
|
done
|
|
printf '%s\n' "${result[@]}"
|
|
}
|
|
|
|
detect_branch() {
|
|
local repo="$1" preferred="$2"
|
|
cd "$repo" 2>/dev/null || { echo "main"; return; }
|
|
local variants=("$preferred" "${preferred^}" "development" "Development" "main" "master")
|
|
for v in "${variants[@]}"; do
|
|
git rev-parse --verify "$v" &>/dev/null && { echo "$v"; return; }
|
|
done
|
|
local remote_branch=$(git branch -r 2>/dev/null | grep -oP "origin/\K${preferred}" | head -1 || echo "")
|
|
[ -n "$remote_branch" ] && { echo "$remote_branch"; return; }
|
|
echo "$(git branch --show-current 2>/dev/null || echo "main")"
|
|
}
|
|
|
|
git_update() {
|
|
local repo="$1" branch="$2"
|
|
cd "$repo" || { warn "Cannot access $repo"; return 1; }
|
|
git stash --include-untracked 2>/dev/null || true
|
|
local old_head
|
|
old_head=$(git rev-parse HEAD 2>/dev/null || echo "")
|
|
local resolved_branch
|
|
resolved_branch=$(detect_branch "$repo" "$branch")
|
|
if [ "$resolved_branch" != "$branch" ]; then
|
|
info "Branch '$branch' not in $(basename "$repo"), using '$resolved_branch' instead"
|
|
fi
|
|
if ! git checkout "$resolved_branch" 2>/dev/null; then
|
|
local fallback=$(git branch -r 2>/dev/null | head -1 | sed 's/origin\///' | xargs || echo "main")
|
|
warn "Could not checkout '$resolved_branch', trying '$fallback'"
|
|
if ! git checkout "$fallback" 2>/dev/null; then
|
|
warn "No suitable branch found in $(basename "$repo"), skipping"
|
|
return 1
|
|
fi
|
|
resolved_branch="$fallback"
|
|
fi
|
|
if ! git pull origin "$resolved_branch" 2>/dev/null && ! git pull 2>/dev/null; then
|
|
warn "Git pull failed in $(basename "$repo") (branch: $resolved_branch)"
|
|
return 1
|
|
fi
|
|
[ "$(git rev-parse HEAD 2>/dev/null)" != "$old_head" ]
|
|
}
|
|
|
|
clean_node_modules() {
|
|
rm -rf node_modules 2>/dev/null || sudo rm -rf node_modules 2>/dev/null || true
|
|
if [ "$(stat -c '%U' . 2>/dev/null)" != "$(whoami)" ] && command -v sudo &>/dev/null; then
|
|
sudo chown -R "$(whoami)":"$(whoami)" . 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# UPDATE FUNCTIONS
|
|
# =============================================================================
|
|
update_emulator() {
|
|
step "Update & Build Emulator"
|
|
if git_update "$EMULATOR_DIR/Emulator" "$NITRO_BRANCH"; then
|
|
info "New commits detected"
|
|
HAD_UPDATES=true
|
|
ROLLBACK_NEEDED=true
|
|
UPDATED_REPOS+=("Emulator")
|
|
info "Creating database backup..."
|
|
mkdir -p "$BACKUP_DIR"
|
|
BACKUP_FILE="$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).sql"
|
|
spinner_start "Backing up database..."
|
|
if mariadb-dump $MYSQL_CRED --force --skip-lock-tables --routines --events --triggers "$DB_NAME" > "$BACKUP_FILE" 2>/dev/null; then
|
|
LAST_BACKUP="$BACKUP_FILE"
|
|
BK_SIZE=$(stat -c%s "$BACKUP_FILE" 2>/dev/null || echo 0)
|
|
if [ "$BK_SIZE" -lt 1024 ]; then rm -f "$BACKUP_FILE"; spinner_stop fail; die "Backup too small — aborting"; fi
|
|
spinner_stop ok
|
|
ok "Backup saved: $(numfmt --to=iec "$BK_SIZE") — $(basename "$BACKUP_FILE")"
|
|
else
|
|
spinner_stop fail
|
|
die "Database backup failed"
|
|
fi
|
|
info "Checking for new SQL files..."
|
|
if [ -d "$SQL_DIR" ]; then
|
|
SQL_COUNT=0
|
|
while IFS= read -r -d '' sql_file; do
|
|
info_indent "Importing: $(basename "$sql_file")"
|
|
mariadb $MYSQL_CRED --force "$DB_NAME" < "$sql_file" 2>/dev/null || warn "Import failed for $(basename "$sql_file")"
|
|
SQL_COUNT=$((SQL_COUNT + 1))
|
|
done < <(find "$SQL_DIR" -name '*.sql' -mmin -10 -not -path "$BACKUP_DIR/*" -print0 2>/dev/null)
|
|
[ "$SQL_COUNT" -gt 0 ] && ok "$SQL_COUNT SQL file(s) imported" || info "No new SQL files"
|
|
fi
|
|
info "Building emulator..."
|
|
cd "$EMULATOR_DIR/Emulator"
|
|
spinner_start "Compiling Java..."
|
|
mvn package -q 2>&1 | tail -5 && spinner_stop ok || { spinner_stop fail; die "Maven build failed"; }
|
|
ok "Build complete"
|
|
JAR_FILE=$(find target -maxdepth 1 -name 'Habbo-*-jar-with-dependencies.jar' -printf '%T@ %p\n' 2>/dev/null | sort -rn | sed -n '1s/^[0-9.]* //p' | xargs basename 2>/dev/null || echo "")
|
|
[ -z "$JAR_FILE" ] && die "No jar file found"
|
|
ok "Jar: $JAR_FILE"
|
|
info "Updating launch script..."
|
|
cd target/
|
|
cat << EOF > emulator
|
|
#!/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 -Xmx2G -jar $JAR_FILE
|
|
EOF
|
|
chmod +x emulator
|
|
ok "Launch script updated"
|
|
echo "$(date +%Y-%m-%d\ %H:%M:%S)|BACKUP|$BACKUP_FILE|$BK_SIZE" >> "$BACKUP_DIR/.history" 2>/dev/null || true
|
|
ROLLBACK_NEEDED=false
|
|
else
|
|
info "Already up to date, skipping"
|
|
fi
|
|
}
|
|
|
|
update_renderer() {
|
|
step "Update Nitro_Render_V3"
|
|
if git_update "$NITRO_RENDERER" "$NITRO_BRANCH"; then
|
|
info "New commits detected"
|
|
HAD_UPDATES=true
|
|
UPDATED_REPOS+=("Nitro_Render_V3")
|
|
spinner_start "Installing dependencies..."
|
|
yarn install --frozen-lockfile 2>&1 || { info "Retrying..."; clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; }
|
|
spinner_stop ok
|
|
ok "Dependencies installed"
|
|
else
|
|
info "Already up to date, skipping"
|
|
fi
|
|
}
|
|
|
|
update_client() {
|
|
step "Update & Build Nitro-V3"
|
|
if git_update "$NITRO_CLIENT" "$NITRO_BRANCH"; then
|
|
info "New commits detected"
|
|
HAD_UPDATES=true
|
|
NITRO_BUILT=true
|
|
UPDATED_REPOS+=("Nitro-V3")
|
|
spinner_start "Installing dependencies..."
|
|
yarn install --frozen-lockfile 2>&1 || { info "Retrying..."; clean_node_modules && yarn install 2>&1; } || { spinner_stop fail; die "yarn install failed"; }
|
|
spinner_stop ok
|
|
ok "Dependencies installed"
|
|
info "Building Nitro-V3..."
|
|
spinner_start "Building frontend..."
|
|
yarn build 2>&1 | tail -3 && spinner_stop ok || { spinner_stop fail; die "yarn build failed"; }
|
|
ok "Build complete"
|
|
else
|
|
info "Already up to date, skipping build"
|
|
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"
|
|
}
|
|
|
|
sync_configs() {
|
|
step "Sync Configurations"
|
|
mkdir -p "$GAMEDATA_CONF_DIR"
|
|
local MERGE_SCRIPT="$SCRIPT_DIR/scripts/merge-config.cjs"
|
|
local NITRO_DIST_CONFIG_DIR="$NITRO_CLIENT/dist/configuration"
|
|
for dir in "$NITRO_SRC_DIR" "$GAMEDATA_CONF_DIR"; do
|
|
[ -d "$dir" ] && [ ! -w "$dir" ] && command -v sudo &>/dev/null && sudo chown -R "$(whoami)":"$(whoami)" "$dir" 2>/dev/null || true
|
|
done
|
|
local EXAMPLE_COUNT=0
|
|
for example_file in "$NITRO_SRC_DIR"/*.example; do
|
|
[ -f "$example_file" ] || continue
|
|
local base=$(basename "$example_file")
|
|
local target_name="${base%.example}"
|
|
case "$target_name" in *.*) ;; *) target_name="$target_name.json" ;; esac
|
|
node "$MERGE_SCRIPT" "$example_file" "$NITRO_SRC_DIR/$target_name" 2>/dev/null
|
|
node "$MERGE_SCRIPT" "$example_file" "$GAMEDATA_CONF_DIR/$target_name" 2>/dev/null
|
|
[ -d "$NITRO_DIST_CONFIG_DIR" ] && cp "$NITRO_SRC_DIR/$target_name" "$NITRO_DIST_CONFIG_DIR/$target_name" 2>/dev/null || true
|
|
EXAMPLE_COUNT=$((EXAMPLE_COUNT + 1))
|
|
done
|
|
ok "$EXAMPLE_COUNT .example file(s) processed"
|
|
info "Applying critical config URLs..."
|
|
FORCE_CONFIG_SCRIPT=$(cat << 'PYEOF'
|
|
import json, sys, os
|
|
paths = [
|
|
os.path.join(os.environ.get('NITRO_SRC_DIR', ''), 'renderer-config.json'),
|
|
os.path.join(os.environ.get('NITRO_DIST_CONFIG_DIR', ''), 'renderer-config.json'),
|
|
os.path.join(os.environ.get('NITRO_SRC_DIR', ''), 'ui-config.json'),
|
|
os.path.join(os.environ.get('NITRO_DIST_CONFIG_DIR', ''), 'ui-config.json'),
|
|
]
|
|
for path in paths:
|
|
if not os.path.exists(path): continue
|
|
with open(path, 'r') as f: data = json.load(f)
|
|
for key in ['image.library.url', 'hof.furni.url', 'gamedata.url', 'asset.url']:
|
|
env_val = os.environ.get(f'NITRO_{key.upper().replace(".", "_")}')
|
|
if env_val: data[key] = env_val
|
|
for key in ['api.url', 'socket.url', 'furni.asset.icon.url']:
|
|
if key in data:
|
|
env_val = os.environ.get(f'NITRO_{key.upper().replace(".", "_")}')
|
|
if env_val: data[key] = env_val
|
|
if 'gamedata.url' in data:
|
|
data['radio.url'] = data['gamedata.url'] + '/config/radio-stations.json5?t=%timestamp%'
|
|
data['soundboard.url'] = data['gamedata.url'] + '/config/soundboard-sounds.json5?t=%timestamp%'
|
|
if 'show.google.ads' in data: data['show.google.ads'] = False
|
|
with open(path, 'w') as f: json.dump(data, f, indent=(4 if 'dist' not in path else None), separators=(',', ':') if 'dist' in path else (',', ': '))
|
|
print(f' [OK] Fixed: {os.path.basename(path)}')
|
|
print(' [OK] All config URLs synced')
|
|
PYEOF
|
|
)
|
|
cd "$NITRO_CLIENT" && NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$NITRO_DIST_CONFIG_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" \
|
|
python3 -c "$FORCE_CONFIG_SCRIPT"
|
|
}
|
|
|
|
cleanup_phase() {
|
|
step "Cleanup"
|
|
info "Removing old logs..."
|
|
local LOG_COUNT=$(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
|
|
ok "Removed $LOG_COUNT old log(s)"
|
|
info "Managing backups (keeping max 5)..."
|
|
if [ -d "$BACKUP_DIR" ]; then
|
|
find "$BACKUP_DIR" -maxdepth 1 -name '*.sql' -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +6 | sed 's/^[0-9.]* //' | xargs -r rm -f || true
|
|
fi
|
|
if [ "$HAD_UPDATES" = true ]; then
|
|
yarn cache clean 2>/dev/null || true
|
|
find "$EMULATOR_DIR/Emulator/target" -maxdepth 1 -name 'Habbo-*-jar-with-dependencies.jar' -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +2 | sed 's/^[0-9.]* //' | xargs -r rm -f || true
|
|
fi
|
|
rm -rf /tmp/nitro-* 2>/dev/null || true
|
|
ok "Cleanup complete"
|
|
}
|
|
|
|
set_permissions() {
|
|
step "Set Permissions"
|
|
if command -v sudo &>/dev/null; then
|
|
for dir in "$NITRO_CLIENT" "$NITRO_RENDERER" "$EMULATOR_DIR" "$GAMEDATA_CONF_DIR"; do
|
|
[ -d "$dir" ] && sudo chown -R www-data:www-data "$dir" 2>/dev/null || true
|
|
done
|
|
ok "Permissions set to www-data:www-data"
|
|
else
|
|
warn "sudo not available, skipping chown"
|
|
fi
|
|
chmod -R 775 "$LOG_DIR" 2>/dev/null || true
|
|
chmod -R 775 "$BACKUP_DIR" 2>/dev/null || true
|
|
}
|
|
|
|
restart_services() {
|
|
step "Restart Services"
|
|
if [ "$HAD_UPDATES" = true ]; then
|
|
if systemctl cat "$EMULATOR_SERVICE" &>/dev/null && command -v sudo &>/dev/null; then
|
|
info "Restarting $EMULATOR_SERVICE..."
|
|
spinner_start "Restarting emulator..."
|
|
sudo systemctl restart "$EMULATOR_SERVICE" 2>/dev/null && spinner_stop ok || { spinner_stop fail; die "Failed to restart $EMULATOR_SERVICE"; }
|
|
elif command -v pm2 &>/dev/null; then
|
|
info "Restarting PM2..."
|
|
pm2 restart all 2>/dev/null || warn "PM2 restart had issues"
|
|
else
|
|
warn "No systemd or PM2 found — restart manually"
|
|
fi
|
|
if command -v nginx &>/dev/null && command -v sudo &>/dev/null; then
|
|
sudo nginx -t 2>/dev/null && sudo systemctl reload nginx 2>/dev/null && ok "Nginx reloaded" || warn "Nginx reload failed"
|
|
fi
|
|
if command -v redis-cli &>/dev/null; then
|
|
redis-cli FLUSHALL 2>/dev/null && ok "Redis cache flushed" || warn "Redis flush failed"
|
|
fi
|
|
else
|
|
info "No updates applied, no restart needed"
|
|
fi
|
|
}
|
|
|
|
health_check() {
|
|
step "Health Check & Validation"
|
|
if [ "$NITRO_BUILT" = true ]; then
|
|
if [ -d "$NITRO_CLIENT/dist/assets" ]; then
|
|
local ASSET_COUNT=$(find "$NITRO_CLIENT/dist/assets" -type f 2>/dev/null | wc -l)
|
|
local ASSET_SIZE=$(du -sh "$NITRO_CLIENT/dist/assets" 2>/dev/null | cut -f1 || echo "?")
|
|
ok "Nitro-V3 built: $ASSET_COUNT assets ($ASSET_SIZE)"
|
|
else
|
|
fail "Nitro-V3 build assets missing!"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
fi
|
|
if [ "$HAD_UPDATES" = true ] && systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then
|
|
info "Waiting for $EMULATOR_SERVICE..."
|
|
HEALTHY=false
|
|
for i in $(seq 1 "$HEALTH_RETRIES"); do
|
|
sleep "$HEALTH_INTERVAL"
|
|
STATUS=$(systemctl is-active "$EMULATOR_SERVICE" 2>/dev/null || echo "unknown")
|
|
[ "$STATUS" = "active" ] && { HEALTHY=true; break; }
|
|
done
|
|
[ "$HEALTHY" = true ] && ok "$EMULATOR_SERVICE is active" || { fail "$EMULATOR_SERVICE did not start"; ERRORS=$((ERRORS + 1)); }
|
|
fi
|
|
for dir in "$NITRO_CLIENT" "$NITRO_RENDERER" "$EMULATOR_DIR" "$GAMEDATA_CONF_DIR"; do
|
|
if [ -d "$dir" ]; then
|
|
OWNER=$(stat -c '%U:%G' "$dir" 2>/dev/null || echo "unknown")
|
|
[ "$OWNER" = "www-data:www-data" ] || [ "$(id -u)" -ne 0 ] && debug "$(basename "$dir"): $OWNER" || sudo chown -R www-data:www-data "$dir" 2>/dev/null || true
|
|
fi
|
|
done
|
|
if curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${NITRO_SITE_URL:-}" 2>/dev/null | grep -qE '^(200|301|302|303|307|308)$'; then
|
|
ok "Site responds: ${NITRO_SITE_URL:-}"
|
|
else
|
|
warn "Site did not respond: ${NITRO_SITE_URL:-}"
|
|
fi
|
|
}
|
|
|
|
show_summary() {
|
|
ELAPSED=$(($(date +%s) - START_TIME))
|
|
ELAPSED_FMT=$(printf '%dm %ds' $((ELAPSED / 60)) $((ELAPSED % 60)))
|
|
echo ""
|
|
echo -e " ${C_CYAN}╔══════════════════════════════════════════════════════════════════╗${C_RESET}"
|
|
if [ "$ERRORS" -eq 0 ]; then
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}✔ UPDATE SUCCESSFULLY COMPLETED${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
else
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}⚠ COMPLETED WITH $ERRORS ERROR(S)${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
fi
|
|
echo -e " ${C_CYAN}╠══════════════════════════════════════════════════════════════════╣${C_RESET}"
|
|
printf " ${C_CYAN}║${C_RESET} Duration: %-45s${C_CYAN}║${C_RESET}\n" "$ELAPSED_FMT"
|
|
printf " ${C_CYAN}║${C_RESET} Updates: %-45s${C_CYAN}║${C_RESET}\n" "$([ "$HAD_UPDATES" = true ] && echo "Yes" || echo "No")"
|
|
printf " ${C_CYAN}║${C_RESET} Nitro build: %-45s${C_CYAN}║${C_RESET}\n" "$([ "$NITRO_BUILT" = true ] && echo "Yes" || echo "No")"
|
|
printf " ${C_CYAN}║${C_RESET} Warnings: %-45s${C_CYAN}║${C_RESET}\n" "$WARNINGS"
|
|
printf " ${C_CYAN}║${C_RESET} Errors: %-45s${C_CYAN}║${C_RESET}\n" "$ERRORS"
|
|
[ ${#UPDATED_REPOS[@]} -gt 0 ] && printf " ${C_CYAN}║${C_RESET} Updated: %-45s${C_CYAN}║${C_RESET}\n" "${UPDATED_REPOS[*]}"
|
|
printf " ${C_CYAN}║${C_RESET} Disk free: %-45s${C_CYAN}║${C_RESET}\n" "${AVAIL_GB:-?}GB"
|
|
printf " ${C_CYAN}║${C_RESET} Log: %-45s${C_CYAN}║${C_RESET}\n" "$(basename "${LOG_FILE:-?}")"
|
|
echo -e " ${C_CYAN}╚══════════════════════════════════════════════════════════════════╝${C_RESET}"
|
|
echo ""
|
|
notify "SUCCESS" "Completed in $ELAPSED_FMT (${ERRORS} errors, ${WARNINGS} warnings)"
|
|
}
|
|
|
|
# =============================================================================
|
|
# COMMANDS
|
|
# =============================================================================
|
|
cmd_update() {
|
|
preflight
|
|
preflight_checks
|
|
if [ "$DRY_RUN" = true ]; then dry_run_display; return 0; fi
|
|
echo ""
|
|
info "Site: ${NITRO_SITE_URL:-?} | Branch: ${NITRO_BRANCH:-main} | Mode: ${SELECTIVE_UPDATE:-full}"
|
|
info "Log: $LOG_FILE"
|
|
case "${SELECTIVE_UPDATE:-full}" in
|
|
emulator) update_emulator ;;
|
|
renderer) update_renderer ;;
|
|
client) update_client ;;
|
|
configs) sync_configs ;;
|
|
*) update_emulator; update_renderer; update_client; sync_configs; cleanup_phase; set_permissions; restart_services; health_check ;;
|
|
esac
|
|
show_summary
|
|
}
|
|
|
|
cmd_backup() {
|
|
preflight
|
|
step "Database Backup"
|
|
mkdir -p "$BACKUP_DIR"
|
|
BACKUP_FILE="$BACKUP_DIR/manual_$(date +%Y%m%d_%H%M%S).sql"
|
|
spinner_start "Backing up database..."
|
|
if mariadb-dump $MYSQL_CRED --force --skip-lock-tables --routines --events --triggers "$DB_NAME" > "$BACKUP_FILE" 2>/dev/null; then
|
|
BK_SIZE=$(stat -c%s "$BACKUP_FILE" 2>/dev/null || echo 0)
|
|
spinner_stop ok
|
|
ok "Backup saved: $(numfmt --to=iec "$BK_SIZE") — $(basename "$BACKUP_FILE")"
|
|
echo "$(date +%Y-%m-%d\ %H:%M:%S)|MANUAL_BACKUP|$BACKUP_FILE|$BK_SIZE" >> "$BACKUP_DIR/.history" 2>/dev/null || true
|
|
else
|
|
spinner_stop fail
|
|
die "Backup failed"
|
|
fi
|
|
}
|
|
|
|
cmd_restore() {
|
|
preflight
|
|
step "Database Restore"
|
|
[ ! -d "$BACKUP_DIR" ] && die "No backup directory found"
|
|
local backups=()
|
|
while IFS= read -r line; do backups+=("$line"); done < <(find "$BACKUP_DIR" -maxdepth 1 -name '*.sql' -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -20 | sed 's/^[0-9.]* //')
|
|
[ ${#backups[@]} -eq 0 ] && die "No backups found"
|
|
echo ""
|
|
echo -e " ${C_BOLD}${C_CYAN}Available Backups:${C_RESET}"
|
|
echo ""
|
|
local i=1
|
|
for bk in "${backups[@]}"; do
|
|
local name=$(basename "$bk")
|
|
local size=$(numfmt --to=iec "$(stat -c%s "$bk" 2>/dev/null || echo 0)" 2>/dev/null || echo "?")
|
|
printf " ${C_GREEN}${C_BOLD}%2d)${C_RESET} %-40s ${C_DIM}%s${C_RESET}\n" "$i" "$name" "$size"
|
|
i=$((i + 1))
|
|
done
|
|
echo ""
|
|
echo -en " Select [1-$((i-1))]: "
|
|
local choice; read -r choice
|
|
[[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#backups[@]} ] || die "Invalid selection"
|
|
local selected="${backups[$((choice-1))]}"
|
|
if ! confirm "Restore from $(basename "$selected")? This OVERWRITES the database!"; then info "Cancelled."; return 0; fi
|
|
spinner_start "Restoring..."
|
|
mariadb $MYSQL_CRED "$DB_NAME" < "$selected" 2>/dev/null && spinner_stop ok && ok "Restored from $(basename "$selected")" || { spinner_stop fail; die "Restore failed"; }
|
|
}
|
|
|
|
cmd_backups() {
|
|
step "Backup Inventory"
|
|
[ ! -d "$BACKUP_DIR" ] && { warn "No backup directory"; return 0; }
|
|
echo ""
|
|
printf " ${C_DIM}%-4s %-38s %-10s %-6s${C_RESET}\n" "#" "FILENAME" "SIZE" "AGE"
|
|
printf " ${C_DIM}%s${C_RESET}\n" "$(printf '%0.s─' $(seq 1 62))"
|
|
local total=0 total_size=0
|
|
while IFS= read -r line; do
|
|
total=$((total + 1))
|
|
local name=$(basename "$line")
|
|
local size=$(stat -c%s "$line" 2>/dev/null || echo 0)
|
|
total_size=$((total_size + size))
|
|
local size_fmt=$(numfmt --to=iec "$size" 2>/dev/null || echo "?")
|
|
local age_days=$(( ($(date +%s) - $(stat -c%Y "$line" 2>/dev/null || echo 0)) / 86400 ))
|
|
local age_fmt="${age_days}d"; [ "$age_days" -eq 0 ] && age_fmt="today"; [ "$age_days" -eq 1 ] && age_fmt="1d"
|
|
printf " ${C_GREEN}%2d${C_RESET} %-38s ${C_CYAN}%-10s${C_RESET} ${C_DIM}%-6s${C_RESET}\n" "$total" "$name" "$size_fmt" "$age_fmt"
|
|
done < <(find "$BACKUP_DIR" -maxdepth 1 -name '*.sql' -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -20 | sed 's/^[0-9.]* //')
|
|
printf " ${C_DIM}%s${C_RESET}\n" "$(printf '%0.s─' $(seq 1 62))"
|
|
echo -e " ${C_BOLD}Total: $total backup(s) — $(numfmt --to=iec "$total_size" 2>/dev/null || echo "?")${C_RESET}"
|
|
echo ""
|
|
}
|
|
|
|
cmd_status() {
|
|
step "System Status Dashboard"
|
|
echo ""
|
|
box_top "SERVER" 56
|
|
box_kv "${E_SERVER} Hostname:" "$(hostname)" 56
|
|
box_kv "${E_GLOBE} OS:" "$(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"' || uname -s)" 56
|
|
box_kv "${E_GEAR} Kernel:" "$(uname -r)" 56
|
|
box_kv "${E_CLOCK} Uptime:" "$(uptime -p 2>/dev/null || uptime)" 56
|
|
box_bottom 56
|
|
echo ""
|
|
box_top "RESOURCES" 56
|
|
local mem_info=$(free -m 2>/dev/null | awk '/^Mem:/{printf "%dMB / %dMB (%.1f%%)", $3, $2, $3*100/$2}')
|
|
box_kv "${E_LIGHTNING} CPU:" "$(awk '{printf "%.1f", $1}' /proc/loadavg 2>/dev/null || echo "?") / $(nproc 2>/dev/null || echo '?') cores" 56
|
|
box_kv "${E_CHART} Memory:" "$mem_info" 56
|
|
box_kv "${E_FOLDER} Disk:" "$(df -h "$SCRIPT_DIR" 2>/dev/null | tail -1 | awk '{printf "%s / %s (%s)", $3, $2, $5}')" 56
|
|
box_kv "${E_DB} Database:" "${DB_SIZE:-?}MB / ${DB_TABLES:-?} 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}"
|
|
systemctl is-active "$svc" &>/dev/null && st="${C_GREEN}ACTIVE${C_RESET}" || systemctl is-enabled "$svc" &>/dev/null 2>&1 && st="${C_YELLOW}INACTIVE${C_RESET}"
|
|
printf " ${C_CYAN}║${C_RESET} %-20s %b${C_CYAN} ║${C_RESET}\n" "$svc" "$st"
|
|
done
|
|
box_bottom 56
|
|
echo ""
|
|
box_top "GIT REPOSITORIES" 56
|
|
for repo_entry in "Emulator:$EMULATOR_DIR/Emulator" "Nitro-V3:$NITRO_CLIENT" "Nitro_Render_V3:$NITRO_RENDERER"; do
|
|
local name="${repo_entry%%:*}" path="${repo_entry##*:}"
|
|
[ -d "$path/.git" ] || continue
|
|
local branch=$(cd "$path" && git branch --show-current 2>/dev/null || echo "?")
|
|
local commit=$(cd "$path" && git log --oneline -1 2>/dev/null | cut -c1-30 || echo "?")
|
|
local dirty=$(cd "$path" && git status --porcelain 2>/dev/null | wc -l)
|
|
local dirty_str=""; [ "$dirty" -gt 0 ] && dirty_str=" ${C_YELLOW}($dirty modified)${C_RESET}"
|
|
printf " ${C_CYAN}║${C_RESET} %-14s ${C_CYAN}%-8s${C_RESET} %s%b\n" "$name" "$branch" "$commit" "$dirty_str"
|
|
done
|
|
box_bottom 56
|
|
echo ""
|
|
box_top "LOGS" 56
|
|
box_kv "${E_EYE} Total:" "$(find "$LOG_DIR" -name 'update-*.log' 2>/dev/null | wc -l) files" 56
|
|
box_kv "${E_FOLDER} Current:" "$(basename "${LOG_FILE:-?}")" 56
|
|
box_bottom 56
|
|
echo ""
|
|
}
|
|
|
|
cmd_health() {
|
|
step "System Health Check"
|
|
local total=0 passed=0 failed=0 warn_c=0
|
|
check() { total=$((total + 1)); "$@" &>/dev/null && { passed=$((passed + 1)); ok "$1"; } || { failed=$((failed + 1)); fail "$1"; }; }
|
|
check_w() { total=$((total + 1)); "$@" &>/dev/null && { passed=$((passed + 1)); ok "$1"; } || { warn_c=$((warn_c + 1)); warn "$1"; }; }
|
|
echo ""
|
|
echo -e " ${C_BOLD}System:${C_RESET}"
|
|
check_w "Git installed" command -v git
|
|
check_w "Node.js installed" command -v node
|
|
check_w "Yarn installed" command -v yarn
|
|
check_w "Python3 installed" command -v python3
|
|
check_w "Java installed" command -v java
|
|
check_w "Maven installed" command -v mvn
|
|
echo ""
|
|
echo -e " ${C_BOLD}Directories:${C_RESET}"
|
|
check_w "Emulator dir" test -d "$EMULATOR_DIR/Emulator"
|
|
check_w "Nitro-V3 dir" test -d "$NITRO_CLIENT"
|
|
check_w "Renderer dir" test -d "$NITRO_RENDERER"
|
|
echo ""
|
|
echo -e " ${C_BOLD}Services:${C_RESET}"
|
|
check_w "MariaDB running" systemctl is-active mariadb
|
|
check_w "Nginx running" systemctl is-active nginx
|
|
check_w "Redis running" systemctl is-active redis-server
|
|
echo ""
|
|
echo -e " ${C_BOLD}Application:${C_RESET}"
|
|
check_w "artisan exists" test -f "$SCRIPT_DIR/artisan"
|
|
check_w ".env exists" test -f "$SCRIPT_DIR/.env"
|
|
check_w "Site reachable" timeout 5 curl -sf "${NITRO_SITE_URL:-}" 2>/dev/null
|
|
echo ""
|
|
echo -e " ${C_BOLD}Resources:${C_RESET}"
|
|
local avail_kb=$(df --output=avail "$EMULATOR_DIR" 2>/dev/null | tail -1)
|
|
local avail_gb=$((avail_kb / 1024 / 1024))
|
|
total=$((total + 1))
|
|
[ "$avail_gb" -ge "$MIN_DISK_GB" ] && { passed=$((passed + 1)); ok "Disk: ${avail_gb}GB free"; } || { failed=$((failed + 1)); fail "Disk: ${avail_gb}GB (min ${MIN_DISK_GB}GB)"; }
|
|
echo ""
|
|
local score=$((passed * 100 / total))
|
|
box_top "HEALTH SCORE" 50
|
|
box_kv "Total:" "$total" 50
|
|
box_kv "${C_GREEN}Passed:" "$passed" 50
|
|
box_kv "${C_RED}Failed:" "$failed" 50
|
|
box_kv "${C_YELLOW}Warnings:" "$warn_c" 50
|
|
if [ "$score" -ge 90 ]; then
|
|
box_line "${C_GREEN}${C_BOLD}Score: ${score}% — EXCELLENT${C_RESET}" 50
|
|
elif [ "$score" -ge 70 ]; then
|
|
box_line "${C_YELLOW}${C_BOLD}Score: ${score}% — GOOD${C_RESET}" 50
|
|
else
|
|
box_line "${C_RED}${C_BOLD}Score: ${score}% — NEEDS ATTENTION${C_RESET}" 50
|
|
fi
|
|
box_bottom 50
|
|
echo ""
|
|
}
|
|
|
|
cmd_services() {
|
|
step "Service Manager"
|
|
select_menu "Service Control" "Start Emulator" "Stop Emulator" "Restart Emulator" "Status Emulator" "Start Nginx" "Restart Nginx" "Status All" "Emulator Logs (tail)" "Return"
|
|
case "${REPLY:-}" in
|
|
1) sudo systemctl start "$EMULATOR_SERVICE" && ok "$EMULATOR_SERVICE started" || fail "Failed" ;;
|
|
2) sudo systemctl stop "$EMULATOR_SERVICE" && ok "$EMULATOR_SERVICE stopped" || fail "Failed" ;;
|
|
3) sudo systemctl restart "$EMULATOR_SERVICE" && ok "$EMULATOR_SERVICE restarted" || fail "Failed" ;;
|
|
4) systemctl status "$EMULATOR_SERVICE" --no-pager ;;
|
|
5) sudo systemctl start nginx && ok "Nginx started" || fail "Failed" ;;
|
|
6) sudo systemctl restart nginx && ok "Nginx restarted" || fail "Failed" ;;
|
|
7) for svc in nginx mariadb redis-server "$EMULATOR_SERVICE" php-fpm cron; do local st="N/A"; systemctl is-active "$svc" &>/dev/null && st="${C_GREEN}ACTIVE${C_RESET}" || st="${C_RED}INACTIVE${C_RESET}"; printf " %-25s %b\n" "$svc" "$st"; done ;;
|
|
8) sudo journalctl -u "$EMULATOR_SERVICE" --no-pager -n 50 ;;
|
|
esac
|
|
}
|
|
|
|
cmd_database() {
|
|
step "Database Tools"
|
|
local choice
|
|
choice=$(select_menu "Database Management" "Show DB Info" "List Tables" "Table Sizes" "Optimize Tables" "Check Tables" "Custom Query" "Active Users" "Return")
|
|
case "$choice" in
|
|
1) box_top "DATABASE INFO" 50; box_kv "Name:" "$DB_NAME" 50; box_kv "Host:" "$DB_HOST:$DB_PORT" 50; box_kv "User:" "$DB_USER" 50
|
|
box_kv "Size:" "${DB_SIZE:-?}MB" 50; box_kv "Tables:" "${DB_TABLES:-?}" 50; box_bottom 50 ;;
|
|
2) mariadb $MYSQL_CRED -e "SHOW TABLES FROM $DB_NAME" 2>/dev/null ;;
|
|
3) mariadb $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', ROUND((data_length+index_length)/1024/1024,2) AS 'Total MB' FROM information_schema.tables WHERE table_schema='$DB_NAME' ORDER BY (data_length+index_length) DESC" 2>/dev/null ;;
|
|
4) spinner_start "Optimizing..."
|
|
local count=0
|
|
for tbl in $(mariadb $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
|
|
mariadb $MYSQL_CRED -e "OPTIMIZE TABLE $DB_NAME.\`$tbl\`" "$DB_NAME" &>/dev/null || true
|
|
count=$((count + 1))
|
|
done
|
|
spinner_stop ok; ok "Optimized $count tables" ;;
|
|
5) mariadb $MYSQL_CRED -e "CHECK TABLE $DB_NAME.*" "$DB_NAME" 2>/dev/null ;;
|
|
6) echo -en " SQL: "; local q; read -r q; [ -n "$q" ] && mariadb $MYSQL_CRED "$DB_NAME" -e "$q" 2>/dev/null ;;
|
|
7) mariadb $MYSQL_CRED -e "SELECT name, lookAt, motto FROM $DB_NAME.users WHERE online='1'" 2>/dev/null || warn "Could not query users" ;;
|
|
esac
|
|
}
|
|
|
|
cmd_config() {
|
|
step "Configuration"
|
|
echo ""
|
|
box_top "CURRENT CONFIGURATION" 56
|
|
box_kv "${E_CLOUD} Site URL:" "${NITRO_SITE_URL:-?}" 56
|
|
box_kv "${E_GIT} Branch:" "${NITRO_BRANCH:-main}" 56
|
|
box_kv "${E_FOLDER} Emulator:" "$EMULATOR_DIR" 56
|
|
box_kv "${E_FOLDER} Nitro-V3:" "$NITRO_CLIENT" 56
|
|
box_kv "${E_FOLDER} Renderer:" "$NITRO_RENDERER" 56
|
|
box_kv "${E_DB} Database:" "$DB_NAME @ $DB_HOST:$DB_PORT" 56
|
|
box_kv "${E_GEAR} Service:" "$EMULATOR_SERVICE" 56
|
|
box_kv "${E_CLOUD} Socket:" "${NITRO_SOCKET_URL:-?}" 56
|
|
box_kv "${E_CLOUD} API:" "${NITRO_API_URL:-?}" 56
|
|
box_kv "${E_CLOUD} Gamedata:" "${NITRO_GAMEDATA_URL:-?}" 56
|
|
box_kv "${E_LOCK} Min Disk:" "${MIN_DISK_GB}GB" 56
|
|
box_bottom 56
|
|
echo ""
|
|
}
|
|
|
|
cmd_logs() {
|
|
step "Log Viewer"
|
|
[ ! -d "$LOG_DIR" ] && { warn "No log directory"; return 0; }
|
|
local logs=()
|
|
while IFS= read -r line; do logs+=("$line"); done < <(find "$LOG_DIR" -name 'update-*.log' -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -20 | sed 's/^[0-9.]* //')
|
|
[ ${#logs[@]} -eq 0 ] && { warn "No logs found"; return 0; }
|
|
echo ""
|
|
echo -e " ${C_BOLD}${C_CYAN}Recent Logs:${C_RESET}"
|
|
echo ""
|
|
local i=1
|
|
for log in "${logs[@]}"; do
|
|
local name=$(basename "$log")
|
|
local size=$(numfmt --to=iec "$(stat -c%s "$log" 2>/dev/null || echo 0)" 2>/dev/null || echo "?")
|
|
local lines=$(wc -l < "$log" 2>/dev/null || echo "?")
|
|
printf " ${C_GREEN}${C_BOLD}%2d)${C_RESET} %-35s ${C_DIM}%s (%s lines)${C_RESET}\n" "$i" "$name" "$size" "$lines"
|
|
i=$((i + 1))
|
|
done
|
|
echo ""
|
|
echo -en " Select [1-$((i-1))]: "
|
|
local choice; read -r choice
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#logs[@]} ]; then
|
|
echo ""
|
|
echo -e " ${C_BOLD}--- $(basename "${logs[$((choice-1))]}") ---${C_RESET}"
|
|
echo ""
|
|
less -R "${logs[$((choice-1))]}" 2>/dev/null || cat "${logs[$((choice-1))]}"
|
|
fi
|
|
}
|
|
|
|
cmd_clean() {
|
|
step "Cache Cleanup"
|
|
local choice
|
|
choice=$(select_menu "Cleanup" "Yarn Cache" "Laravel Cache" "Redis Cache" "Old Logs (>14d)" "Old Backups (keep 5)" "Temp Files" "Everything" "Return")
|
|
case "$choice" in
|
|
1) spinner_start "Cleaning..."; yarn cache clean 2>/dev/null; spinner_stop ok ;;
|
|
2) cd "$SCRIPT_DIR" && php artisan cache:clear 2>/dev/null && php artisan config:clear 2>/dev/null && php artisan route:clear 2>/dev/null && php artisan view:clear 2>/dev/null && ok "Laravel cache cleared" ;;
|
|
3) redis-cli FLUSHALL 2>/dev/null && ok "Redis flushed" || warn "Redis not available" ;;
|
|
4) local c=$(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; ok "Removed $c logs" ;;
|
|
5) local b=$(find "$BACKUP_DIR" -name '*.sql' 2>/dev/null | wc -l); 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; ok "Backups: $b -> $(find "$BACKUP_DIR" -name '*.sql' 2>/dev/null | wc -l)" ;;
|
|
6) rm -rf /tmp/nitro-* 2>/dev/null; ok "Temp cleaned" ;;
|
|
7) yarn cache clean 2>/dev/null; find "$EMULATOR_DIR" -name "*.log" -mtime +14 -exec rm -f {} \; 2>/dev/null; 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; rm -rf /tmp/nitro-* 2>/dev/null; ok "Full cleanup done" ;;
|
|
esac
|
|
}
|
|
|
|
# =============================================================================
|
|
# NEW: GIT LOG VIEWER
|
|
# =============================================================================
|
|
cmd_gitlog() {
|
|
step "Git Commit History"
|
|
echo ""
|
|
echo -e " ${C_BOLD}Select repository:${C_RESET}"
|
|
echo ""
|
|
echo -e " ${C_GREEN}1)${C_RESET} Emulator"
|
|
echo -e " ${C_GREEN}2)${C_RESET} Nitro-V3"
|
|
echo -e " ${C_GREEN}3)${C_RESET} Nitro_Render_V3"
|
|
echo ""
|
|
echo -en " Select [1-3]: "
|
|
local rc; read -r rc
|
|
local repo=""
|
|
case "$rc" in
|
|
1) repo="$EMULATOR_DIR/Emulator" ;;
|
|
2) repo="$NITRO_CLIENT" ;;
|
|
3) repo="$NITRO_RENDERER" ;;
|
|
*) warn "Invalid"; 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}:"
|
|
echo ""
|
|
printf " ${C_DIM}%-12s %-8s %-50s${C_RESET}\n" "DATE" "HASH" "MESSAGE"
|
|
printf " ${C_DIM}%s${C_RESET}\n" "$(printf '%0.s─' $(seq 1 72))"
|
|
cd "$repo" && git log --pretty=format:" ${C_CYAN}%h${C_RESET} %C(yellow)%cr${C_RESET} %s" -20 2>/dev/null
|
|
echo ""
|
|
echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# NEW: BRANCH COMPARE
|
|
# =============================================================================
|
|
cmd_compare() {
|
|
step "Branch Comparison"
|
|
echo ""
|
|
local BRANCHES=()
|
|
while IFS= read -r b; do [ -n "$b" ] && BRANCHES+=("$b"); done < <(detect_available_branches)
|
|
[ ${#BRANCHES[@]} -lt 2 ] && { warn "Need at least 2 branches to compare (found: ${#BRANCHES[@]})"; return 0; }
|
|
echo -e " ${C_BOLD}Available branches:${C_RESET}"
|
|
echo ""
|
|
local i=1
|
|
for b in "${BRANCHES[@]}"; do
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} ${C_CYAN}$b${C_RESET}"
|
|
i=$((i + 1))
|
|
done
|
|
echo ""
|
|
echo -en " Branch A [1-$((i-1))]: "
|
|
local ba; read -r ba
|
|
echo -en " Branch B [1-$((i-1))]: "
|
|
local bb; read -r bb
|
|
[[ "$ba" =~ ^[0-9]+$ ]] && [[ "$bb" =~ ^[0-9]+$ ]] && [ "$ba" -ge 1 ] && [ "$bb" -ge 1 ] && [ "$ba" -le ${#BRANCHES[@]} ] && [ "$bb" -le ${#BRANCHES[@]} ] || { warn "Invalid selection"; return 0; }
|
|
local branchA="${BRANCHES[$((ba-1))]}"
|
|
local branchB="${BRANCHES[$((bb-1))]}"
|
|
echo ""
|
|
echo -e " ${C_BOLD}Comparing ${C_CYAN}$branchA${C_RESET} vs ${C_CYAN}$branchB${C_RESET}"
|
|
echo ""
|
|
for repo in "$EMULATOR_DIR/Emulator" "$NITRO_CLIENT" "$NITRO_RENDERER"; do
|
|
[ -d "$repo/.git" ] || continue
|
|
local name=$(basename "$repo")
|
|
echo -e " ${C_BOLD}${C_CYAN}$name:${C_RESET}"
|
|
local ahead=0 behind=0
|
|
cd "$repo" 2>/dev/null || continue
|
|
if git rev-parse --verify "$branchA" &>/dev/null && git rev-parse --verify "$branchB" &>/dev/null; then
|
|
ahead=$(git rev-list --count "$branchB".."$branchA" 2>/dev/null || echo "0")
|
|
behind=$(git rev-list --count "$branchA".."$branchB" 2>/dev/null || echo "0")
|
|
local diff_stat=$(git diff --stat "$branchA".."$branchB" 2>/dev/null | tail -1 || echo "")
|
|
echo -e " $branchA is ${C_GREEN}$ahead${C_RESET} ahead, ${C_YELLOW}$behind${C_RESET} behind $branchB"
|
|
[ -n "$diff_stat" ] && echo -e " ${C_DIM}$diff_stat${C_RESET}"
|
|
else
|
|
echo -e " ${C_YELLOW}One or both branches not found locally${C_RESET}"
|
|
fi
|
|
echo ""
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# NEW: UPDATE HISTORY
|
|
# =============================================================================
|
|
cmd_history() {
|
|
step "Update History"
|
|
echo ""
|
|
local hist_file="$BACKUP_DIR/.history"
|
|
if [ ! -f "$hist_file" ]; then
|
|
warn "No update history found"
|
|
return 0
|
|
fi
|
|
echo ""
|
|
printf " ${C_DIM}%-20s %-20s %-30s %-10s${C_RESET}\n" "DATE" "TYPE" "FILE" "SIZE"
|
|
printf " ${C_DIM}%s${C_RESET}\n" "$(printf '%0.s─' $(seq 1 82))"
|
|
local count=0
|
|
while IFS='|' read -r date type file size; do
|
|
count=$((count + 1))
|
|
local size_fmt=$(numfmt --to=iec "$size" 2>/dev/null || echo "?")
|
|
printf " %-20s ${C_CYAN}%-20s${C_RESET} %-30s ${C_GREEN}%-10s${C_RESET}\n" "$date" "$type" "$(basename "${file:-?}")" "$size_fmt"
|
|
[ "$count" -ge 20 ] && break
|
|
done < "$hist_file"
|
|
[ "$count" -eq 0 ] && echo -e " ${C_DIM}No history entries found${C_RESET}"
|
|
echo ""
|
|
echo -e " ${C_DIM}Showing last 20 entries${C_RESET}"
|
|
echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# NEW: CRON SCHEDULER
|
|
# =============================================================================
|
|
cmd_schedule() {
|
|
step "Scheduled Updates (Cron)"
|
|
echo ""
|
|
local current_cron=$(crontab -l 2>/dev/null | grep -F "$SCRIPT_NAME" || echo "")
|
|
echo -e " ${C_BOLD}Current schedule:${C_RESET}"
|
|
if [ -n "$current_cron" ]; then
|
|
echo -e " ${C_GREEN}$current_cron${C_RESET}"
|
|
else
|
|
echo -e " ${C_DIM}No scheduled updates${C_RESET}"
|
|
fi
|
|
echo ""
|
|
echo -e " ${C_BOLD}Options:${C_RESET}"
|
|
echo -e " ${C_GREEN}1)${C_RESET} Schedule daily at 03:00"
|
|
echo -e " ${C_GREEN}2)${C_RESET} Schedule daily at 04:00"
|
|
echo -e " ${C_GREEN}3)${C_RESET} Schedule weekly (Sunday 03:00)"
|
|
echo -e " ${C_GREEN}4)${C_RESET} Schedule custom time"
|
|
echo -e " ${C_GREEN}5)${C_RESET} Remove schedule"
|
|
echo -e " ${C_GREEN}6)${C_RESET} Run scheduled update now (test)"
|
|
echo ""
|
|
echo -en " Select [1-6]: "
|
|
local sc; read -r sc
|
|
case "$sc" in
|
|
1) (crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME"; echo "0 3 * * * cd $SCRIPT_DIR && ./$SCRIPT_NAME update --branch ${NITRO_BRANCH:-main} >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled daily at 03:00" ;;
|
|
2) (crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME"; echo "0 4 * * * cd $SCRIPT_DIR && ./$SCRIPT_NAME update --branch ${NITRO_BRANCH:-main} >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled daily at 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:-main} >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled weekly (Sunday 03:00)" ;;
|
|
4) echo -en " Minute [0-59]: "; local m; read -r m
|
|
echo -en " Hour [0-23]: "; local h; read -r h
|
|
echo -en " Day [1-31]: "; local d; read -r d
|
|
echo -en " Month [1-12]: "; local mo; read -r mo
|
|
(crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME"; echo "$m $h $d $mo * cd $SCRIPT_DIR && ./$SCRIPT_NAME update --branch ${NITRO_BRANCH:-main} >> /var/log/nitro-cron.log 2>&1") | crontab - && ok "Scheduled custom: $m $h $d $mo *" ;;
|
|
5) crontab -l 2>/dev/null | grep -vF "$SCRIPT_NAME" | crontab - && ok "Schedule removed" ;;
|
|
6) info "Running test update..."; cd "$SCRIPT_DIR" && ./"$SCRIPT_NAME" update --branch "${NITRO_BRANCH:-main}" ;;
|
|
esac
|
|
}
|
|
|
|
# =============================================================================
|
|
# MAIN INTERACTIVE MENU
|
|
# =============================================================================
|
|
pick_branch_and_update() {
|
|
local mode="${SELECTIVE_UPDATE:-full}"
|
|
local mode_label="Full"
|
|
case "$mode" in
|
|
emulator) mode_label="Emulator Only" ;;
|
|
client) mode_label="Nitro-V3 Client Only" ;;
|
|
renderer) mode_label="Renderer Only" ;;
|
|
configs) mode_label="Configs Only" ;;
|
|
esac
|
|
local BRANCHES=()
|
|
while IFS= read -r b; do [ -n "$b" ] && BRANCHES+=("$b"); done < <(detect_available_branches)
|
|
echo ""
|
|
echo -e " ${C_BOLD}${C_CYAN}╔═══════════════════════════════════════════════════════╗${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BOLD}Update: ${C_GREEN}$mode_label${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}╚═══════════════════════════════════════════════════════╝${C_RESET}"
|
|
echo ""
|
|
echo -e " ${C_BOLD}Select branch:${C_RESET}"
|
|
echo ""
|
|
local i=1
|
|
for b in "${BRANCHES[@]}"; do
|
|
local marker=""; [ "$b" = "${NITRO_BRANCH:-main}" ] && marker=" ${C_GREEN}(current)${C_RESET}"
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} ${C_CYAN}$b${C_RESET}$marker"
|
|
i=$((i + 1))
|
|
done
|
|
echo -e " ${C_GREEN}${C_BOLD}$i)${C_RESET} Custom branch"
|
|
echo ""
|
|
echo -en " Select [1-$i]: "
|
|
local bc; read -r bc
|
|
if [[ "$bc" =~ ^[0-9]+$ ]] && [ "$bc" -ge 1 ] && [ "$bc" -lt "$i" ]; then
|
|
NITRO_BRANCH="${BRANCHES[$((bc-1))]}"
|
|
elif [ "$bc" = "$i" ]; then
|
|
echo -en " Branch name: "; read -r NITRO_BRANCH
|
|
[ -z "$NITRO_BRANCH" ] && NITRO_BRANCH="main"
|
|
else
|
|
warn "Invalid, using: ${NITRO_BRANCH:-main}"
|
|
fi
|
|
echo ""
|
|
echo -e " ${C_BOLD}Update Summary:${C_RESET}"
|
|
echo -e " ${E_GIT} Branch: ${C_CYAN}${C_BOLD}${NITRO_BRANCH}${C_RESET}"
|
|
echo -e " ${E_GEAR} Mode: ${C_GREEN}${C_BOLD}${mode_label}${C_RESET}"
|
|
echo -e " ${E_CLOUD} Site: ${C_CYAN}${NITRO_SITE_URL:-?}${C_RESET}"
|
|
echo ""
|
|
if ! confirm "Start update?"; then info "Cancelled."; return 0; fi
|
|
cmd_update
|
|
}
|
|
|
|
cmd_interactive() {
|
|
while true; do
|
|
show_banner
|
|
local AVAILABLE_BRANCHES=()
|
|
while IFS= read -r b; do [ -n "$b" ] && AVAILABLE_BRANCHES+=("$b"); done < <(detect_available_branches)
|
|
local branch_display="${C_CYAN}${C_BOLD}${NITRO_BRANCH:-main}${C_RESET}"
|
|
local branch_count="${#AVAILABLE_BRANCHES[@]}"
|
|
echo -e " ${C_CYAN}╔══════════════════════════════════════════════════════════════════╗${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GOLD}${C_BOLD}Emulator/Nitro-V3/Render Updater — $(date '+%H:%M')${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}╠══════════════════════════════════════════════════════════════════╣${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BOLD}Settings:${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${E_GIT} Branch: ${branch_display} ${C_DIM}(${branch_count} detected)${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${E_CLOUD} Site: ${C_CYAN}${NITRO_SITE_URL:-?}${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BG_GREEN}${C_WHITE}${C_BOLD} UPDATE ${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}1)${C_RESET} ${E_ROCKET} Quick Update — branch: ${branch_display} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}2)${C_RESET} ${E_LIGHTNING} Full Update — everything (pull + build + deploy) ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}3)${C_RESET} ${E_GEAR} Emulator Only — Java backend build & deploy ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}4)${C_RESET} ${E_GEAR} Nitro-V3 Client Only — frontend build ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}5)${C_RESET} ${E_GEAR} Renderer Only — Nitro renderer install ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_GREEN}${C_BOLD}6)${C_RESET} ${E_GEAR} Configs Only — URL sync & merge ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BG_MAGENTA}${C_WHITE}${C_BOLD} TOOLS ${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_MAGENTA}${C_BOLD}7)${C_RESET} ${E_GIT} Switch Branch — select from detected ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}8)${C_RESET} ${E_DB} Database Manager — tables, optimize, queries ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}9)${C_RESET} ${E_SHIELD} Backup & Restore ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}10)${C_RESET} ${E_WRENCH} Service Manager ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}11)${C_RESET} ${E_CHART} System Status ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}12)${C_RESET} ${E_HEART} Health Check ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}13)${C_RESET} ${E_BROOM} Cache Cleanup ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}14)${C_RESET} ${E_EYE} View Logs ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_YELLOW}${C_BOLD}15)${C_RESET} ${E_GEAR} Configuration ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BG_BLUE}${C_WHITE}${C_BOLD} ADVANCED ${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BLUE}${C_BOLD}16)${C_RESET} ${E_CONSOLE} Git Log — commit history per repo ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BLUE}${C_BOLD}17)${C_RESET} ${E_GLOBE} Compare Branches — diff between branches ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BLUE}${C_BOLD}18)${C_RESET} ${E_CLOCK} Update History — backup/changelog ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_BLUE}${C_BOLD}19)${C_RESET} ${E_FIRE} Cron Scheduler — auto-update setup ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_RED}${C_BOLD}0)${C_RESET} Exit ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}║${C_RESET} ${C_CYAN}║${C_RESET}"
|
|
echo -e " ${C_CYAN}╚══════════════════════════════════════════════════════════════════╝${C_RESET}"
|
|
echo ""
|
|
echo -en " ${C_CYAN}${C_BOLD}Select [0-19]${C_RESET}: "
|
|
local choice; read -r choice
|
|
case "$choice" in
|
|
1) SELECTIVE_UPDATE=""; IN_INTERACTIVE=true; set +e; info "Starting quick update..."; cmd_update; set -e; IN_INTERACTIVE=false ;;
|
|
2) SELECTIVE_UPDATE=""; IN_INTERACTIVE=true; set +e; pick_branch_and_update; set -e; IN_INTERACTIVE=false ;;
|
|
3) SELECTIVE_UPDATE="emulator"; IN_INTERACTIVE=true; set +e; pick_branch_and_update; set -e; IN_INTERACTIVE=false ;;
|
|
4) SELECTIVE_UPDATE="client"; IN_INTERACTIVE=true; set +e; pick_branch_and_update; set -e; IN_INTERACTIVE=false ;;
|
|
5) SELECTIVE_UPDATE="renderer"; IN_INTERACTIVE=true; set +e; pick_branch_and_update; set -e; IN_INTERACTIVE=false ;;
|
|
6) SELECTIVE_UPDATE="configs"; IN_INTERACTIVE=true; set +e; pick_branch_and_update; set -e; IN_INTERACTIVE=false ;;
|
|
7)
|
|
echo ""
|
|
echo -e " ${C_BOLD}Current: ${C_CYAN}${NITRO_BRANCH:-main}${C_RESET}"
|
|
echo ""
|
|
local i=1
|
|
for b in "${AVAILABLE_BRANCHES[@]}"; do
|
|
local m=""; [ "$b" = "${NITRO_BRANCH:-main}" ] && m=" ${C_GREEN}(active)${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} Custom"
|
|
echo ""
|
|
echo -en " Select [1-$i]: "
|
|
local bc; read -r bc
|
|
if [[ "$bc" =~ ^[0-9]+$ ]] && [ "$bc" -ge 1 ] && [ "$bc" -lt "$i" ]; then
|
|
NITRO_BRANCH="${AVAILABLE_BRANCHES[$((bc-1))]}"
|
|
ok "Branch: $NITRO_BRANCH"
|
|
elif [ "$bc" = "$i" ]; then
|
|
echo -en " Branch: "; read -r NITRO_BRANCH
|
|
[ -n "$NITRO_BRANCH" ] && ok "Branch: $NITRO_BRANCH"
|
|
else
|
|
warn "Invalid"
|
|
fi
|
|
;;
|
|
8) cmd_database ;;
|
|
9) local sc; sc=$(select_menu "Backup & Restore" "Create Backup" "Restore Backup" "View Backups" "Return"); case "${sc:-}" in 1) cmd_backup;; 2) cmd_restore;; 3) cmd_backups;; esac ;;
|
|
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_history ;;
|
|
19) cmd_schedule ;;
|
|
0|q|Q) echo -e "\n ${C_GREEN}Goodbye!${C_RESET}\n"; cursor_show; exit 0 ;;
|
|
*) warn "Invalid selection" ;;
|
|
esac
|
|
echo ""
|
|
echo -en " ${C_DIM}Press Enter to continue...${C_RESET}"
|
|
read -r
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# HELP
|
|
# =============================================================================
|
|
show_help() {
|
|
show_mini_banner
|
|
echo -e " ${C_BOLD}USAGE:${C_RESET}"
|
|
echo -e " $SCRIPT_NAME ${C_CYAN}<command>${C_RESET} [${C_DIM}options${C_RESET}]"
|
|
echo ""
|
|
echo -e " ${C_BOLD}${C_GOLD}Emulator / Nitro-V3 / Nitro-V3-Render Updater${C_RESET}"
|
|
echo -e " ${C_DIM}by Remco — epicnabbo.nl${C_RESET}"
|
|
echo ""
|
|
echo -e " ${C_BOLD}COMMANDS:${C_RESET}"
|
|
echo -e " ${C_GREEN}update${C_RESET} Run full update (--only for selective)"
|
|
echo -e " ${C_GREEN}interactive${C_RESET} Launch TUI control panel (default)"
|
|
echo -e " ${C_GREEN}status${C_RESET} System status dashboard"
|
|
echo -e " ${C_GREEN}health${C_RESET} Health check with scoring"
|
|
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}gitlog${C_RESET} Git commit history"
|
|
echo -e " ${C_GREEN}compare${C_RESET} Branch comparison"
|
|
echo -e " ${C_GREEN}history${C_RESET} Update history"
|
|
echo -e " ${C_GREEN}schedule${C_RESET} Cron scheduler"
|
|
echo -e " ${C_GREEN}services${C_RESET} Service manager"
|
|
echo -e " ${C_GREEN}database${C_RESET} Database tools"
|
|
echo -e " ${C_GREEN}config${C_RESET} Show configuration"
|
|
echo -e " ${C_GREEN}logs${C_RESET} View logs"
|
|
echo -e " ${C_GREEN}clean${C_RESET} Cache cleanup"
|
|
echo -e " ${C_GREEN}help${C_RESET} Show help"
|
|
echo ""
|
|
echo -e " ${C_BOLD}OPTIONS:${C_RESET}"
|
|
echo -e " ${C_CYAN}--dry-run, -n${C_RESET} Preview only"
|
|
echo -e " ${C_CYAN}--force, -f${C_RESET} Force update"
|
|
echo -e " ${C_CYAN}--verbose, -v${C_RESET} Debug output"
|
|
echo -e " ${C_CYAN}--quiet, -q${C_RESET} Suppress output"
|
|
echo -e " ${C_CYAN}--branch, -b${C_RESET} Git 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}--version, -V${C_RESET} Show version"
|
|
echo -e " ${C_CYAN}--help, -h${C_RESET} Show help"
|
|
echo ""
|
|
echo -e " ${C_BOLD}EXAMPLES:${C_RESET}"
|
|
echo -e " ${C_DIM}$SCRIPT_NAME interactive${C_RESET}"
|
|
echo -e " ${C_DIM}$SCRIPT_NAME update --branch dev --dry-run${C_RESET}"
|
|
echo -e " ${C_DIM}$SCRIPT_NAME update --only=emulator --force${C_RESET}"
|
|
echo -e " ${C_DIM}$SCRIPT_NAME backup${C_RESET}"
|
|
echo ""
|
|
}
|
|
|
|
# =============================================================================
|
|
# ARGUMENT PARSING
|
|
# =============================================================================
|
|
parse_args() {
|
|
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" ;;
|
|
history|hist) cmd="history" ;;
|
|
schedule|cron) cmd="schedule" ;;
|
|
services|svc) cmd="services" ;;
|
|
database|db) cmd="database" ;;
|
|
config|cfg) cmd="config" ;;
|
|
logs|log) cmd="logs" ;;
|
|
clean|cl) cmd="clean" ;;
|
|
help|-h|--help) cmd="help" ;;
|
|
version|-V|--version) echo "Nitro V3 Update System v${SCRIPT_VERSION} (build ${SCRIPT_BUILD})"; exit 0 ;;
|
|
--dry-run|-n) DRY_RUN=true ;;
|
|
--force|-f) FORCE=true ;;
|
|
--verbose|-v) VERBOSE=true ;;
|
|
--quiet|-q) QUIET=true ;;
|
|
--branch|-b) shift; NITRO_BRANCH="$1" ;;
|
|
--url|-u) shift; NITRO_SITE_URL="$1" ;;
|
|
--only=*) SELECTIVE_UPDATE="${1#*=}" ;;
|
|
-*) die "Unknown option: $1 (use --help)" ;;
|
|
*) [ -z "$cmd" ] && cmd="interactive" ;;
|
|
esac
|
|
shift
|
|
done
|
|
if [ -n "$SELECTIVE_UPDATE" ]; then
|
|
case "$SELECTIVE_UPDATE" in
|
|
emulator|client|renderer|configs) ;;
|
|
*) die "Invalid --only: $SELECTIVE_UPDATE" ;;
|
|
esac
|
|
fi
|
|
[ -z "$cmd" ] && cmd="interactive"
|
|
echo "$cmd"
|
|
}
|
|
|
|
# =============================================================================
|
|
# MAIN
|
|
# =============================================================================
|
|
main() {
|
|
if [ -z "${_UNBUFFERED:-}" ] && command -v unbuffer &>/dev/null; then
|
|
export _UNBUFFERED=1
|
|
exec unbuffer bash "$0" "$@"
|
|
fi
|
|
LOCKFILE="/tmp/$(basename "$0").lock"
|
|
exec 200>"$LOCKFILE"
|
|
flock -n 200 2>/dev/null || die "Another instance is already running"
|
|
|
|
DB_NAME="${NITRO_DB_NAME:-habbo}"
|
|
DB_HOST="${NITRO_DB_HOST:-127.0.0.1}"
|
|
DB_PORT="${NITRO_DB_PORT:-3306}"
|
|
DB_USER="${NITRO_DB_USER:-root}"
|
|
DB_PASS="${NITRO_DB_PASS:-}"
|
|
EMULATOR_SERVICE="${NITRO_EMULATOR_SERVICE:-emulator}"
|
|
EMULATOR_DIR="${NITRO_EMULATOR_PATH:-/var/www/emulator}"
|
|
SQL_DIR="${NITRO_SQL_DIR:-$EMULATOR_DIR/Database Updates}"
|
|
GAMEDATA_CONF_DIR="${NITRO_GAMEDATA_DIR:-/var/www/Gamedata/config}"
|
|
NITRO_SRC_DIR="${NITRO_CLIENT_DIR:-/var/www/Nitro-V3/public/configuration}"
|
|
BACKUP_DIR="${NITRO_BACKUP_DIR:-$EMULATOR_DIR/Database Updates/backups}"
|
|
NITRO_CLIENT="${NITRO_CLIENT_SRC:-/var/www/Nitro-V3}"
|
|
NITRO_RENDERER="${NITRO_RENDERER_SRC:-/var/www/Nitro_Render_V3}"
|
|
NITRO_BRANCH="${NITRO_BRANCH:-main}"
|
|
MIN_DISK_GB="${NITRO_MIN_DISK_GB:-5}"
|
|
HEALTH_RETRIES="${NITRO_HEALTH_RETRIES:-12}"
|
|
HEALTH_INTERVAL="${NITRO_HEALTH_INTERVAL:-5}"
|
|
|
|
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://}" ;;
|
|
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_CAMERA_URL="${NITRO_CAMERA_URL:-${NITRO_SITE_URL:-}/camera/photo/}"
|
|
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}"
|
|
|
|
MYSQL_CRED="-h $DB_HOST -P $DB_PORT -u $DB_USER --ssl-verify-server-cert=OFF"
|
|
[ -n "$DB_PASS" ] && export MYSQL_PWD="$DB_PASS"
|
|
|
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
|
|
local cmd
|
|
cmd=$(parse_args "$@")
|
|
case "$cmd" in
|
|
interactive) cmd_interactive ;;
|
|
update) cmd_update ;;
|
|
status) cmd_status ;;
|
|
health) cmd_health ;;
|
|
backup) cmd_backup ;;
|
|
restore) cmd_restore ;;
|
|
backups) cmd_backups ;;
|
|
gitlog) cmd_gitlog ;;
|
|
compare) cmd_compare ;;
|
|
history) cmd_history ;;
|
|
schedule) cmd_schedule ;;
|
|
services) cmd_services ;;
|
|
database) cmd_database ;;
|
|
config) cmd_config ;;
|
|
logs) cmd_logs ;;
|
|
clean) cmd_clean ;;
|
|
help) show_help ;;
|
|
esac
|
|
cursor_show
|
|
}
|
|
|
|
main "$@"
|