diff --git a/docs/code-bridge/requests/REQ-2026-04-16-modpack-installer-tweaks.md b/docs/code-bridge/archive/REQ-2026-04-16-modpack-installer-tweaks.md similarity index 100% rename from docs/code-bridge/requests/REQ-2026-04-16-modpack-installer-tweaks.md rename to docs/code-bridge/archive/REQ-2026-04-16-modpack-installer-tweaks.md diff --git a/services/arbiter-3.0/src/routes/admin/modpack-installer.js b/services/arbiter-3.0/src/routes/admin/modpack-installer.js index cc0959e..ecae7d5 100644 --- a/services/arbiter-3.0/src/routes/admin/modpack-installer.js +++ b/services/arbiter-3.0/src/routes/admin/modpack-installer.js @@ -14,10 +14,91 @@ const express = require('express'); const router = express.Router(); +const axios = require('axios'); const db = require('../../database'); const { getProvider, listProviders } = require('../../services/providerApi'); const { estimateRam, slugify } = require('../../services/modpackInstaller'); +const PANEL_URL = process.env.PANEL_URL || 'https://panel.firefrostgaming.com'; +const PANEL_ADMIN_KEY = process.env.PANEL_ADMIN_KEY || ''; + +// Aikar G1GC flags template — {RAM} replaced with allocation in MB +const AIKAR_FLAGS = '-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 ' + + '-XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch ' + + '-XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M ' + + '-XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 ' + + '-XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 ' + + '-XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem ' + + '-XX:MaxTenuringThreshold=1 -Dusing.aikars.flags=https://mcflags.emc.gs ' + + '-Daikars.new.flags=true'; + +function javaVersionForMC(mcVersion) { + if (!mcVersion) return 21; + const parts = mcVersion.split('.').map(Number); + const minor = parts[1] || 0; + if (minor <= 16) return 8; + if (minor <= 20) return 17; + return 21; +} + +// ─── Node resource usage (JSON for form) ──────────────────────────────────── +router.get('/node-info', async (req, res) => { + try { + const panelHeaders = { + 'Authorization': `Bearer ${PANEL_ADMIN_KEY}`, + 'Accept': 'application/json' + }; + const nodesResp = await axios.get(`${PANEL_URL}/api/application/nodes`, { + headers: panelHeaders, timeout: 10000 + }); + const nodes = (nodesResp.data.data || []).map(n => { + const a = n.attributes; + return { + id: a.id, name: a.name, location_id: a.location_id, + memory: a.memory, memory_overallocate: a.memory_overallocate, + disk: a.disk, disk_overallocate: a.disk_overallocate, + allocated_resources: a.allocated_resources || {} + }; + }); + // Map to our node IDs + const result = {}; + for (const n of nodes) { + const key = n.name.includes('TX') ? 'TX1' : n.name.includes('NC') ? 'NC1' : n.name; + const usedRam = n.allocated_resources.memory || 0; + const usedDisk = n.allocated_resources.disk || 0; + result[key] = { + label: key === 'TX1' ? '🔥 TX1 — Dallas' : '❄️ NC1 — Charlotte', + ramUsedMb: usedRam, ramTotalMb: n.memory, + diskUsedMb: usedDisk, diskTotalMb: n.disk + }; + } + res.json(result); + } catch (err) { + console.error('[Installer] node-info error:', err.message); + res.json({ + NC1: { label: '❄️ NC1 — Charlotte', ramUsedMb: 0, ramTotalMb: 65536, diskUsedMb: 0, diskTotalMb: 2048000 }, + TX1: { label: '🔥 TX1 — Dallas', ramUsedMb: 0, ramTotalMb: 65536, diskUsedMb: 0, diskTotalMb: 2048000 } + }); + } +}); + +// ─── Next available port on a node ────────────────────────────────────────── +router.get('/next-port', async (req, res) => { + try { + const node = req.query.node || 'NC1'; + // Query server_config for all servers on this node, find max port + const result = await db.query( + `SELECT COALESCE(MAX(server_port), 25564) AS max_port FROM server_config WHERE node = $1`, + [node] + ); + const nextPort = result.rows[0].max_port + 1; + res.json({ port: nextPort, node }); + } catch (err) { + console.error('[Installer] next-port error:', err.message); + res.json({ port: 25565, node: req.query.node || 'NC1' }); + } +}); + // ─── Main page ────────────────────────────────────────────────────────────── router.get('/', (req, res) => { res.render('admin/modpack-installer/index', { @@ -69,6 +150,8 @@ router.get('/pack/:provider/:id', async (req, res) => { provider: req.params.provider, estimateRam, slugify, + javaVersionForMC, + aikarFlags: AIKAR_FLAGS, csrfToken: req.csrfToken(), layout: false }); diff --git a/services/arbiter-3.0/src/views/admin/modpack-installer/_pack_details.ejs b/services/arbiter-3.0/src/views/admin/modpack-installer/_pack_details.ejs index dd28cc1..a9f2661 100644 --- a/services/arbiter-3.0/src/views/admin/modpack-installer/_pack_details.ejs +++ b/services/arbiter-3.0/src/views/admin/modpack-installer/_pack_details.ejs @@ -1,8 +1,19 @@ + + +<% + // Derive first MC version for Java auto-select + var firstVersion = versions.length > 0 ? (versions[0].gameVersions || [])[0] || '' : ''; + var autoJava = javaVersionForMC(firstVersion); + // Estimate pack size from first version file + var firstFile = versions.length > 0 ? (versions[0].files ? versions[0].files[0] : versions[0]) : {}; + var estSizeMb = firstFile.fileLength ? Math.round(firstFile.fileLength / 1024 / 1024) : (firstFile.size ? Math.round(firstFile.size / 1024 / 1024) : 0); +%>
<%= (pack.summary || '').substring(0, 200) %>
- <% if (pack.categories && pack.categories.length > 0) { %> -