feat(headroom): activate/uninstall extras + fix interpreter detection

- find interpreter next to headroom binary so extras/version read correctly
- add on/off toggle to activate [code]/[ml] via proxy restart
- add uninstall action + live install log progress + ~1GB confirm modal
This commit is contained in:
decolua
2026-07-10 17:32:45 +07:00
parent dcf1927f22
commit 74d5fedf79
7 changed files with 367 additions and 47 deletions
+18 -2
View File
@@ -1,11 +1,15 @@
import { NextResponse } from "next/server";
import { findPython310, getInstalledHeadroomExtras, HEADROOM_COMPRESSION_EXTRAS } from "@/lib/headroom/detect";
import { installHeadroomExtras } from "@/lib/headroom/process";
import { installHeadroomExtras, uninstallHeadroomExtras, getInstallLogTail } from "@/lib/headroom/process";
export const dynamic = "force-dynamic";
export async function GET() {
export async function GET(req) {
try {
// `?log=1` returns the live install/uninstall log tail for progress polling.
if (new URL(req.url).searchParams.get("log") === "1") {
return NextResponse.json({ log: getInstallLogTail() });
}
const python = findPython310();
const status = getInstalledHeadroomExtras(python);
return NextResponse.json({
@@ -28,3 +32,15 @@ export async function POST(req) {
return NextResponse.json({ error: error.message, code: error.code || null }, { status });
}
}
export async function DELETE(req) {
try {
const body = await req.json().catch(() => ({}));
const requested = Array.isArray(body?.extras) ? body.extras : [];
const result = await uninstallHeadroomExtras(requested);
return NextResponse.json(result);
} catch (error) {
const status = error.code === "NO_PYTHON" || error.code === "INVALID_EXTRAS" ? 400 : 500;
return NextResponse.json({ error: error.message, code: error.code || null }, { status });
}
}
+35
View File
@@ -0,0 +1,35 @@
import { NextResponse } from "next/server";
import { getSettings } from "@/lib/localDb";
import { restartHeadroomProxy } from "@/lib/headroom/process";
import { DEFAULT_HEADROOM_URL, isLoopbackHeadroomUrl } from "@/lib/headroom/detect";
export const dynamic = "force-dynamic";
function parsePortFromUrl(url) {
try {
const u = new URL(url);
const p = parseInt(u.port, 10);
if (p > 0 && p < 65536) return p;
} catch { /* ignore, fall through to default */ }
return null;
}
export async function POST() {
try {
const settings = await getSettings();
const url = settings.headroomUrl || DEFAULT_HEADROOM_URL;
if (!isLoopbackHeadroomUrl(url)) {
return NextResponse.json({ error: "External Headroom proxies must be started outside 9Router", code: "EXTERNAL_PROXY" }, { status: 400 });
}
const port = parsePortFromUrl(url) || 8787;
const result = await restartHeadroomProxy({
port,
codeAware: settings.headroomCodeAware === true,
kompress: settings.headroomKompress !== false,
});
return NextResponse.json({ success: true, ...result });
} catch (error) {
const status = error.code === "NOT_INSTALLED" ? 400 : 500;
return NextResponse.json({ error: error.message, code: error.code || null }, { status });
}
}
+5 -1
View File
@@ -22,7 +22,11 @@ export async function POST() {
return NextResponse.json({ error: "External Headroom proxies must be started outside 9Router", code: "EXTERNAL_PROXY" }, { status: 400 });
}
const port = parsePortFromUrl(url) || 8787;
const result = await startHeadroomProxy({ port });
const result = await startHeadroomProxy({
port,
codeAware: settings.headroomCodeAware === true,
kompress: settings.headroomKompress !== false,
});
return NextResponse.json({ success: true, ...result });
} catch (error) {
const status = error.code === "NOT_INSTALLED" ? 400 : 500;