perf: optimize dashboard database queries and remove unused imports
CI / check (push) Failing after 10s
CI / deploy (push) Skipped
CI / release (push) Skipped

This commit is contained in:
openhands committed 2026-08-25 21:52:18 +02:00
1 parent 3a76dbdb97
commit 416c31643b
4 files changed
+593 -114

No files matched your search

+438 -48
View File
@@ -1143,10 +1143,17 @@ backup_configs() {
validate_json() {
local file="$1"
if ! python3 -m json.tool "$file" >/dev/null 2>&1; then
return 1
# Strict JSON first...
if python3 -m json.tool "$file" >/dev/null 2>&1; then
return 0
fi
return 0
# ...otherwise fall back to JSONC (the renderer configs legitimately carry
# // and /* */ comments — that is not an error, it is by design).
if command -v node &>/dev/null && \
node -e "require('jsonc-parser').parse(require('fs').readFileSync(process.argv[1],'utf-8'),[],{allowTrailingComma:true,allowEmptyContent:true});" "$file" >/dev/null 2>&1; then
return 0
fi
return 1
}
validate_configs() {
@@ -1434,6 +1441,7 @@ cmd_cleanup_old_nitro() {
# =============================================================================
auto_setup_missing_repos() {
local cloned=false
local needs_link=false
if [ ! -d "$NITRO_CLIENT" ] || [ ! -d "$NITRO_CLIENT/.git" ]; then
echo ""
@@ -1443,7 +1451,7 @@ auto_setup_missing_repos() {
if git clone --depth 1 "$GIT_REPO_CLIENT" "$NITRO_CLIENT" >> "$LOG_FILE" 2>&1; then
spinner_stop ok
ok "Octane client cloned successfully"
cloned=true
cloned=true; needs_link=true
else
spinner_stop fail
die "Failed to clone Octane client from %s" "$GIT_REPO_CLIENT"
@@ -1458,32 +1466,75 @@ auto_setup_missing_repos() {
if git clone --depth 1 "$GIT_REPO_RENDERER" "$NITRO_RENDERER" >> "$LOG_FILE" 2>&1; then
spinner_stop ok
ok "Octane renderer cloned successfully"
cloned=true
cloned=true; needs_link=true
else
spinner_stop fail
die "Failed to clone Octane renderer from %s" "$GIT_REPO_RENDERER"
fi
fi
if [ "$cloned" = true ]; then
echo ""
info "Installing dependencies for cloned repos..."
# Also detect when either repo exists but was never linked
if [ "$needs_link" = false ]; then
if [ -d "$NITRO_RENDERER" ] && [ -d "$NITRO_RENDERER/.git" ]; then
# Check if yarn global link is registered — if not, we need to re-link
if [ ! -d "$(yarn global dir 2>/dev/null)/node_modules/@nitrots/nitro-renderer" ] 2>/dev/null && \
[ ! -L "$(yarn global dir 2>/dev/null)/node_modules/@nitrots/nitro-renderer" ] 2>/dev/null; then
needs_link=true
fi
fi
fi
if [ "$cloned" = true ] || [ "$needs_link" = true ]; then
echo ""
info "Installing dependencies..."
# Step 1: Install renderer deps
if [ -d "$NITRO_RENDERER" ] && [ -f "$NITRO_RENDERER/package.json" ]; then
spinner_start "Installing renderer deps..."
yarn_install_repo "$NITRO_RENDERER" --frozen-lockfile --prefer-offline >> "$LOG_FILE" 2>&1 || \
yarn_install_repo "$NITRO_RENDERER" --prefer-offline >> "$LOG_FILE" 2>&1 || \
yarn_install_repo "$NITRO_RENDERER" >> "$LOG_FILE" 2>&1 || warn "Renderer yarn install failed"
spinner_stop ok
ok "Renderer dependencies installed"
# Step 2: Register renderer as a global yarn link
spinner_start "Linking renderer package..."
cd "$NITRO_RENDERER"
yarn link >> "$LOG_FILE" 2>&1 || warn "yarn link in renderer failed (may already be linked)"
cd "$SCRIPT_DIR"
spinner_stop ok
ok "Renderer linked globally"
fi
# Step 3: Install client deps
if [ -d "$NITRO_CLIENT" ] && [ -f "$NITRO_CLIENT/package.json" ]; then
spinner_start "Installing client deps..."
yarn_install_repo "$NITRO_CLIENT" --frozen-lockfile --prefer-offline >> "$LOG_FILE" 2>&1 || \
yarn_install_repo "$NITRO_CLIENT" --prefer-offline >> "$LOG_FILE" 2>&1 || \
yarn_install_repo "$NITRO_CLIENT" >> "$LOG_FILE" 2>&1 || warn "Client yarn install failed"
spinner_stop ok
ok "Client dependencies installed"
# Step 4: Link renderer into client
spinner_start "Linking renderer to client..."
cd "$NITRO_CLIENT"
yarn link "@nitrots/nitro-renderer" >> "$LOG_FILE" 2>&1 || warn "yarn link to renderer failed (may already be linked)"
cd "$SCRIPT_DIR"
spinner_stop ok
ok "Renderer linked to client"
fi
# Step 5: Setup config files from examples
echo ""
info "Setting up configuration files..."
_setup_config_files
# Step 6: Create .nitro-build.json for JSONC mode
if [ ! -f "$NITRO_CLIENT/.nitro-build.json" ]; then
echo '{"jsonMode":"jsonc"}' > "$NITRO_CLIENT/.nitro-build.json"
ok "Created .nitro-build.json (jsonc mode)"
fi
# Step 7: Build client frontend
if [ -d "$NITRO_CLIENT" ] && [ -f "$NITRO_CLIENT/package.json" ]; then
spinner_start "Building client frontend..."
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
spinner_stop ok
@@ -1505,6 +1556,60 @@ auto_setup_missing_repos() {
fi
}
# Copy example config files to their runtime names, handling renamed files
# in the Octane repo (renderer-config..jsonc.example.json etc.)
_setup_config_files() {
local src_dir="$NITRO_SRC_DIR"
[ ! -d "$src_dir" ] && mkdir -p "$src_dir" 2>/dev/null
local copied=0
# Standard *.example files (ui-config, client-mode, etc.)
for ef in "$src_dir"/*.example; do
[ -f "$ef" ] || continue
local b=$(basename "$ef"); local tn="${b%.example}"
case "$tn" in *.*) ;; *) tn="$tn.json" ;; esac
if [ ! -f "$src_dir/$tn" ]; then
cp "$ef" "$src_dir/$tn" 2>/dev/null && copied=$((copied + 1))
fi
done
# Handle renamed renderer config files
# Repo has: renderer-config..jsonc.example.json / renderer-config.json.example.json
# Runtime needs: renderer-config.jsonc (JSONC) or renderer-config.json
local rc_jsonc="$src_dir/renderer-config..jsonc.example.json"
local rc_json="$src_dir/renderer-config.json.example.json"
if [ -f "$rc_jsonc" ] && [ ! -f "$src_dir/renderer-config.jsonc" ]; then
cp "$rc_jsonc" "$src_dir/renderer-config.jsonc" 2>/dev/null && copied=$((copied + 1))
fi
if [ -f "$rc_json" ] && [ ! -f "$src_dir/renderer-config.json" ]; then
cp "$rc_json" "$src_dir/renderer-config.json" 2>/dev/null && copied=$((copied + 1))
fi
# Copy to gamedata config dir too
if [ -d "$GAMEDATA_CONF_DIR" ]; then
for f in "$src_dir"/*.json "$src_dir"/*.jsonc; do
[ -f "$f" ] || continue
local fn=$(basename "$f")
# Skip example/source files
case "$fn" in *.example*|*.example) continue ;; esac
[ ! -f "$GAMEDATA_CONF_DIR/$fn" ] && cp "$f" "$GAMEDATA_CONF_DIR/$fn" 2>/dev/null || true
done
fi
# Copy to dist/configuration if it exists
local dcd="$NITRO_CLIENT/dist/configuration"
if [ -d "$dcd" ]; then
for f in "$src_dir"/*.json "$src_dir"/*.jsonc; do
[ -f "$f" ] || continue
local fn=$(basename "$f")
case "$fn" in *.example*|*.example) continue ;; esac
cp "$f" "$dcd/$fn" 2>/dev/null || true
done
fi
ok "%s config file(s) copied" "$copied"
}
# =============================================================================
# PRE-FLIGHT CHECKS
# =============================================================================
@@ -1629,6 +1734,10 @@ update_renderer() {
yarn_install_repo "$NITRO_RENDERER" || { spinner_stop fail; git_revert_repo "$NITRO_RENDERER"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
fi
spinner_stop ok; ok "Renderer dependencies installed"
# Ensure renderer is globally linked
cd "$NITRO_RENDERER"
yarn link >> "$LOG_FILE" 2>&1 || true
cd "$SCRIPT_DIR"
else
info "Renderer: already up to date"
fi
@@ -1670,6 +1779,10 @@ update_client() {
cd "$NITRO_CLIENT"
fi
spinner_stop ok
# Ensure renderer is linked into client
cd "$NITRO_CLIENT"
yarn link "@nitrots/nitro-renderer" >> "$LOG_FILE" 2>&1 || true
cd "$SCRIPT_DIR"
spinner_start "Building frontend..."
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
spinner_stop ok
@@ -1702,6 +1815,7 @@ sync_configs() {
[ -d "$d" ] && [ ! -w "$d" ] && sudo chown -R "$(whoami)":"$(whoami)" "$d" 2>/dev/null || true
done
local ec=0
# Standard *.example files (ui-config, client-mode, etc.)
for ef in "$NITRO_SRC_DIR"/*.example; do
[ -f "$ef" ] || continue
local b=$(basename "$ef"); local tn="${b%.example}"
@@ -1711,6 +1825,24 @@ sync_configs() {
[ -d "$dcd" ] && cp "$NITRO_SRC_DIR/$tn" "$dcd/$tn" 2>/dev/null || true
ec=$((ec+1))
done
# Repo ships renamed examples that don't match the standard *.example glob:
# renderer-config.json.example.json -> renderer-config.json
# renderer-config..jsonc.example.json -> renderer-config.jsonc
# (the ".." in that second name is a quirk of the upstream repo). Map them
# explicitly so we never emit garbage like renderer-config.jsonc.json.
local -A rc_map=(
["renderer-config.json.example.json"]="renderer-config.json"
["renderer-config..jsonc.example.json"]="renderer-config.jsonc"
)
for ex in "${!rc_map[@]}"; do
local src="$NITRO_SRC_DIR/$ex"
[ -f "$src" ] || continue
local tgt="${rc_map[$ex]}"
node "$ms" "$src" "$NITRO_SRC_DIR/$tgt" 2>/dev/null || true
node "$ms" "$src" "$GAMEDATA_CONF_DIR/$tgt" 2>/dev/null || true
[ -d "$dcd" ] && cp "$NITRO_SRC_DIR/$tgt" "$dcd/$tgt" 2>/dev/null || true
ec=$((ec+1))
done
ok "%s config file(s) synced" "$ec"
# Validate the generated configs
@@ -1736,42 +1868,16 @@ sync_configs() {
fi
info "Applying critical config URLs..."
NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$dcd" \
# Use the node-based sync so JSONC configs (which contain // comments and
# URLs like https://) are parsed and rewritten safely. The previous Python
# json.load choked on the comments and silently skipped the URL injection,
# leaving the example.com placeholders in place.
NITRO_SRC_DIR="$NITRO_SRC_DIR" NITRO_DIST_CONFIG_DIR="$dcd" NITRO_GAMEDATA_CONF_DIR="$GAMEDATA_CONF_DIR" \
NITRO_IMAGE_LIBRARY_URL="$NITRO_IMAGE_LIBRARY_URL" NITRO_HOF_FURNITURE_URL="$NITRO_HOF_FURNITURE_URL" \
NITRO_API_URL="$NITRO_API_URL" NITRO_SOCKET_URL="$NITRO_SOCKET_URL" \
NITRO_GAMEDATA_URL="$NITRO_GAMEDATA_URL" NITRO_ASSET_URL="$NITRO_ASSET_URL" \
NITRO_FURNI_ASSET_ICON_URL="$NITRO_FURNI_ASSET_ICON_URL" \
python3 -c '
import json, os
def to_jsonc(value):
if isinstance(value, dict): return {k: to_jsonc(v) for k, v in value.items()}
if isinstance(value, list): return [to_jsonc(v) for v in value]
if isinstance(value, str): return value.replace(".json5", ".jsonc")
return value
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)
data = to_jsonc(data)
for key in ["image.library.url", "hof.furni.url", "gamedata.url", "asset.url"]:
env_val = os.environ.get(f"NITRO_{key.upper().replace(chr(46), chr(95))}")
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(chr(46), chr(95))}")
if env_val: data[key] = env_val
if "gamedata.url" in data:
data["radio.url"] = data["gamedata.url"] + "/config/radio-stations.jsonc?t=%timestamp%"
data["soundboard.url"] = data["gamedata.url"] + "/config/soundboard-sounds.jsonc?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)}")
' && ok "All config URLs synced"
node "$SCRIPT_DIR/scripts/sync-nitro-urls.cjs" 2>/dev/null && ok "All config URLs synced" || warn "Config URL sync failed"
# Flush Redis so new config is picked up immediately
if command -v redis-cli &>/dev/null; then
@@ -1886,6 +1992,7 @@ ensure_gamedata_redirect() {
continue
fi
tmp=$(mktemp)
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
snippet=" # Redirect /gamedata/ to /gamedata/config/
location = /gamedata { return 301 /gamedata/config/; }
location = /gamedata/ { return 301 /gamedata/config/; }
@@ -1902,7 +2009,7 @@ ensure_gamedata_redirect() {
warn "Could not inject gamedata redirect into %s" "$conf"
continue
fi
bak="$conf.bak-$(date +%s)"
bak="/var/backups/nginx-cms/$(basename "$conf").bak-$(date +%s)"
$root_cmd cp "$conf" "$bak" 2>/dev/null || { rm -f "$tmp"; warn "Cannot backup %s" "$conf"; continue; }
if ! $root_cmd install -m 640 "$tmp" "$conf" 2>/dev/null; then
rm -f "$tmp" 2>/dev/null || true
@@ -1921,9 +2028,255 @@ ensure_gamedata_redirect() {
return 0
}
# Resolve the emulator's public game (WebSocket) port — the port the Nitro
# client actually connects to (socket.url -> wss://ws.<domain> -> here).
#
# NOTE: Emulator config.ini ships a stale `game.port=3000` that does NOT speak
# the game WebSocket (it is an unused legacy listener); the real game socket
# listens on 2096. We therefore do NOT trust config.ini and instead:
# 1. explicit NITRO_EMULATOR_GAME_PORT override
# 2. auto-detect a listening port of the emulator java process that is not a
# well-known non-game port (3000/3001/3002/3030/3306/5432/6379/80/443/...).
# This reliably yields 2096.
# 3. fall back to 2096 (the conventional Nitro/Polaris game port).
emulator_game_port() {
[ -n "${NITRO_EMULATOR_GAME_PORT:-}" ] && { echo "$NITRO_EMULATOR_GAME_PORT"; return; }
local pid
pid=$(pgrep -f 'jar.*[Pp]olaris' 2>/dev/null | head -1)
[ -z "$pid" ] && pid=$(pgrep -x java 2>/dev/null | head -1)
if [ -n "$pid" ] && command -v ss >/dev/null 2>&1; then
local ports
ports=$(ss -ltnp 2>/dev/null | grep -E "pid=$pid," | grep -oE ':[0-9]+ ' | tr -d ' :' | sort -n -u)
for cand in $ports; do
case "$cand" in 80|443|3000|3001|3002|3030|3306|5432|6379|8080|8081|8422|9443|9444|5000|22|53|38939) continue ;; *) echo "$cand"; return ;; esac
done
fi
echo 2096
}
# Ensure nginx terminates the game WebSocket. The client connects to
# wss://ws.<domain> (port 443) and that has to be proxied to the emulator's
# game port, otherwise the client loads but never joins the server. Idempotent:
# injects a server block (reusing the existing SSL cert), validates with
# `nginx -t`, and reloads. Derives the domain automatically so this self-heals
# on any hotel VPS.
ensure_ws_proxy() {
[ ${#NGINX_SITE_CONFS[@]} -eq 0 ] && return 0
local domain="${NITRO_DOMAIN:-}"
if [ -z "$domain" ]; then
for conf in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$conf" ] || continue
domain=$(grep -oE 'server_name[^;]+;' "$conf" 2>/dev/null | head -1 \
| tr -s ' ' '\n' | grep -vE 'server_name|;|127\.0\.0\.1|172\.|10\.|192\.168|localhost|\[?' | head -1)
[ -n "$domain" ] && break
done
fi
[ -z "$domain" ] && { warn "Could not derive domain for ws proxy — skipping"; return 0; }
local ws_name="ws.${domain}"
local gport; gport=$(emulator_game_port)
local root_cmd=""
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
local cert="" key=""
for conf in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$conf" ] || continue
cert=$(grep -m1 'ssl_certificate[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
key=$(grep -m1 'ssl_certificate_key[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
[ -n "$cert" ] && [ -n "$key" ] && break
done
[ -z "$cert" ] && cert="/etc/ssl/epicnabbo-backend.pem"
[ -z "$key" ] && key="/etc/ssl/epicnabbo-backend.key"
local conf
for conf in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$conf" ] || continue
if grep -qs "$ws_name" "$conf" 2>/dev/null; then
ok "Game WebSocket proxy already present for %s" "$ws_name"
continue
fi
local tmp; tmp=$(mktemp)
local snippet
snippet="server {
listen 9443 ssl;
listen [::]:9443 ssl;
http2 on;
server_name $ws_name;
ssl_certificate $cert;
ssl_certificate_key $key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
location / {
proxy_pass http://127.0.0.1:$gport;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \"upgrade\";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 86400;
}
}
"
printf "%s\n" "$snippet" > "$tmp"
cat "$conf" >> "$tmp" 2>/dev/null
local bak="/var/backups/nginx-cms/$(basename "$conf").bak-ws-$(date +%s)"
$root_cmd cp "$conf" "$bak" 2>/dev/null || { rm -f "$tmp"; warn "Cannot backup %s" "$conf"; continue; }
if ! $root_cmd install -m 640 "$tmp" "$conf" 2>/dev/null; then
rm -f "$tmp" 2>/dev/null || true
warn "Cannot update %s" "$conf"
continue
fi
rm -f "$tmp" 2>/dev/null || true
if ! sudo nginx -t >/dev/null 2>&1; then
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
warn "nginx -t failed — reverted ws proxy in %s" "$conf"
continue
fi
ok "Added game WebSocket proxy %s -> 127.0.0.1:%s" "$ws_name" "$gport"
done
}
# Ensure the Octane client's emulator game API is reachable same-origin. The
# client calls /api/auth/* (login, server-key, sso-token, register, change-*,
# remember, refresh, ...) and /api/badges/custom via api.url (same origin =
# the CMS domain). The Polaris game server serves these on its game port (2096);
# the CMS does NOT. Proxying them here keeps api.url same-origin (no CORS) while
# still reaching the emulator. Inserted before the generic `location /api/`
# (which sends everything else to the CMS on :3002) inside the HTTPS vhost that
# listens on 9443. Idempotent: skips when the block is already present.
ensure_api_proxy() {
[ ${#NGINX_SITE_CONFS[@]} -eq 0 ] && return 0
local gport; gport=$(emulator_game_port)
local root_cmd=""
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
local snippet=" # --- Nitro/Emulator game API (managed by update-Nitrov3.sh: ensure_api_proxy) ---
# Octane client calls /api/auth/* and /api/badges/custom via api.url (same
# origin). The emulator serves these on its game port; proxy them so the
# client stays same-origin (no CORS). Inserted before the generic /api/.
location /api/auth/ {
proxy_pass http://127.0.0.1:${gport};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
add_header Cache-Control \"no-cache, no-store, must-revalidate\";
}
location /api/badges/custom {
proxy_pass http://127.0.0.1:${gport};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
add_header Cache-Control \"no-cache, no-store, must-revalidate\";
}
"
local conf
for conf in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$conf" ] || continue
if grep -qs "location /api/auth/" "$conf" 2>/dev/null; then
ok "Emulator /api/auth proxy already present in %s" "$(basename "$conf")"
continue
fi
local bak="/var/backups/nginx-cms/$(basename "$conf").bak-api-$(date +%s)"
$root_cmd cp "$conf" "$bak" 2>/dev/null || { warn "Cannot backup %s" "$conf"; continue; }
# Insert the snippet before the first `location /api/ {` inside the
# server block that listens on 9443 (the HTTPS client vhost).
if ! SNIPPET="$snippet" $root_cmd perl -i -pe '
BEGIN { $https=0; $inserted=0; $snip=$ENV{SNIPPET}; }
if (/^\s*server\s*\{/) { $https=0; }
if (/^\s*listen\s+9443\b/) { $https=1; }
if ($https && !$inserted && /^\s*location\s+\/api\/\s*\{/) {
$_ = $snip . $_;
$inserted=1;
}
' "$conf" 2>/dev/null; then
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
warn "Failed to insert emulator /api proxy into %s" "$(basename "$conf")"
continue
fi
if ! sudo nginx -t >/dev/null 2>&1; then
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
warn "nginx -t failed — reverted emulator /api proxy in %s" "$(basename "$conf")"
continue
fi
ok "Added emulator /api proxy to %s" "$(basename "$conf")"
done
}
# Ensure a catch-all default_server exists on the nginx vhost port. L7 proxies
# such as Traefik health-check the upstream using the IP as the Host header,
# which matches no real vhost; without this block those checks hit the first
# server block (e.g. the ws proxy) and get a 400, marking the site unhealthy
# (HTTP 503). Answering /health with 200 keeps proxy health checks green.
ensure_default_server() {
[ ${#NGINX_SITE_CONFS[@]} -eq 0 ] && return 0
local found=false
for conf in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$conf" ] || continue
if grep -qs "listen.*default_server" "$conf" 2>/dev/null; then found=true; break; fi
done
$found && { ok "Default-server health block already present"; return 0; }
local root_cmd=""
[ "$(id -u)" -eq 0 ] || root_cmd="sudo -n"
local cert="" key=""
for conf in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$conf" ] || continue
cert=$(grep -m1 'ssl_certificate[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
key=$(grep -m1 'ssl_certificate_key[[:space:]]' "$conf" 2>/dev/null | awk '{print $2}' | tr -d ';')
[ -n "$cert" ] && [ -n "$key" ] && break
done
[ -z "$cert" ] && cert="/etc/ssl/epicnabbo-backend.pem"
[ -z "$key" ] && key="/etc/ssl/epicnabbo-backend.key"
local conf
for conf in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$conf" ] || continue
local tmp; tmp=$(mktemp)
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
cat > "$tmp" <<EOF
server {
listen 9443 ssl default_server;
listen [::]:9443 ssl default_server;
server_name _;
ssl_certificate $cert;
ssl_certificate_key $key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
location = /health { return 200 "OK"; }
location / { return 444; }
}
EOF
cat "$conf" >> "$tmp" 2>/dev/null
local bak="/var/backups/nginx-cms/$(basename "$conf").bak-default-$(date +%s)"
$root_cmd cp "$conf" "$bak" 2>/dev/null || { rm -f "$tmp"; warn "Cannot backup %s" "$conf"; continue; }
if ! $root_cmd install -m 640 "$tmp" "$conf" 2>/dev/null; then
rm -f "$tmp" 2>/dev/null || true
warn "Cannot update %s" "$conf"
continue
fi
rm -f "$tmp" 2>/dev/null || true
if ! sudo nginx -t >/dev/null 2>&1; then
$root_cmd cp "$bak" "$conf" 2>/dev/null || true
warn "nginx -t failed — reverted default-server block in %s" "$conf"
continue
fi
ok "Added default-server /health block to %s" "$(basename "$conf")"
done
}
do_restart() {
step 8 8 "Restart Services"
ensure_gamedata_redirect
ensure_default_server
ensure_ws_proxy
ensure_api_proxy
if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then
sudo systemctl restart "$EMULATOR_SERVICE" 2>/dev/null && ok "$EMULATOR_SERVICE restarted" || warn "Restart failed"
if ! wait_for_emulator_health; then
@@ -1959,7 +2312,15 @@ do_restart() {
else
warn "No service manager found"
fi
command -v nginx &>/dev/null && sudo nginx -t 2>/dev/null && sudo systemctl reload nginx 2>/dev/null && ok "Nginx reloaded"
# Reload nginx. Prefer `nginx -s reload` (the running master picks it up
# immediately); fall back to systemctl for hosts where nginx is a unit.
if command -v nginx &>/dev/null && sudo nginx -t >/dev/null 2>&1; then
if sudo nginx -s reload >/dev/null 2>&1 || sudo systemctl reload nginx >/dev/null 2>&1; then
ok "Nginx reloaded"
else
warn "Nginx reload failed"
fi
fi
command -v redis-cli &>/dev/null && redis-cli FLUSHALL 2>/dev/null && ok "Redis flushed"
}
@@ -1968,10 +2329,7 @@ do_restart() {
emulator_port_open() {
local port="${NITRO_EMULATOR_PORT:-}"
if [ -z "$port" ]; then
port=$(grep -m1 '^game\.port=' "$EMULATOR_MODULE/target/config.ini" 2>/dev/null | cut -d= -f2)
fi
if [ -z "$port" ]; then
port=30000
port=$(emulator_game_port)
fi
if command -v redis-cli >/dev/null 2>&1; then
local p; p=$(redis-cli GET nitro:emulator:port 2>/dev/null)
@@ -2302,9 +2660,29 @@ cmd_flyaway_repair() {
yarn_install_repo "$repo" --frozen-lockfile --prefer-offline 2>/dev/null || warn "yarn install still failed for %s" "$name"
fi
fi
# Re-link renderer: register global link + link into client
if [ "$repo" = "$NITRO_RENDERER" ] && [ -d "$repo" ]; then
spinner_start "Re-linking renderer..."
cd "$NITRO_RENDERER"
yarn link >> "$LOG_FILE" 2>&1 || warn "yarn link in renderer failed"
cd "$SCRIPT_DIR"
cd "$NITRO_CLIENT"
yarn link "@nitrots/nitro-renderer" >> "$LOG_FILE" 2>&1 || warn "yarn link to renderer failed"
cd "$SCRIPT_DIR"
spinner_stop ok
ok "Renderer re-linked"
fi
repaired=$((repaired + 1))
done
# Ensure .nitro-build.json exists
if [ ! -f "$NITRO_CLIENT/.nitro-build.json" ]; then
echo '{"jsonMode":"jsonc"}' > "$NITRO_CLIENT/.nitro-build.json"
ok "Created .nitro-build.json (jsonc mode)"
fi
# Realign Flyway schema history with the packaged migrations
step 3 7 "$(_t "Flyway DB Repair")"
if flyaway_repair_db; then
@@ -2826,6 +3204,18 @@ main() {
fi
if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then NITRO_SITE_URL="$APP_URL"; fi
# Auto-detect the site domain from the nginx vhost when no explicit URL is
# configured (APP_URL / NITRO_SITE_URL / --url). This lets the update script
# self-configure the client URLs and the ws.<domain> proxy on a fresh hotel
# VPS without manual .env edits.
if [ -z "${NITRO_SITE_URL:-}" ]; then
for _c in "${NGINX_SITE_CONFS[@]}"; do
[ -f "$_c" ] || continue
NITRO_SITE_URL=$(grep -oE 'server_name[^;]+;' "$_c" 2>/dev/null | head -1 \
| tr -s ' ' '\n' | grep -vE 'server_name|;|127\.0\.0\.1|172\.|10\.|192\.168|localhost|\[?' | head -1)
[ -n "$NITRO_SITE_URL" ] && NITRO_SITE_URL="https://$NITRO_SITE_URL" && break
done
fi
case "${NITRO_SITE_URL:-}" in
https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;;
http://*) NITRO_WS_PROTO="ws://" ; NITRO_DOMAIN="${NITRO_SITE_URL#http://}" ;;