feat: bulletproof updater + fixes for client links (icons/furniture/gamedata)
This commit is contained in:
1 parent
f2ac4745d4
commit
678d22ceab
3 files changed
+346
-74
No files matched your search
+49
-10
@@ -8,19 +8,30 @@ if (!exampleFile || !targetFile) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
// Merge the upstream example into the live config with strict "add missing,
|
||||
// never remove or overwrite" semantics so hotel configs survive every update:
|
||||
// - every key already present in the live config is PRESERVED (scalars,
|
||||
// arrays and nested objects always win over the upstream example),
|
||||
// - nested objects are merged recursively so upstream-only sub-keys are
|
||||
// added while the hotel's own sub-keys stay untouched,
|
||||
// - keys that do not exist in the live config yet are ADDED from the example,
|
||||
// - a source object NEVER replaces an existing scalar/array (would corrupt).
|
||||
function deepMerge(target, source) {
|
||||
const result = { ...target };
|
||||
for (const key of Object.keys(source)) {
|
||||
if (
|
||||
source[key] !== null &&
|
||||
typeof source[key] === "object" &&
|
||||
!Array.isArray(source[key])
|
||||
) {
|
||||
result[key] = deepMerge(target[key] || {}, source[key]);
|
||||
} else {
|
||||
if (!(key in result)) {
|
||||
result[key] = source[key];
|
||||
if (isPlainObject(source[key])) {
|
||||
if (isPlainObject(result[key])) {
|
||||
result[key] = deepMerge(result[key], source[key]);
|
||||
} else if (!(key in result)) {
|
||||
result[key] = deepMerge({}, source[key]);
|
||||
}
|
||||
// Else: live value is a scalar/array/null -> keep it untouched.
|
||||
} else if (!(key in result)) {
|
||||
result[key] = source[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -69,4 +80,32 @@ try {
|
||||
}
|
||||
|
||||
const merged = deepMerge(current, example);
|
||||
fs.writeFileSync(targetFile, `${JSON.stringify(merged, null, 4)}\n`);
|
||||
|
||||
// Sanity: the merged result must stay an object and the written file must be
|
||||
// re-parseable. If anything goes wrong we keep the live file untouched rather
|
||||
// than shipping a corrupted config to the client.
|
||||
if (merged === undefined || merged === null || typeof merged !== "object") {
|
||||
process.stderr.write(
|
||||
`[merge-config] refused to write non-object result for ${targetFile}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Verify the result parses round-trip before overwriting the live config.
|
||||
const serialized = `${JSON.stringify(merged, null, 4)}\n`;
|
||||
let check;
|
||||
try {
|
||||
check = parse(serialized, [], {
|
||||
allowTrailingComma: true,
|
||||
allowEmptyContent: true,
|
||||
});
|
||||
} catch {
|
||||
check = undefined;
|
||||
}
|
||||
if (!check || typeof check !== "object" || Array.isArray(check)) {
|
||||
process.stderr.write(
|
||||
`[merge-config] refused to write unparseable result for ${targetFile}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
fs.writeFileSync(targetFile, serialized);
|
||||
@@ -13,9 +13,10 @@
|
||||
// NITRO_IMAGE_LIBRARY_URL, NITRO_HOF_FURNITURE_URL, NITRO_API_URL,
|
||||
// NITRO_SOCKET_URL, NITRO_GAMEDATA_URL, NITRO_ASSET_URL,
|
||||
// NITRO_FURNI_ASSET_ICON_URL
|
||||
// NITRO_URL_FORCE=1 (optional) overwrite existing values too
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { parse, modify } = require("jsonc-parser");
|
||||
const { parse } = require("jsonc-parser");
|
||||
|
||||
const FILES = [
|
||||
"renderer-config.json",
|
||||
@@ -23,6 +24,22 @@ const FILES = [
|
||||
"ui-config.json",
|
||||
];
|
||||
|
||||
const FORCE = process.env.NITRO_URL_FORCE === "1";
|
||||
|
||||
// Values that must never survive into a production config: placeholder or
|
||||
// loopback hosts that originate from the upstream example files. When a key
|
||||
// still holds one of these, it has been newly added by the merge step and has
|
||||
// to be filled from the environment. A real, already-configured URL (the
|
||||
// hotel's own domain) is NEVER rewritten, so hotel links stay exactly as the
|
||||
// hotel owner configured them.
|
||||
const PLACEHOLDER_RE =
|
||||
/(localhost|127\.0\.0\.1|0\.0\.0\.0|::1|\.example\.com|nitro\.example\.|hotel\.example\.|example\.org|example\.net|:5173|:2096|\$\{)/i;
|
||||
|
||||
function isPlaceholder(value) {
|
||||
if (!value) return true;
|
||||
return PLACEHOLDER_RE.test(value);
|
||||
}
|
||||
|
||||
function env(name) {
|
||||
return process.env[name]?.length ? process.env[name] : null;
|
||||
}
|
||||
@@ -36,9 +53,14 @@ function applyOverrides(data) {
|
||||
const asset = env("NITRO_ASSET_URL");
|
||||
const icon = env("NITRO_FURNI_ASSET_ICON_URL");
|
||||
|
||||
// Add-only, placeholder-aware URL fill-in. Existing real values are kept,
|
||||
// crypto.* keys are never touched, and only missing/placeholder keys are
|
||||
// filled from the environment. NITRO_URL_FORCE=1 opts back into overwriting.
|
||||
const set = (key, value) => {
|
||||
if (value && !(key in data)) data[key] = value;
|
||||
if (value) data[key] = value;
|
||||
if (!value || key.startsWith("crypto.")) return;
|
||||
if (FORCE || !(key in data) || isPlaceholder(data[key])) {
|
||||
data[key] = value;
|
||||
}
|
||||
};
|
||||
|
||||
set("image.library.url", image);
|
||||
@@ -49,12 +71,19 @@ function applyOverrides(data) {
|
||||
set("asset.url", asset);
|
||||
set("furni.asset.icon.url", icon);
|
||||
|
||||
if (gamedata) {
|
||||
if (gamedata && (!("radio.url" in data) || isPlaceholder(data["radio.url"]))) {
|
||||
data["radio.url"] = `${gamedata}/config/radio-stations.jsonc?t=%timestamp%`;
|
||||
}
|
||||
if (
|
||||
gamedata &&
|
||||
(!("soundboard.url" in data) || isPlaceholder(data["soundboard.url"]))
|
||||
) {
|
||||
data["soundboard.url"] =
|
||||
`${gamedata}/config/soundboard-sounds.jsonc?t=%timestamp%`;
|
||||
}
|
||||
if ("show.google.ads" in data) data["show.google.ads"] = false;
|
||||
// Never force ads on/off over a hotel's own choice; only default to "off"
|
||||
// when the key does not exist yet (prevents external ad scripts from 404ing).
|
||||
if (!("show.google.ads" in data)) data["show.google.ads"] = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
+263
-59
@@ -9,8 +9,8 @@ set -Eeo pipefail
|
||||
# =============================================================================
|
||||
# CONSTANTS
|
||||
# =============================================================================
|
||||
readonly SCRIPT_VERSION="8.0.2"
|
||||
readonly SCRIPT_BUILD="20260803"
|
||||
readonly SCRIPT_VERSION="8.0.4"
|
||||
readonly SCRIPT_BUILD="20260830"
|
||||
readonly SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly START_TIME=$(date +%s)
|
||||
@@ -216,7 +216,8 @@ load_language() {
|
||||
["Cleanup old Nitro dirs"]="Oude Nitro mappen opruimen"
|
||||
["Rebuild Frontend"]="Frontend Herbouwen"
|
||||
["Rebuild Emulator"]="Emulator Herbouwen"
|
||||
["Select [0-23]"]="Selecteer [0-23]"
|
||||
["Select [0-24]"]="Selecteer [0-24]"
|
||||
["Fix Client Links (icons/furniture/gamedata)"]="Fixeer Client Links (icons/meubels/gamedata)"
|
||||
["Press Enter to continue..."]="Druk op Enter om door te gaan..."
|
||||
["Goodbye!"]="Tot ziens!"
|
||||
# ── HELP ──────────────────────────────────────────────
|
||||
@@ -531,7 +532,8 @@ load_language() {
|
||||
["Cleanup old Nitro dirs"]="Nettoyer les anciens répertoires Nitro"
|
||||
["Rebuild Frontend"]="Reconstruire le Frontend"
|
||||
["Rebuild Emulator"]="Reconstruire l'Emulateur"
|
||||
["Select [0-23]"]="Sélectionner [0-23]"
|
||||
["Select [0-24]"]="Sélectionner [0-24]"
|
||||
["Fix Client Links (icons/furniture/gamedata)"]="Corriger les liens Client (icônes/meubles/gamedata)"
|
||||
["Press Enter to continue..."]="Appuyez sur Entrée pour continuer..."
|
||||
["Goodbye!"]="Au revoir !"
|
||||
["Start update?"]="Lancer la mise à jour ?"
|
||||
@@ -618,7 +620,8 @@ load_language() {
|
||||
["Cleanup old Nitro dirs"]="Limpiar directorios Nitro antiguos"
|
||||
["Rebuild Frontend"]="Reconstruir Frontend"
|
||||
["Rebuild Emulator"]="Reconstruir Emulador"
|
||||
["Select [0-23]"]="Seleccionar [0-23]"
|
||||
["Select [0-24]"]="Seleccionar [0-24]"
|
||||
["Fix Client Links (icons/furniture/gamedata)"]="Arreglar enlaces del Cliente (iconos/muebles/gamedata)"
|
||||
["Press Enter to continue..."]="Presione Enter para continuar..."
|
||||
["Goodbye!"]="¡Hasta luego!"
|
||||
["Start update?"]="¿Iniciar actualización?"
|
||||
@@ -668,7 +671,8 @@ load_language() {
|
||||
["Cleanup old Nitro dirs"]="Alte Nitro-Verzeichnisse aufräumen"
|
||||
["Rebuild Frontend"]="Frontend neu bauen"
|
||||
["Rebuild Emulator"]="Emulator neu bauen"
|
||||
["Select [0-23]"]="Auswählen [0-23]"
|
||||
["Select [0-24]"]="Auswählen [0-24]"
|
||||
["Fix Client Links (icons/furniture/gamedata)"]="Client-Links reparieren (Icons/Möbel/Gamedata)"
|
||||
["Press Enter to continue..."]="Drücken Sie Enter zum Fortfahren..."
|
||||
["Goodbye!"]="Auf Wiedersehen!"
|
||||
["Start update?"]="Update starten?"
|
||||
@@ -718,7 +722,8 @@ load_language() {
|
||||
["Cleanup old Nitro dirs"]="Pulisci vecchie cartelle Nitro"
|
||||
["Rebuild Frontend"]="Ricostruisci Frontend"
|
||||
["Rebuild Emulator"]="Ricostruisci Emulatore"
|
||||
["Select [0-23]"]="Seleziona [0-23]"
|
||||
["Select [0-24]"]="Seleziona [0-24]"
|
||||
["Fix Client Links (icons/furniture/gamedata)"]="Correggi link Client (icone/mobili/gamedata)"
|
||||
["Press Enter to continue..."]="Premi Invio per continuare..."
|
||||
["Goodbye!"]="Arrivederci!"
|
||||
["Start update?"]="Avviare aggiornamento?"
|
||||
@@ -1826,6 +1831,17 @@ update_emulator() {
|
||||
cd "$EMULATOR_MODULE"
|
||||
backup_binaries
|
||||
ROLLBACK_NEEDED=true
|
||||
# The emulator reads its live config from target/config.ini (gitignored,
|
||||
# so git pull never touches it). Guard it anyway: if the Maven build ever
|
||||
# rebuilds target/ without it, the live config is restored verbatim so
|
||||
# the DB/hotel settings survive every update untouched.
|
||||
local emu_cfg="$EMULATOR_MODULE/target/config.ini"
|
||||
local emu_cfg_bak=""
|
||||
if [ -f "$emu_cfg" ]; then
|
||||
emu_cfg_bak="$(mktemp -d)/config.ini"
|
||||
cp "$emu_cfg" "$emu_cfg_bak" 2>/dev/null && info "Emulator config.ini backed up" \
|
||||
|| { rm -rf "${emu_cfg_bak%/config.ini}" 2>/dev/null || true; emu_cfg_bak=""; }
|
||||
fi
|
||||
spinner_start "Building emulator (parallel)..."
|
||||
# Prefer offline mode when the local Maven repo is warm; fall back to
|
||||
# online if offline resolution fails. ccache speeds up repeated JNI/C++
|
||||
@@ -1840,6 +1856,20 @@ update_emulator() {
|
||||
git_revert_repo "$EMULATOR_REPO"
|
||||
die "Maven build failed (see: %s)" "$LOG_FILE"
|
||||
fi
|
||||
# Restore the live config if the build produced a target/ without it.
|
||||
if [ ! -f "$emu_cfg" ]; then
|
||||
if [ -n "$emu_cfg_bak" ] && [ -f "$emu_cfg_bak" ]; then
|
||||
mkdir -p "$EMULATOR_MODULE/target"
|
||||
cp "$emu_cfg_bak" "$emu_cfg" 2>/dev/null && ok "Emulator config.ini restored (preserved across build)"
|
||||
fi
|
||||
else
|
||||
info "Emulator config.ini preserved"
|
||||
fi
|
||||
if [ ! -f "$emu_cfg" ]; then
|
||||
fail "No target/config.ini — emulator would start with defaults!"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
[ -n "$emu_cfg_bak" ] && rm -rf "${emu_cfg_bak%/config.ini}" 2>/dev/null || true
|
||||
local jar=$(find target -maxdepth 1 -name 'Polaris-*-jar-with-dependencies.jar' -printf '%T@ %p\n' 2>/dev/null | sort -rn | sed -n '1s/^[0-9.]* //p' | xargs -r basename 2>/dev/null || echo "")
|
||||
[ -z "$jar" ] && die "No jar found"
|
||||
EMULATOR_BUILT=true
|
||||
@@ -1872,31 +1902,39 @@ update_renderer() {
|
||||
# Let the caller fail the parallel group; the state file records it.
|
||||
warn "Renderer update failed (git error)"
|
||||
return 1
|
||||
elif [ $grc -eq 0 ]; then
|
||||
fi
|
||||
if [ $grc -eq 0 ]; then
|
||||
HAD_UPDATES=true; UPDATED_REPOS+=("Octane-Renderer")
|
||||
cd "$NITRO_RENDERER"
|
||||
spinner_start "Installing deps..."
|
||||
if ! yarn_install_repo "$NITRO_RENDERER" --frozen-lockfile --prefer-offline; then
|
||||
warn "Renderer: yarn install failed — cleaning node_modules and retrying"
|
||||
yarn_reset_repo "$NITRO_RENDERER"
|
||||
yarn_install_repo "$NITRO_RENDERER" || { spinner_stop fail; git_revert_repo "$NITRO_RENDERER"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
|
||||
fi
|
||||
cd "$NITRO_RENDERER"
|
||||
if [ ! -d node_modules/pixi.js ]; then
|
||||
warn "Renderer: node_modules incomplete — resetting repo cache and doing full install"
|
||||
# Parallel yarn installs can corrupt a shared cache; resetting the
|
||||
# repo's own cache folder guarantees a clean fetch from the registry.
|
||||
yarn_reset_repo "$NITRO_RENDERER"
|
||||
yarn_install_repo "$NITRO_RENDERER" || { spinner_stop fail; git_revert_repo "$NITRO_RENDERER"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
|
||||
fi
|
||||
spinner_stop ok; ok "Renderer dependencies installed"
|
||||
# Ensure renderer is globally linked
|
||||
cd "$NITRO_RENDERER"
|
||||
yarn link >> "$LOG_FILE" 2>&1 || true
|
||||
cd "$SCRIPT_DIR"
|
||||
info "Renderer pulled (%s)" "$(git -C "$NITRO_RENDERER" log --oneline -1 2>/dev/null || true)"
|
||||
else
|
||||
info "Renderer: already up to date"
|
||||
fi
|
||||
# Renderer is consumed by the Octane client build through the global
|
||||
# `@nitrots/nitro-renderer` link — it needs its SOURCE updated (git pull
|
||||
# above) and its node_modules present, nothing more. We deliberately do NOT
|
||||
# rebuild or reinstall a working node_modules: repeated parallel yarn
|
||||
# installs corrupt the cache and are how the client build silently breaks.
|
||||
if [ -d "$NITRO_RENDERER/node_modules" ]; then
|
||||
info "Renderer node_modules present — skipping yarn install"
|
||||
else
|
||||
spinner_start "Installing renderer deps (first run)..."
|
||||
if yarn_install_repo "$NITRO_RENDERER" --frozen-lockfile --prefer-offline; then
|
||||
spinner_stop ok; ok "Renderer dependencies installed"
|
||||
elif yarn_install_repo "$NITRO_RENDERER"; then
|
||||
spinner_stop ok; ok "Renderer dependencies installed (online)"
|
||||
else
|
||||
spinner_stop fail
|
||||
warn "Renderer: node_modules still missing — Octane build may fail"
|
||||
fi
|
||||
fi
|
||||
# Idempotent global registration (no install involved) so the client build
|
||||
# can still `yarn link "@nitrots/nitro-renderer"`. Not fatal when absent.
|
||||
if [ ! -e "$HOME/.config/yarn/link/@nitrots/nitro-renderer" ] \
|
||||
&& [ ! -e "/usr/local/share/.config/yarn/link/@nitrots/nitro-renderer" ]; then
|
||||
cd "$NITRO_RENDERER"
|
||||
yarn link >> "$LOG_FILE" 2>&1 || warn "Renderer global link not registered"
|
||||
cd "$SCRIPT_DIR"
|
||||
fi
|
||||
fix_perms
|
||||
return 0
|
||||
}
|
||||
@@ -1941,6 +1979,15 @@ update_client() {
|
||||
cd "$SCRIPT_DIR"
|
||||
spinner_start "Building frontend..."
|
||||
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
||||
# Hard guarantee: the served entrypoint must exist, otherwise the
|
||||
# client is broken. Revert the repo and restore the previous dist
|
||||
# so the hotel keeps serving a working client.
|
||||
if [ ! -f "$NITRO_CLIENT/dist/index.html" ]; then
|
||||
spinner_stop fail
|
||||
tail -30 "$LOG_FILE" || true
|
||||
git_revert_repo "$NITRO_CLIENT"
|
||||
die "Octane build produced no dist/index.html — reverted (see: %s)" "$LOG_FILE"
|
||||
fi
|
||||
spinner_stop ok
|
||||
else
|
||||
spinner_stop fail
|
||||
@@ -2092,11 +2139,9 @@ sync_configs() {
|
||||
# Validate the generated configs
|
||||
validate_configs "$NITRO_SRC_DIR"
|
||||
|
||||
# Remove legacy .json5 leftovers — the client only supports JSON/JSONC
|
||||
for d in "$NITRO_SRC_DIR" "$dcd" "$GAMEDATA_CONF_DIR"; do
|
||||
[ -d "$d" ] && find "$d" -maxdepth 1 -name '*.json5' -type f -delete 2>/dev/null || true
|
||||
done
|
||||
ok "Legacy .json5 files removed"
|
||||
# NOTE: legacy *.json5 leftovers in the config dirs are intentionally left
|
||||
# in place — this updater never deletes user files, it only adds what is
|
||||
# missing from the upstream examples.
|
||||
|
||||
# Pre-flight: warn if NITRO_IMAGE_LIBRARY_URL looks wrong before applying configs
|
||||
if [ -n "$NITRO_IMAGE_LIBRARY_URL" ]; then
|
||||
@@ -2121,6 +2166,7 @@ sync_configs() {
|
||||
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" \
|
||||
NITRO_URL_FORCE="${NITRO_URL_FORCE:-0}" \
|
||||
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
|
||||
@@ -3343,10 +3389,11 @@ cmd_interactive() {
|
||||
echo -e " ${C_RED}${C_BOLD}21)${C_RESET} ${E_WRENCH} $(_t "Flyaway Repair")"
|
||||
echo -e " ${C_GREEN}${C_BOLD}22)${C_RESET} ${E_ROCKET} $(_t "Auto-setup missing repos")"
|
||||
echo -e " ${C_YELLOW}${C_BOLD}23)${C_RESET} ${E_BROOM} $(_t "Cleanup old Nitro dirs")"
|
||||
echo -e " ${C_GREEN}${C_BOLD}24)${C_RESET} ${E_GLOBE} $(_t "Fix Client Links (icons/furniture/gamedata)")"
|
||||
echo ""
|
||||
echo -e " ${C_RED}${C_BOLD}0)${C_RESET} $(_t "Exit")"
|
||||
echo ""
|
||||
echo -en " ${C_CYAN}${C_BOLD}$(_t "Select [0-23]")${C_RESET}: "
|
||||
echo -en " ${C_CYAN}${C_BOLD}$(_t "Select [0-24]")${C_RESET}: "
|
||||
local choice; read -rt 30 choice
|
||||
|
||||
case "$choice" in
|
||||
@@ -3387,6 +3434,7 @@ cmd_interactive() {
|
||||
21) (cmd_flyaway_repair) ;;
|
||||
22) (cmd_setup) ;;
|
||||
23) (cmd_cleanup_old_nitro) ;;
|
||||
24) (cmd_fixlinks) ;;
|
||||
0|q|Q) echo -e "\n ${C_GREEN}$(_t "Goodbye!")${C_RESET}"; cursor_show; exit 0 ;;
|
||||
*) warn "Invalid selection" ;;
|
||||
esac
|
||||
@@ -3397,6 +3445,182 @@ cmd_interactive() {
|
||||
done
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CLIENT LINK FIXER (URLs / icons / furniture / gamedata)
|
||||
# =============================================================================
|
||||
# Resolve the public site URL the client configs must point at. Order:
|
||||
# explicit NITRO_SITE_URL / --url > APP_URL > nginx server_name vhost. This
|
||||
# lets the script self-configure on ANY hotel VPS without manual .env edits.
|
||||
resolve_site_url() {
|
||||
if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then NITRO_SITE_URL="$APP_URL"; fi
|
||||
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
|
||||
NITRO_SITE_URL="${NITRO_SITE_URL%/}"
|
||||
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
|
||||
}
|
||||
|
||||
# Derive every client-facing URL (icons, furniture/gamedata configs, bundled
|
||||
# assets, websocket...) from the detected site. Explicit NITRO_*_URL values in
|
||||
# .env win; everything else is computed and sanitized so the client never ends
|
||||
# up pointed at /nitro-client/ or a path missing its trailing slash.
|
||||
compute_nitro_urls() {
|
||||
[ -n "${NITRO_SITE_URL:-}" ] || resolve_site_url
|
||||
NITRO_IMAGE_LIBRARY_URL="${NITRO_IMAGE_LIBRARY_URL:-${NITRO_SITE_URL:-}/gamedata/c_images/}"
|
||||
NITRO_HOF_FURNITURE_URL="${NITRO_HOF_FURNITURE_URL:-${NITRO_SITE_URL:-}/gamedata/icons}"
|
||||
NITRO_API_URL="${NITRO_API_URL:-${NITRO_SITE_URL:-}}"
|
||||
NITRO_SOCKET_URL="${NITRO_SOCKET_URL:-${NITRO_WS_PROTO}ws.${NITRO_DOMAIN}}"
|
||||
NITRO_GAMEDATA_URL="${NITRO_GAMEDATA_URL:-${NITRO_SITE_URL:-}/gamedata}"
|
||||
NITRO_ASSET_URL="${NITRO_ASSET_URL:-${NITRO_SITE_URL:-}/gamedata/bundled}"
|
||||
NITRO_FURNI_ASSET_ICON_URL="${NITRO_FURNI_ASSET_ICON_URL:-${NITRO_SITE_URL:-}/gamedata/icons/%libname%%param%_icon.png}"
|
||||
# Normalize slashes.
|
||||
NITRO_API_URL="${NITRO_API_URL%/}"
|
||||
NITRO_GAMEDATA_URL="${NITRO_GAMEDATA_URL%/}"
|
||||
NITRO_ASSET_URL="${NITRO_ASSET_URL%/}"
|
||||
NITRO_HOF_FURNITURE_URL="${NITRO_HOF_FURNITURE_URL%/}"
|
||||
NITRO_FURNI_ASSET_ICON_URL="${NITRO_FURNI_ASSET_ICON_URL%/}"
|
||||
NITRO_IMAGE_LIBRARY_URL="${NITRO_IMAGE_LIBRARY_URL%/}"
|
||||
[ -n "$NITRO_IMAGE_LIBRARY_URL" ] && NITRO_IMAGE_LIBRARY_URL="$NITRO_IMAGE_LIBRARY_URL/"
|
||||
# Never let these point at the client's own path.
|
||||
case "$NITRO_IMAGE_LIBRARY_URL" in
|
||||
*"/nitro-client"*) NITRO_IMAGE_LIBRARY_URL="${NITRO_SITE_URL:-}/gamedata/c_images/"; warn "Corrected NITRO_IMAGE_LIBRARY_URL (was /nitro-client/)" ;;
|
||||
esac
|
||||
case "$NITRO_GAMEDATA_URL" in
|
||||
*"/nitro-client"*) NITRO_GAMEDATA_URL="${NITRO_SITE_URL:-}/gamedata"; warn "Corrected NITRO_GAMEDATA_URL (was /nitro-client/)" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# HTTP answer code for a URL (000 = unreachable/curl failed). Never aborts the
|
||||
# caller, even under `set -e` — a failed probe is reported as 000, not an error.
|
||||
http_status() {
|
||||
local code
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 --max-redirs 4 -k -L "$1" 2>/dev/null) || code=000
|
||||
if [ -n "$code" ]; then echo "$code"; else echo 000; fi
|
||||
}
|
||||
|
||||
# TCP reachability probe for the websocket endpoint (curl cannot talk `wss://`).
|
||||
check_socket() {
|
||||
local url="$1" hostport host port
|
||||
[ -n "$url" ] || { warn "$(_t "Socket not configured")"; bad_urls=$((bad_urls + 1)); return 0; }
|
||||
hostport="${url#*://}"; hostport="${hostport%%/*}"
|
||||
case "$hostport" in
|
||||
*:*) host="${hostport%%:*}"; port="${hostport##*:}" ;;
|
||||
*) host="$hostport"; port="443" ;;
|
||||
esac
|
||||
if timeout 5 bash -c "exec 3<>/dev/tcp/'$host'/'$port'" 2>/dev/null; then
|
||||
ok "$(_t "Socket reachable") (%s:%s)" "$host" "$port"
|
||||
ok_urls=$((ok_urls + 1))
|
||||
else
|
||||
fail "$(_t "Socket unreachable") (%s:%s)" "$host" "$port"
|
||||
bad_urls=$((bad_urls + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# Recompute + force-write every client-facing URL into renderer-config and
|
||||
# ui-config (source, dist mirror and gamedata mirror), verify the on-disk asset
|
||||
# directories exist and that the live URLs actually answer, then reload nginx
|
||||
# and flush Redis so the client picks everything up. Self-heals stale or
|
||||
# placeholder links (icons, furniture/gamedata, bundled assets, socket) on any
|
||||
# system — crypto.* settings are still never touched.
|
||||
cmd_fixlinks() {
|
||||
acquire_lock
|
||||
|
||||
step 1 5 "$(_t "Detect Site & URLs")"
|
||||
echo ""
|
||||
resolve_site_url
|
||||
compute_nitro_urls
|
||||
if [ -z "$NITRO_SITE_URL" ]; then
|
||||
fail "$(_t "No site URL detected (set APP_URL or NITRO_SITE_URL)")"
|
||||
release_lock
|
||||
return 1
|
||||
fi
|
||||
echo -e " ${C_CYAN}$(_t "Site:")${C_RESET} ${C_GREEN}${NITRO_SITE_URL}${C_RESET}"
|
||||
echo -e " ${C_CYAN}$(_t "Socket:")${C_RESET} ${C_GREEN}${NITRO_SOCKET_URL}${C_RESET}"
|
||||
echo -e " ${C_CYAN}$(_t "Gamedata:")${C_RESET} ${C_GREEN}${NITRO_GAMEDATA_URL}${C_RESET}"
|
||||
echo -e " ${C_CYAN}$(_t "Images:")${C_RESET} ${C_GREEN}${NITRO_IMAGE_LIBRARY_URL}${C_RESET}"
|
||||
|
||||
step 2 5 "$(_t "Verify on-disk asset paths")"
|
||||
echo ""
|
||||
local misses=0
|
||||
local gamedata_root; gamedata_root=$(dirname "$GAMEDATA_CONF_DIR")
|
||||
for d in "$gamedata_root/c_images" "$gamedata_root/bundled" "$gamedata_root/icons" "$GAMEDATA_CONF_DIR"; do
|
||||
if [ -d "$d" ]; then ok "%s" "$d"; else fail "Missing dir: %s" "$d"; misses=$((misses + 1)); fi
|
||||
done
|
||||
if [ -f "$GAMEDATA_CONF_DIR/FurnitureData.json" ]; then
|
||||
ok "FurnitureData.json present"
|
||||
else
|
||||
fail "Missing: %s/FurnitureData.json" "$GAMEDATA_CONF_DIR"
|
||||
misses=$((misses + 1))
|
||||
fi
|
||||
if [ "$misses" -gt 0 ]; then warn "%s path(s) missing — some assets will 404" "$misses"; fi
|
||||
|
||||
step 3 5 "$(_t "Write corrected links (forced)")"
|
||||
echo ""
|
||||
local dcd="$NITRO_CLIENT/dist/configuration"
|
||||
mkdir -p "$dcd" "$NITRO_SRC_DIR" "$GAMEDATA_CONF_DIR" 2>/dev/null || true
|
||||
if 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" \
|
||||
NITRO_URL_FORCE=1 \
|
||||
node "$SCRIPT_DIR/scripts/sync-nitro-urls.cjs" 2>/dev/null; then
|
||||
ok "$(_t "Links written to configs")"
|
||||
else
|
||||
fail "$(_t "Could not write links")"
|
||||
fi
|
||||
validate_configs "$NITRO_SRC_DIR"
|
||||
|
||||
step 4 5 "$(_t "Validate links over HTTP")"
|
||||
echo ""
|
||||
if [ "$misses" -gt 0 ]; then warn "$(_t "Skipping some URL checks — on-disk paths missing")"; fi
|
||||
local chk ok_urls=0 bad_urls=0
|
||||
check_url() {
|
||||
local label="$1" url="$2" msg=""
|
||||
[ -n "$url" ] || { return 0; }
|
||||
chk=$(http_status "$url")
|
||||
msg="${label}: ${url} (HTTP ${chk})"
|
||||
case "$chk" in
|
||||
200) ok "%s" "$msg"; ok_urls=$((ok_urls + 1)) ;;
|
||||
[23][0-9][0-9]|40[013]) ok "%s $(_t "reachable")" "$msg"; ok_urls=$((ok_urls + 1)) ;;
|
||||
404) fail "%s (404)" "$msg"; bad_urls=$((bad_urls + 1)) ;;
|
||||
000) fail "%s ($(_t "unreachable"))" "$msg"; bad_urls=$((bad_urls + 1)) ;;
|
||||
*) warn "%s" "$msg"; bad_urls=$((bad_urls + 1)) ;;
|
||||
esac
|
||||
}
|
||||
check_url "$(_t "Site")" "$NITRO_API_URL"
|
||||
check_url "$(_t "FurnitureData")" "$NITRO_GAMEDATA_URL/config/FurnitureData.json"
|
||||
check_url "$(_t "Images")" "${NITRO_IMAGE_LIBRARY_URL}reception/AIRpromo_background_left.png"
|
||||
check_url "$(_t "Icons")" "${NITRO_HOF_FURNITURE_URL}/00011_icon.png"
|
||||
check_url "$(_t "Bundled assets")" "$NITRO_ASSET_URL/figure/Hair_U_leet4.nitro"
|
||||
check_socket "$NITRO_SOCKET_URL"
|
||||
if [ "$ok_urls" -gt 0 ]; then ok "%s $(_t "URL(s) OK")" "$ok_urls"; fi
|
||||
if [ "$bad_urls" -gt 0 ]; then warn "%s $(_t "URL(s) need attention")" "$bad_urls"; fi
|
||||
|
||||
step 5 5 "$(_t "Apply (nginx + redis)")"
|
||||
echo ""
|
||||
ensure_gamedata_redirect
|
||||
ensure_ws_proxy
|
||||
if command -v nginx &>/dev/null && sudo nginx -t >/dev/null 2>&1; then
|
||||
sudo nginx -s reload >/dev/null 2>&1 || sudo systemctl reload nginx >/dev/null 2>&1 || true
|
||||
ok "Nginx reloaded"
|
||||
fi
|
||||
command -v redis-cli &>/dev/null && redis-cli FLUSHALL 2>/dev/null && ok "Redis flushed" || true
|
||||
fix_perms
|
||||
info "$(_t "Done — links fixed. Test the client now.")"
|
||||
release_lock
|
||||
return 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# HELP
|
||||
# =============================================================================
|
||||
@@ -3427,6 +3651,7 @@ show_help() {
|
||||
echo -e " ${C_GREEN}repair${C_RESET} $(_t "Flyaway Repair")"
|
||||
echo -e " ${C_GREEN}setup${C_RESET} $(_t "Auto-setup missing repos")"
|
||||
echo -e " ${C_GREEN}cleanup-nitro${C_RESET} $(_t "Cleanup old Nitro dirs")"
|
||||
echo -e " ${C_GREEN}fixlinks${C_RESET} $(_t "Fix client links (icons/furniture/gamedata)")"
|
||||
echo ""
|
||||
echo -e " ${C_BOLD}$(_t "OPTIONS:")${C_RESET}"
|
||||
echo -e " ${C_CYAN}--branch, -b${C_RESET} $(_t "Branch (auto-detects)")"
|
||||
@@ -3465,6 +3690,7 @@ main() {
|
||||
repair|flyaway) cmd="repair" ;;
|
||||
setup|init) cmd="setup" ;;
|
||||
cleanup-nitro|oldnitro) cmd="cleanup-nitro" ;;
|
||||
fixlinks|fix-links|links) cmd="fixlinks" ;;
|
||||
help|-h|--help) cmd="help" ;;
|
||||
version|-V|--version) echo "v${SCRIPT_VERSION} (build ${SCRIPT_BUILD})"; exit 0 ;;
|
||||
--branch|-b) shift; NITRO_BRANCH="$1" ;;
|
||||
@@ -3486,31 +3712,8 @@ main() {
|
||||
info "Auto-detected branch: %s" "$NITRO_BRANCH"
|
||||
fi
|
||||
|
||||
if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then NITRO_SITE_URL="$APP_URL"; fi
|
||||
# Auto-detect the site domain from the nginx vhost when no explicit URL is
|
||||
# configured (APP_URL / NITRO_SITE_URL / --url). This lets the update script
|
||||
# self-configure the client URLs and the ws.<domain> proxy on a fresh hotel
|
||||
# VPS without manual .env edits.
|
||||
if [ -z "${NITRO_SITE_URL:-}" ]; then
|
||||
for _c in "${NGINX_SITE_CONFS[@]}"; do
|
||||
[ -f "$_c" ] || continue
|
||||
NITRO_SITE_URL=$(grep -oE 'server_name[^;]+;' "$_c" 2>/dev/null | head -1 \
|
||||
| tr -s ' ' '\n' | grep -vE 'server_name|;|127\.0\.0\.1|172\.|10\.|192\.168|localhost|\[?' | head -1)
|
||||
[ -n "$NITRO_SITE_URL" ] && NITRO_SITE_URL="https://$NITRO_SITE_URL" && break
|
||||
done
|
||||
fi
|
||||
case "${NITRO_SITE_URL:-}" in
|
||||
https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;;
|
||||
http://*) NITRO_WS_PROTO="ws://" ; NITRO_DOMAIN="${NITRO_SITE_URL#http://}" ;;
|
||||
*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL:-}" ;;
|
||||
esac
|
||||
NITRO_IMAGE_LIBRARY_URL="${NITRO_IMAGE_LIBRARY_URL:-${NITRO_SITE_URL:-}/gamedata/c_images/}"
|
||||
NITRO_HOF_FURNITURE_URL="${NITRO_HOF_FURNITURE_URL:-${NITRO_SITE_URL:-}/gamedata/icons}"
|
||||
NITRO_API_URL="${NITRO_API_URL:-${NITRO_SITE_URL:-}}"
|
||||
NITRO_SOCKET_URL="${NITRO_SOCKET_URL:-${NITRO_WS_PROTO}ws.${NITRO_DOMAIN}}"
|
||||
NITRO_GAMEDATA_URL="${NITRO_GAMEDATA_URL:-${NITRO_SITE_URL:-}/gamedata}"
|
||||
NITRO_ASSET_URL="${NITRO_ASSET_URL:-${NITRO_SITE_URL:-}/gamedata/bundled}"
|
||||
NITRO_FURNI_ASSET_ICON_URL="${NITRO_FURNI_ASSET_ICON_URL:-${NITRO_SITE_URL:-}/gamedata/icons/%libname%%param%_icon.png}"
|
||||
resolve_site_url
|
||||
compute_nitro_urls
|
||||
|
||||
# Validate computed URLs for common misconfigurations.
|
||||
_nitro_url_warn() {
|
||||
@@ -3554,6 +3757,7 @@ main() {
|
||||
repair) cmd_flyaway_repair ;;
|
||||
setup) cmd_setup ;;
|
||||
cleanup-nitro) cmd_cleanup_old_nitro ;;
|
||||
fixlinks) cmd_fixlinks ;;
|
||||
help) show_help ;;
|
||||
esac
|
||||
|
||||
|
||||
Reference in new issue
Block a user