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 Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
UserReferrals,
|
UserReferrals,
|
||||||
UsersBadges,
|
UsersBadges,
|
||||||
UsersSettings,
|
UsersSettings,
|
||||||
WebsiteLoginLogs,
|
|
||||||
} from "@/lib/db";
|
} from "@/lib/db";
|
||||||
import { avatarImageUrl } from "@/lib/format";
|
import { avatarImageUrl } from "@/lib/format";
|
||||||
import { resolveHotelName } from "@/lib/hotel-name";
|
import { resolveHotelName } from "@/lib/hotel-name";
|
||||||
@@ -58,10 +57,7 @@ const ERROR_MESSAGES: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getErrorMessage(error: string): string {
|
function getErrorMessage(error: string): string {
|
||||||
if (error === "not_enough") return ERROR_MESSAGES.not_enough;
|
return ERROR_MESSAGES[error] ?? ERROR_MESSAGES.error;
|
||||||
if (error === "no_referrals") return ERROR_MESSAGES.no_referrals;
|
|
||||||
if (error === "bad_config") return ERROR_MESSAGES.bad_config;
|
|
||||||
return ERROR_MESSAGES.error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function MePage({
|
export default async function MePage({
|
||||||
@@ -90,10 +86,11 @@ export default async function MePage({
|
|||||||
alertRaw,
|
alertRaw,
|
||||||
recentRooms,
|
recentRooms,
|
||||||
badges,
|
badges,
|
||||||
lastWebLoginRows,
|
|
||||||
userSettingsRows,
|
userSettingsRows,
|
||||||
friendCountRows,
|
friendCountRows,
|
||||||
unreadCountRows,
|
unreadCountRows,
|
||||||
|
referralsRows,
|
||||||
|
friends,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
db
|
db
|
||||||
.select({
|
.select({
|
||||||
@@ -139,13 +136,6 @@ export default async function MePage({
|
|||||||
.where(and(eq(UsersBadges.userId, userId), gt(UsersBadges.slotId, 0)))
|
.where(and(eq(UsersBadges.userId, userId), gt(UsersBadges.slotId, 0)))
|
||||||
.orderBy(asc(UsersBadges.slotId))
|
.orderBy(asc(UsersBadges.slotId))
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
db
|
|
||||||
.select({ createdAt: WebsiteLoginLogs.createdAt })
|
|
||||||
.from(WebsiteLoginLogs)
|
|
||||||
.where(eq(WebsiteLoginLogs.userId, userId))
|
|
||||||
.orderBy(desc(WebsiteLoginLogs.id))
|
|
||||||
.limit(1)
|
|
||||||
.catch(() => []),
|
|
||||||
db
|
db
|
||||||
.select({
|
.select({
|
||||||
achievementScore: UsersSettings.achievementScore,
|
achievementScore: UsersSettings.achievementScore,
|
||||||
@@ -170,18 +160,51 @@ export default async function MePage({
|
|||||||
.from(MessengerOffline)
|
.from(MessengerOffline)
|
||||||
.where(eq(MessengerOffline.userId, userId))
|
.where(eq(MessengerOffline.userId, userId))
|
||||||
.catch(() => [{ value: 0 }]),
|
.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 user = userRows[0] ?? null;
|
||||||
const lastWebLogin = lastWebLoginRows[0] ?? null;
|
if (!user) throw new Error("user-not-found");
|
||||||
|
|
||||||
const userSettings = userSettingsRows[0] ?? null;
|
const userSettings = userSettingsRows[0] ?? null;
|
||||||
const friendCount = Number(friendCountRows[0]?.value ?? 0);
|
const friendCount = Number(friendCountRows[0]?.value ?? 0);
|
||||||
const unreadCount = Number(unreadCountRows[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 needed = Number.parseInt(neededRaw ?? "5", 10) || 5;
|
||||||
const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0;
|
const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0;
|
||||||
const rewardCurrency = (
|
const rewardCurrency = (
|
||||||
@@ -192,53 +215,12 @@ export default async function MePage({
|
|||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
|
||||||
const [referrals] = await db
|
const referralTotal = referralsRows[0]
|
||||||
.select({ referralsTotal: UserReferrals.referralsTotal })
|
? Number(referralsRows[0].referralsTotal)
|
||||||
.from(UserReferrals)
|
: 0;
|
||||||
.where(eq(UserReferrals.userId, userId))
|
|
||||||
.orderBy(desc(UserReferrals.id))
|
|
||||||
.limit(1)
|
|
||||||
.catch(() => []);
|
|
||||||
const referralTotal = referrals ? Number(referrals.referralsTotal) : 0;
|
|
||||||
const canClaim = referralTotal >= needed;
|
const canClaim = referralTotal >= needed;
|
||||||
const remaining = Math.max(0, needed - referralTotal);
|
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 onlineFriends = friends.filter((f) => f.online === "1");
|
||||||
|
|
||||||
const registered = new Date(user.accountCreated * 1000)
|
const registered = new Date(user.accountCreated * 1000)
|
||||||
@@ -255,6 +237,7 @@ export default async function MePage({
|
|||||||
const avatarFull = avatarImageUrl(user.look, {
|
const avatarFull = avatarImageUrl(user.look, {
|
||||||
direction: 2,
|
direction: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<div className="flex flex-col gap-8 pb-8">
|
<div className="flex flex-col gap-8 pb-8">
|
||||||
{claimed ? (
|
{claimed ? (
|
||||||
@@ -298,6 +281,7 @@ export default async function MePage({
|
|||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
href="/client"
|
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"
|
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={{
|
style={{
|
||||||
background: "var(--color-primary)",
|
background: "var(--color-primary)",
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ function ToolbarBtn({
|
|||||||
} as const;
|
} as const;
|
||||||
if (href) {
|
if (href) {
|
||||||
return (
|
return (
|
||||||
<Link href={href} className={cls} style={style} title={title}>
|
<Link
|
||||||
|
href={href}
|
||||||
|
prefetch={false}
|
||||||
|
className={cls}
|
||||||
|
style={style}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -377,9 +383,12 @@ export function ClientView({
|
|||||||
<iframe
|
<iframe
|
||||||
id="nitro"
|
id="nitro"
|
||||||
src={clientSrc}
|
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}
|
title={hotelName}
|
||||||
allow="autoplay; gamepad"
|
allow="autoplay; gamepad; fullscreen"
|
||||||
|
loading="eager"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+438
-48
@@ -1143,10 +1143,17 @@ backup_configs() {
|
|||||||
|
|
||||||
validate_json() {
|
validate_json() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
if ! python3 -m json.tool "$file" >/dev/null 2>&1; then
|
# Strict JSON first...
|
||||||
return 1
|
if python3 -m json.tool "$file" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
fi
|
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() {
|
validate_configs() {
|
||||||
@@ -1434,6 +1441,7 @@ cmd_cleanup_old_nitro() {
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
auto_setup_missing_repos() {
|
auto_setup_missing_repos() {
|
||||||
local cloned=false
|
local cloned=false
|
||||||
|
local needs_link=false
|
||||||
|
|
||||||
if [ ! -d "$NITRO_CLIENT" ] || [ ! -d "$NITRO_CLIENT/.git" ]; then
|
if [ ! -d "$NITRO_CLIENT" ] || [ ! -d "$NITRO_CLIENT/.git" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
@@ -1443,7 +1451,7 @@ auto_setup_missing_repos() {
|
|||||||
if git clone --depth 1 "$GIT_REPO_CLIENT" "$NITRO_CLIENT" >> "$LOG_FILE" 2>&1; then
|
if git clone --depth 1 "$GIT_REPO_CLIENT" "$NITRO_CLIENT" >> "$LOG_FILE" 2>&1; then
|
||||||
spinner_stop ok
|
spinner_stop ok
|
||||||
ok "Octane client cloned successfully"
|
ok "Octane client cloned successfully"
|
||||||
cloned=true
|
cloned=true; needs_link=true
|
||||||
else
|
else
|
||||||
spinner_stop fail
|
spinner_stop fail
|
||||||
die "Failed to clone Octane client from %s" "$GIT_REPO_CLIENT"
|
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
|
if git clone --depth 1 "$GIT_REPO_RENDERER" "$NITRO_RENDERER" >> "$LOG_FILE" 2>&1; then
|
||||||
spinner_stop ok
|
spinner_stop ok
|
||||||
ok "Octane renderer cloned successfully"
|
ok "Octane renderer cloned successfully"
|
||||||
cloned=true
|
cloned=true; needs_link=true
|
||||||
else
|
else
|
||||||
spinner_stop fail
|
spinner_stop fail
|
||||||
die "Failed to clone Octane renderer from %s" "$GIT_REPO_RENDERER"
|
die "Failed to clone Octane renderer from %s" "$GIT_REPO_RENDERER"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$cloned" = true ]; then
|
# Also detect when either repo exists but was never linked
|
||||||
echo ""
|
if [ "$needs_link" = false ]; then
|
||||||
info "Installing dependencies for cloned repos..."
|
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
|
if [ -d "$NITRO_RENDERER" ] && [ -f "$NITRO_RENDERER/package.json" ]; then
|
||||||
spinner_start "Installing renderer deps..."
|
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"
|
yarn_install_repo "$NITRO_RENDERER" >> "$LOG_FILE" 2>&1 || warn "Renderer yarn install failed"
|
||||||
spinner_stop ok
|
spinner_stop ok
|
||||||
ok "Renderer dependencies installed"
|
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
|
fi
|
||||||
|
|
||||||
|
# Step 3: Install client deps
|
||||||
if [ -d "$NITRO_CLIENT" ] && [ -f "$NITRO_CLIENT/package.json" ]; then
|
if [ -d "$NITRO_CLIENT" ] && [ -f "$NITRO_CLIENT/package.json" ]; then
|
||||||
spinner_start "Installing client deps..."
|
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"
|
yarn_install_repo "$NITRO_CLIENT" >> "$LOG_FILE" 2>&1 || warn "Client yarn install failed"
|
||||||
spinner_stop ok
|
spinner_stop ok
|
||||||
ok "Client dependencies installed"
|
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..."
|
spinner_start "Building client frontend..."
|
||||||
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
||||||
spinner_stop ok
|
spinner_stop ok
|
||||||
@@ -1505,6 +1556,60 @@ auto_setup_missing_repos() {
|
|||||||
fi
|
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
|
# 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"; }
|
yarn_install_repo "$NITRO_RENDERER" || { spinner_stop fail; git_revert_repo "$NITRO_RENDERER"; die "yarn install failed (see: %s)" "$LOG_FILE"; }
|
||||||
fi
|
fi
|
||||||
spinner_stop ok; ok "Renderer dependencies installed"
|
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
|
else
|
||||||
info "Renderer: already up to date"
|
info "Renderer: already up to date"
|
||||||
fi
|
fi
|
||||||
@@ -1670,6 +1779,10 @@ update_client() {
|
|||||||
cd "$NITRO_CLIENT"
|
cd "$NITRO_CLIENT"
|
||||||
fi
|
fi
|
||||||
spinner_stop ok
|
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..."
|
spinner_start "Building frontend..."
|
||||||
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
if yarn_run "$NITRO_CLIENT" build >> "$LOG_FILE" 2>&1; then
|
||||||
spinner_stop ok
|
spinner_stop ok
|
||||||
@@ -1702,6 +1815,7 @@ sync_configs() {
|
|||||||
[ -d "$d" ] && [ ! -w "$d" ] && sudo chown -R "$(whoami)":"$(whoami)" "$d" 2>/dev/null || true
|
[ -d "$d" ] && [ ! -w "$d" ] && sudo chown -R "$(whoami)":"$(whoami)" "$d" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
local ec=0
|
local ec=0
|
||||||
|
# Standard *.example files (ui-config, client-mode, etc.)
|
||||||
for ef in "$NITRO_SRC_DIR"/*.example; do
|
for ef in "$NITRO_SRC_DIR"/*.example; do
|
||||||
[ -f "$ef" ] || continue
|
[ -f "$ef" ] || continue
|
||||||
local b=$(basename "$ef"); local tn="${b%.example}"
|
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
|
[ -d "$dcd" ] && cp "$NITRO_SRC_DIR/$tn" "$dcd/$tn" 2>/dev/null || true
|
||||||
ec=$((ec+1))
|
ec=$((ec+1))
|
||||||
done
|
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"
|
ok "%s config file(s) synced" "$ec"
|
||||||
|
|
||||||
# Validate the generated configs
|
# Validate the generated configs
|
||||||
@@ -1736,42 +1868,16 @@ sync_configs() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
info "Applying critical config URLs..."
|
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_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_API_URL="$NITRO_API_URL" NITRO_SOCKET_URL="$NITRO_SOCKET_URL" \
|
||||||
NITRO_GAMEDATA_URL="$NITRO_GAMEDATA_URL" NITRO_ASSET_URL="$NITRO_ASSET_URL" \
|
NITRO_GAMEDATA_URL="$NITRO_GAMEDATA_URL" NITRO_ASSET_URL="$NITRO_ASSET_URL" \
|
||||||
NITRO_FURNI_ASSET_ICON_URL="$NITRO_FURNI_ASSET_ICON_URL" \
|
NITRO_FURNI_ASSET_ICON_URL="$NITRO_FURNI_ASSET_ICON_URL" \
|
||||||
python3 -c '
|
node "$SCRIPT_DIR/scripts/sync-nitro-urls.cjs" 2>/dev/null && ok "All config URLs synced" || warn "Config URL sync failed"
|
||||||
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"
|
|
||||||
|
|
||||||
# Flush Redis so new config is picked up immediately
|
# Flush Redis so new config is picked up immediately
|
||||||
if command -v redis-cli &>/dev/null; then
|
if command -v redis-cli &>/dev/null; then
|
||||||
@@ -1886,6 +1992,7 @@ ensure_gamedata_redirect() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
tmp=$(mktemp)
|
tmp=$(mktemp)
|
||||||
|
mkdir -p /var/backups/nginx-cms 2>/dev/null || true
|
||||||
snippet=" # Redirect /gamedata/ to /gamedata/config/
|
snippet=" # Redirect /gamedata/ to /gamedata/config/
|
||||||
location = /gamedata { return 301 /gamedata/config/; }
|
location = /gamedata { return 301 /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"
|
warn "Could not inject gamedata redirect into %s" "$conf"
|
||||||
continue
|
continue
|
||||||
fi
|
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; }
|
$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
|
if ! $root_cmd install -m 640 "$tmp" "$conf" 2>/dev/null; then
|
||||||
rm -f "$tmp" 2>/dev/null || true
|
rm -f "$tmp" 2>/dev/null || true
|
||||||
@@ -1921,9 +2028,255 @@ ensure_gamedata_redirect() {
|
|||||||
return 0
|
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() {
|
do_restart() {
|
||||||
step 8 8 "Restart Services"
|
step 8 8 "Restart Services"
|
||||||
ensure_gamedata_redirect
|
ensure_gamedata_redirect
|
||||||
|
ensure_default_server
|
||||||
|
ensure_ws_proxy
|
||||||
|
ensure_api_proxy
|
||||||
if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then
|
if systemctl cat "$EMULATOR_SERVICE" &>/dev/null; then
|
||||||
sudo systemctl restart "$EMULATOR_SERVICE" 2>/dev/null && ok "$EMULATOR_SERVICE restarted" || warn "Restart failed"
|
sudo systemctl restart "$EMULATOR_SERVICE" 2>/dev/null && ok "$EMULATOR_SERVICE restarted" || warn "Restart failed"
|
||||||
if ! wait_for_emulator_health; then
|
if ! wait_for_emulator_health; then
|
||||||
@@ -1959,7 +2312,15 @@ do_restart() {
|
|||||||
else
|
else
|
||||||
warn "No service manager found"
|
warn "No service manager found"
|
||||||
fi
|
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"
|
command -v redis-cli &>/dev/null && redis-cli FLUSHALL 2>/dev/null && ok "Redis flushed"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1968,10 +2329,7 @@ do_restart() {
|
|||||||
emulator_port_open() {
|
emulator_port_open() {
|
||||||
local port="${NITRO_EMULATOR_PORT:-}"
|
local port="${NITRO_EMULATOR_PORT:-}"
|
||||||
if [ -z "$port" ]; then
|
if [ -z "$port" ]; then
|
||||||
port=$(grep -m1 '^game\.port=' "$EMULATOR_MODULE/target/config.ini" 2>/dev/null | cut -d= -f2)
|
port=$(emulator_game_port)
|
||||||
fi
|
|
||||||
if [ -z "$port" ]; then
|
|
||||||
port=30000
|
|
||||||
fi
|
fi
|
||||||
if command -v redis-cli >/dev/null 2>&1; then
|
if command -v redis-cli >/dev/null 2>&1; then
|
||||||
local p; p=$(redis-cli GET nitro:emulator:port 2>/dev/null)
|
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"
|
yarn_install_repo "$repo" --frozen-lockfile --prefer-offline 2>/dev/null || warn "yarn install still failed for %s" "$name"
|
||||||
fi
|
fi
|
||||||
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))
|
repaired=$((repaired + 1))
|
||||||
done
|
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
|
# Realign Flyway schema history with the packaged migrations
|
||||||
step 3 7 "$(_t "Flyway DB Repair")"
|
step 3 7 "$(_t "Flyway DB Repair")"
|
||||||
if flyaway_repair_db; then
|
if flyaway_repair_db; then
|
||||||
@@ -2826,6 +3204,18 @@ main() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${NITRO_SITE_URL:-}" ] && [ -n "${APP_URL:-}" ]; then NITRO_SITE_URL="$APP_URL"; 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
|
case "${NITRO_SITE_URL:-}" in
|
||||||
https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;;
|
https://*) NITRO_WS_PROTO="wss://" ; NITRO_DOMAIN="${NITRO_SITE_URL#https://}" ;;
|
||||||
http://*) NITRO_WS_PROTO="ws://" ; NITRO_DOMAIN="${NITRO_SITE_URL#http://}" ;;
|
http://*) NITRO_WS_PROTO="ws://" ; NITRO_DOMAIN="${NITRO_SITE_URL#http://}" ;;
|
||||||
|
|||||||
Reference in new issue
Block a user