feat(30-01): hardware and nexus-settings routes, app.ts mounting

- Add hardwareRoutes with unauthenticated GET /system/providers
- Add hardwareRoutes with GET /system/providers/recommendation
- Add nexusSettingsRoutes with board-auth GET/PATCH /nexus/settings
- Mount hardwareRoutes on app before boardMutationGuard (unauthenticated)
- Mount nexusSettingsRoutes on api router (board-auth gated)
This commit is contained in:
Nexus Dev 2026-04-02 23:22:20 +00:00
parent 766460a163
commit 86e30e5c69
3 changed files with 109 additions and 0 deletions

View file

@ -27,6 +27,8 @@ import { sidebarBadgeRoutes } from "./routes/sidebar-badges.js";
import { instanceSettingsRoutes } from "./routes/instance-settings.js";
import { ollamaRoutes } from "./routes/ollama.js";
import { llmRoutes } from "./routes/llms.js";
import { hardwareRoutes } from "./routes/hardware.js";
import { nexusSettingsRoutes } from "./routes/nexus-settings.js";
import { assetRoutes } from "./routes/assets.js";
import { accessRoutes } from "./routes/access.js";
import { pluginRoutes } from "./routes/plugins.js";
@ -127,6 +129,7 @@ export async function createApp(
app.all("/api/auth/*authPath", opts.betterAuthHandler);
}
app.use(llmRoutes(db));
app.use("/api", hardwareRoutes());
// Mount API routes
const api = Router();
@ -157,6 +160,7 @@ export async function createApp(
api.use(dashboardRoutes(db));
api.use(sidebarBadgeRoutes(db));
api.use(instanceSettingsRoutes(db));
api.use(nexusSettingsRoutes());
const hostServicesDisposers = new Map<string, () => void>();
const workerManager = createPluginWorkerManager();
const pluginRegistry = pluginRegistryService(db);

View file

@ -0,0 +1,66 @@
import os from "node:os";
import { Router } from "express";
import { hardwareService } from "../services/hardware.js";
// Unauthenticated — hardware is a property of the machine, not the user. Safe: read-only, no mutation, no secrets.
export function hardwareRoutes(): Router {
const router = Router();
router.get("/system/providers", async (_req, res) => {
try {
const info = await hardwareService().detect();
res.json(info);
} catch {
// Graceful degradation — return basic info without GPU data
const totalBytes = os.totalmem();
const freeBytes = os.freemem();
const totalGb = Math.round((totalBytes / (1024 * 1024 * 1024)) * 10) / 10;
const freeGb = Math.round((freeBytes / (1024 * 1024 * 1024)) * 10) / 10;
const usableGb = Math.round((freeBytes * 0.75) / (1024 * 1024 * 1024) * 10) / 10;
res.json({
totalGb,
freeGb,
usableGb,
platform: process.platform,
gpuName: null,
gpuVramGb: null,
unifiedMemory: false,
hardwareTier: "cpu_only",
cpuModel: os.cpus()[0]?.model ?? null,
});
}
});
router.get("/system/providers/recommendation", async (_req, res) => {
try {
const { fileURLToPath } = await import("node:url");
const path = await import("node:path");
const fs = await import("node:fs");
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const catalogPath = path.resolve(__dirname, "../data/ollama-model-catalog.json");
const catalog = JSON.parse(fs.readFileSync(catalogPath, "utf-8")) as {
models: Array<{
family: string;
variants: Array<{ name: string; ramGb: number; vramGb: number; quality: string; tier?: string[] }>;
}>;
};
const hardwareInfo = await hardwareService().detect();
// Filter catalog entries matching the detected hardware tier
const recommendedModels = catalog.models.flatMap((family) =>
family.variants
.filter((v) => !v.tier || v.tier.includes(hardwareInfo.hardwareTier))
.map((v) => ({ ...v, family: family.family })),
);
res.json({ hardwareInfo, recommendedModels });
} catch {
res.status(500).json({ error: "Failed to load hardware recommendations" });
}
});
return router;
}

View file

@ -0,0 +1,39 @@
import { Router } from "express";
import { assertBoard } from "./authz.js";
import { nexusSettingsService } from "../services/nexus-settings.js";
export function nexusSettingsRoutes(): Router {
const router = Router();
router.get("/nexus/settings", async (req, res) => {
try {
assertBoard(req);
const settings = await nexusSettingsService().get();
res.json(settings);
} catch (err: unknown) {
if (err && typeof err === "object" && "status" in err) {
const e = err as { status: number; message: string };
res.status(e.status).json({ error: e.message });
return;
}
res.status(500).json({ error: "Unexpected error" });
}
});
router.patch("/nexus/settings", async (req, res) => {
try {
assertBoard(req);
const updated = await nexusSettingsService().set(req.body);
res.json(updated);
} catch (err: unknown) {
if (err && typeof err === "object" && "status" in err) {
const e = err as { status: number; message: string };
res.status(e.status).json({ error: e.message });
return;
}
res.status(500).json({ error: "Unexpected error" });
}
});
return router;
}