perf: optimize dashboard database queries and remove unused imports
This commit is contained in:
1 parent
3a76dbdb97
commit
416c31643b
4 files changed
+593
-114
No files matched your search
@@ -0,0 +1,96 @@
|
||||
// Syncs the Nitro/Octane runtime config URLs from environment variables into the
|
||||
// renderer-config.json / renderer-config.jsonc / ui-config.json files.
|
||||
//
|
||||
// Uses jsonc-parser so JSONC (with // and /* */ comments) is handled safely —
|
||||
// unlike a naive JSON.parse/json.load this never corrupts URLs that contain
|
||||
// "https://". After this runs the files are written back as strict JSON (which
|
||||
// is still valid JSONC), so downstream validation passes.
|
||||
//
|
||||
// Env:
|
||||
// NITRO_SRC_DIR e.g. /var/www/Octane/public/configuration
|
||||
// NITRO_DIST_CONFIG_DIR e.g. /var/www/Octane/dist/configuration
|
||||
// NITRO_GAMEDATA_CONF_DIR e.g. /var/www/Gamedata/config
|
||||
// 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
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { parse, modify } = require("jsonc-parser");
|
||||
|
||||
const FILES = ["renderer-config.json", "renderer-config.jsonc", "ui-config.json"];
|
||||
|
||||
function env(name) {
|
||||
return process.env[name] && process.env[name].length ? process.env[name] : null;
|
||||
}
|
||||
|
||||
function applyOverrides(data) {
|
||||
const image = env("NITRO_IMAGE_LIBRARY_URL");
|
||||
const hof = env("NITRO_HOF_FURNITURE_URL");
|
||||
const api = env("NITRO_API_URL");
|
||||
const socket = env("NITRO_SOCKET_URL");
|
||||
const gamedata = env("NITRO_GAMEDATA_URL");
|
||||
const asset = env("NITRO_ASSET_URL");
|
||||
const icon = env("NITRO_FURNI_ASSET_ICON_URL");
|
||||
|
||||
const set = (key, value) => {
|
||||
if (value && !(key in data)) data[key] = value;
|
||||
if (value) data[key] = value;
|
||||
};
|
||||
|
||||
set("image.library.url", image);
|
||||
set("hof.furni.url", hof);
|
||||
set("api.url", api);
|
||||
set("socket.url", socket);
|
||||
set("gamedata.url", gamedata);
|
||||
set("asset.url", asset);
|
||||
set("furni.asset.icon.url", icon);
|
||||
|
||||
if (gamedata) {
|
||||
data["radio.url"] = `${gamedata}/config/radio-stations.jsonc?t=%timestamp%`;
|
||||
data["soundboard.url"] = `${gamedata}/config/soundboard-sounds.jsonc?t=%timestamp%`;
|
||||
}
|
||||
if ("show.google.ads" in data) data["show.google.ads"] = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
const dirs = [
|
||||
env("NITRO_SRC_DIR"),
|
||||
env("NITRO_DIST_CONFIG_DIR"),
|
||||
env("NITRO_GAMEDATA_CONF_DIR"),
|
||||
].filter(Boolean);
|
||||
|
||||
let changed = 0;
|
||||
for (const dir of dirs) {
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
for (const file of FILES) {
|
||||
const full = path.join(dir, file);
|
||||
if (!fs.existsSync(full)) continue;
|
||||
let content;
|
||||
try {
|
||||
content = fs.readFileSync(full, "utf-8");
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
data = parse(content, [], { allowTrailingComma: true, allowEmptyContent: true });
|
||||
} catch (e) {
|
||||
console.error(`[sync-nitro-urls] parse error in ${full}: ${e}`);
|
||||
continue;
|
||||
}
|
||||
if (!data || typeof data !== "object") continue;
|
||||
const before = JSON.stringify(data);
|
||||
const updated = applyOverrides(data);
|
||||
if (JSON.stringify(updated) === before) continue;
|
||||
try {
|
||||
fs.writeFileSync(full, `${JSON.stringify(updated, null, 4)}\n`);
|
||||
changed++;
|
||||
console.log(` [OK] Synced URLs: ${file} (${dir})`);
|
||||
} catch (e) {
|
||||
console.error(`[sync-nitro-urls] write error ${full}: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` [OK] URL sync complete (${changed} file(s) updated)`);
|
||||
process.exit(0);
|
||||
+47
-63
@@ -1,4 +1,4 @@
|
||||
import { and, asc, count, desc, eq, gt, inArray, or } from "drizzle-orm";
|
||||
import { and, asc, count, desc, eq, gt, or } from "drizzle-orm";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
UserReferrals,
|
||||
UsersBadges,
|
||||
UsersSettings,
|
||||
WebsiteLoginLogs,
|
||||
} from "@/lib/db";
|
||||
import { avatarImageUrl } from "@/lib/format";
|
||||
import { resolveHotelName } from "@/lib/hotel-name";
|
||||
@@ -58,10 +57,7 @@ const ERROR_MESSAGES: Record<string, string> = {
|
||||
};
|
||||
|
||||
function getErrorMessage(error: string): string {
|
||||
if (error === "not_enough") return ERROR_MESSAGES.not_enough;
|
||||
if (error === "no_referrals") return ERROR_MESSAGES.no_referrals;
|
||||
if (error === "bad_config") return ERROR_MESSAGES.bad_config;
|
||||
return ERROR_MESSAGES.error;
|
||||
return ERROR_MESSAGES[error] ?? ERROR_MESSAGES.error;
|
||||
}
|
||||
|
||||
export default async function MePage({
|
||||
@@ -90,10 +86,11 @@ export default async function MePage({
|
||||
alertRaw,
|
||||
recentRooms,
|
||||
badges,
|
||||
lastWebLoginRows,
|
||||
userSettingsRows,
|
||||
friendCountRows,
|
||||
unreadCountRows,
|
||||
referralsRows,
|
||||
friends,
|
||||
] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
@@ -139,13 +136,6 @@ export default async function MePage({
|
||||
.where(and(eq(UsersBadges.userId, userId), gt(UsersBadges.slotId, 0)))
|
||||
.orderBy(asc(UsersBadges.slotId))
|
||||
.catch(() => []),
|
||||
db
|
||||
.select({ createdAt: WebsiteLoginLogs.createdAt })
|
||||
.from(WebsiteLoginLogs)
|
||||
.where(eq(WebsiteLoginLogs.userId, userId))
|
||||
.orderBy(desc(WebsiteLoginLogs.id))
|
||||
.limit(1)
|
||||
.catch(() => []),
|
||||
db
|
||||
.select({
|
||||
achievementScore: UsersSettings.achievementScore,
|
||||
@@ -170,18 +160,51 @@ export default async function MePage({
|
||||
.from(MessengerOffline)
|
||||
.where(eq(MessengerOffline.userId, userId))
|
||||
.catch(() => [{ value: 0 }]),
|
||||
db
|
||||
.select({ referralsTotal: UserReferrals.referralsTotal })
|
||||
.from(UserReferrals)
|
||||
.where(eq(UserReferrals.userId, userId))
|
||||
.orderBy(desc(UserReferrals.id))
|
||||
.limit(1)
|
||||
.catch(() => []),
|
||||
db
|
||||
.select({
|
||||
id: User.id,
|
||||
username: User.username,
|
||||
look: User.look,
|
||||
motto: User.motto,
|
||||
online: User.online,
|
||||
})
|
||||
.from(MessengerFriendships)
|
||||
.innerJoin(
|
||||
User,
|
||||
or(
|
||||
and(
|
||||
eq(MessengerFriendships.userOneId, userId),
|
||||
eq(User.id, MessengerFriendships.userTwoId),
|
||||
),
|
||||
and(
|
||||
eq(MessengerFriendships.userTwoId, userId),
|
||||
eq(User.id, MessengerFriendships.userOneId),
|
||||
),
|
||||
),
|
||||
)
|
||||
.where(
|
||||
or(
|
||||
eq(MessengerFriendships.userOneId, userId),
|
||||
eq(MessengerFriendships.userTwoId, userId),
|
||||
),
|
||||
)
|
||||
.catch(() => []),
|
||||
]);
|
||||
|
||||
const user = userRows[0] ?? null;
|
||||
const lastWebLogin = lastWebLoginRows[0] ?? null;
|
||||
if (!user) throw new Error("user-not-found");
|
||||
|
||||
const userSettings = userSettingsRows[0] ?? null;
|
||||
const friendCount = Number(friendCountRows[0]?.value ?? 0);
|
||||
const unreadCount = Number(unreadCountRows[0]?.value ?? 0);
|
||||
|
||||
void lastWebLogin;
|
||||
|
||||
if (!user) throw new Error("user-not-found");
|
||||
|
||||
const needed = Number.parseInt(neededRaw ?? "5", 10) || 5;
|
||||
const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0;
|
||||
const rewardCurrency = (
|
||||
@@ -192,53 +215,12 @@ export default async function MePage({
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
const [referrals] = await db
|
||||
.select({ referralsTotal: UserReferrals.referralsTotal })
|
||||
.from(UserReferrals)
|
||||
.where(eq(UserReferrals.userId, userId))
|
||||
.orderBy(desc(UserReferrals.id))
|
||||
.limit(1)
|
||||
.catch(() => []);
|
||||
const referralTotal = referrals ? Number(referrals.referralsTotal) : 0;
|
||||
const referralTotal = referralsRows[0]
|
||||
? Number(referralsRows[0].referralsTotal)
|
||||
: 0;
|
||||
const canClaim = referralTotal >= needed;
|
||||
const remaining = Math.max(0, needed - referralTotal);
|
||||
|
||||
const friendships = await db
|
||||
.select({
|
||||
userOneId: MessengerFriendships.userOneId,
|
||||
userTwoId: MessengerFriendships.userTwoId,
|
||||
})
|
||||
.from(MessengerFriendships)
|
||||
.where(
|
||||
or(
|
||||
eq(MessengerFriendships.userOneId, userId),
|
||||
eq(MessengerFriendships.userTwoId, userId),
|
||||
),
|
||||
)
|
||||
.catch(() => []);
|
||||
|
||||
const friendIds = Array.from(
|
||||
new Set(
|
||||
friendships
|
||||
.map((f) => (f.userOneId === userId ? f.userTwoId : f.userOneId))
|
||||
.filter((id) => id && id !== userId),
|
||||
),
|
||||
);
|
||||
|
||||
const friends = friendIds.length
|
||||
? await db
|
||||
.select({
|
||||
id: User.id,
|
||||
username: User.username,
|
||||
look: User.look,
|
||||
motto: User.motto,
|
||||
online: User.online,
|
||||
})
|
||||
.from(User)
|
||||
.where(inArray(User.id, friendIds))
|
||||
.catch(() => [])
|
||||
: [];
|
||||
|
||||
const onlineFriends = friends.filter((f) => f.online === "1");
|
||||
|
||||
const registered = new Date(user.accountCreated * 1000)
|
||||
@@ -255,6 +237,7 @@ export default async function MePage({
|
||||
const avatarFull = avatarImageUrl(user.look, {
|
||||
direction: 2,
|
||||
});
|
||||
|
||||
content = (
|
||||
<div className="flex flex-col gap-8 pb-8">
|
||||
{claimed ? (
|
||||
@@ -298,6 +281,7 @@ export default async function MePage({
|
||||
/>
|
||||
<Link
|
||||
href="/client"
|
||||
prefetch={false}
|
||||
className="btn-shine flex items-center justify-center gap-1.5 rounded-xl border py-2 px-4 font-extrabold text-xs transition-all duration-200 hover:scale-[1.02] active:scale-95 shadow-lg"
|
||||
style={{
|
||||
background: "var(--color-primary)",
|
||||
|
||||
@@ -34,7 +34,13 @@ function ToolbarBtn({
|
||||
} as const;
|
||||
if (href) {
|
||||
return (
|
||||
<Link href={href} className={cls} style={style} title={title}>
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={false}
|
||||
className={cls}
|
||||
style={style}
|
||||
title={title}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
@@ -377,9 +383,12 @@ export function ClientView({
|
||||
<iframe
|
||||
id="nitro"
|
||||
src={clientSrc}
|
||||
className="absolute inset-0 w-full h-full border-0 m-0 p-0 overflow-hidden"
|
||||
className={`absolute inset-0 w-full h-full border-0 m-0 p-0 overflow-hidden ${
|
||||
dragging ? "pointer-events-none" : "pointer-events-auto"
|
||||
}`}
|
||||
title={hotelName}
|
||||
allow="autoplay; gamepad"
|
||||
allow="autoplay; gamepad; fullscreen"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+438
-48
@@ -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://}" ;;
|
||||
|
||||
Reference in new issue
Block a user