Files
stack/packages/model-proxy/src/settings.mjs
T
jason.woltje 362893d043 feat: integrate reviewed proxy page and managed model keys
Source-candidate: poe d9a564e6d20f5f0fd75ce45b12cbbf8bb4c9d568\nSource-candidate: magica38ba5762b06716db1dee00d0afec162656488f5c\nValidated321whole-file phase plus whole bus1349/model-proxy100/webui490; final gate tooling checks pending, not landed.
2026-10-26 12:04:21 -05:00

100 lines
5.8 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import net from 'node:net';
import { randomBytes } from 'node:crypto';
import { PIN, refuse, safePath, regularBytes, sha256, validManagementKey, proxyTiming } from './common.mjs';
export function installPaths(dataRoot, pin = PIN) {
const dir = path.join(dataRoot, 'opt', 'cliproxyapi', pin.version);
return { dir, binary: path.join(dir, 'cli-proxy-api'), panel: path.join(dir, 'static', 'management.html') };
}
export function validateProxy(value, dataRoot, pin = PIN) {
if (!value || typeof value !== 'object' || Array.isArray(value)
|| Object.keys(value).some(k => !['binary', 'port', 'rotateGraceMinutes', 'firstOutputSeconds'].includes(k))
|| !Number.isInteger(value.port) || value.port < 1024 || value.port > 65535 || value.port === 8317
|| value.binary !== installPaths(dataRoot, pin).binary) refuse('proxy-config-invalid');
return { port: value.port, binary: value.binary, ...proxyTiming(value) };
}
export function loadProxy(configDir, dataRoot) {
const file = path.join(configDir, 'proxy.json');
try { fs.lstatSync(file); } catch (e) { if (e.code === 'ENOENT') return undefined; refuse('proxy-config-invalid'); }
try { return validateProxy(JSON.parse(regularBytes(file, 'proxy-config-invalid')), dataRoot); }
catch (error) {
if (['proxy-rotate-grace-invalid', 'proxy-first-output-invalid'].includes(error.code)) throw error;
refuse('proxy-config-invalid');
}
}
export function verifyInstall(dataRoot, pin = PIN) {
const paths = installPaths(dataRoot, pin);
for (const [file, hash] of [[paths.binary, pin.binary.sha256], [paths.panel, pin.panel.sha256]]) {
try { fs.lstatSync(file); } catch (e) { if (e.code === 'ENOENT') refuse('proxy-install-missing'); refuse('proxy-install-invalid'); }
if (sha256(regularBytes(file, 'proxy-install-invalid')) !== hash) refuse('proxy-install-invalid');
}
return paths;
}
async function reservePort(wanted) {
if (wanted !== undefined && (!Number.isInteger(wanted) || wanted < 1024 || wanted > 65535 || wanted === 8317)) refuse('proxy-config-invalid');
const server = net.createServer();
try {
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(wanted ?? 0, '127.0.0.1', resolve); });
const port = server.address().port;
if (port === 8317 || port < 1024) { await new Promise(r => server.close(r)); return reservePort(wanted); }
return { port, release: () => new Promise(r => server.close(r)) };
} catch { refuse('proxy-port-in-use'); }
}
export function initialConfig({ dataRoot, port, key, sentinel }) {
return {
'config-version': 8,
server: { host: '127.0.0.1', port },
management: { 'allow-remote': false, 'secret-key': key, 'disable-control-panel': false, 'disable-auto-update-panel': true },
access: { 'api-keys': [sentinel] },
oauth: { 'auth-dir': path.join(dataRoot, 'proxy', 'auth') },
observability: { logs: { 'logging-to-file': true } },
routing: { strategy: 'fill-first', 'session-affinity': true, 'session-affinity-rebind-on': 'quota', retry: { 'request-retry': 0 } },
};
}
// Called only by the bus's path-bound human verb, never by an agent or the tester.
export async function initializeProxy({ configDir, dataRoot, key, port, pin = PIN, ...options }) {
if (!validManagementKey(key)) refuse('proxy-key-invalid');
const timing = proxyTiming(options);
safePath(configDir, 'proxy-init-failed'); safePath(dataRoot, 'proxy-init-failed');
const paths = verifyInstall(dataRoot, pin);
const settingsFile = path.join(configDir, 'proxy.json'), proxyDir = path.join(dataRoot, 'proxy');
const secrets = path.join(configDir, 'secrets'), keyFile = path.join(secrets, 'proxy-management.key');
for (const file of [settingsFile, proxyDir, keyFile]) {
try { fs.lstatSync(file); refuse('proxy-already-initialized'); } catch (e) { if (e.code !== 'ENOENT') throw e; }
}
const lockFile = path.join(configDir, '.proxy-init.lock');
let lock;
try { lock = fs.openSync(lockFile, 'wx', 0o600); } catch { refuse('proxy-init-busy'); }
let reservation, ownsProxy = false, ownsKey = false, ownsSettings = false, ownsSecrets = false;
try {
reservation = await reservePort(port);
safePath(secrets, 'proxy-init-failed');
try { fs.mkdirSync(secrets, { mode: 0o700 }); ownsSecrets = true; }
catch (e) { if (e.code !== 'EEXIST') throw e; }
const stat = fs.lstatSync(secrets);
if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 0o777) !== 0o700) refuse('proxy-init-failed');
fs.mkdirSync(proxyDir, { mode: 0o700 }); ownsProxy = true;
for (const name of ['auth', 'logs']) fs.mkdirSync(path.join(proxyDir, name), { mode: 0o700 });
const keyFd = fs.openSync(keyFile, 'wx', 0o600); ownsKey = true;
try { fs.writeFileSync(keyFd, key); } finally { fs.closeSync(keyFd); }
const tag = sha256(configDir).slice(0, 12);
const sentinel = `msk-${tag}-sentinel-${randomBytes(32).toString('base64url')}`;
fs.writeFileSync(path.join(proxyDir, 'config.yaml'), JSON.stringify(initialConfig({ dataRoot, port: reservation.port, key, sentinel }), null, 2) + '\n', { flag: 'wx', mode: 0o600 });
const settings = { port: reservation.port, binary: paths.binary, ...timing };
const settingsFd = fs.openSync(settingsFile, 'wx', 0o600); ownsSettings = true;
try { fs.writeFileSync(settingsFd, JSON.stringify(settings, null, 2) + '\n'); } finally { fs.closeSync(settingsFd); }
return settings;
} catch (e) {
if (ownsSettings) fs.unlinkSync(settingsFile);
if (ownsKey) fs.unlinkSync(keyFile);
if (ownsProxy) fs.rmSync(proxyDir, { recursive: true });
if (ownsSecrets) fs.rmdirSync(secrets);
if (e.code?.startsWith('proxy-')) throw e;
refuse('proxy-init-failed');
} finally {
if (reservation) await reservation.release();
fs.closeSync(lock); fs.unlinkSync(lockFile);
}
}