a431b87f1f
Local auth stays the primary/always-available login (don't want to lock out the saved admin password) — OIDC is additive, shown as a second button when OIDC_ENABLED=true. Uses openid-client v6 with PKCE. Authentik-side provider was set up via an authentik blueprint (its own declarative automation, see docs/oidc-setup.md) rather than touching any existing admin credentials. Closes #12. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
119 lines
3.5 KiB
TypeScript
119 lines
3.5 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { parse } from "yaml";
|
|
import type { SshHostConfig } from "../collectors/sshHost.js";
|
|
|
|
interface RawSshHost {
|
|
id: string;
|
|
displayName: string;
|
|
group: string;
|
|
host: string;
|
|
port?: number;
|
|
username: string;
|
|
diskPaths?: { path: string; label: string }[];
|
|
}
|
|
|
|
interface RawKnownDevice {
|
|
ip: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface HostsConfig {
|
|
proxmox: {
|
|
host: string;
|
|
node: string;
|
|
};
|
|
sshHosts?: RawSshHost[];
|
|
knownDevices?: RawKnownDevice[];
|
|
}
|
|
|
|
export interface AppConfig {
|
|
port: number;
|
|
dbPath: string;
|
|
pollIntervalSeconds: number;
|
|
snapshotRetentionHours: number;
|
|
sessionSecret: string;
|
|
// Only set once a TLS-terminating reverse proxy sits in front (see issue #13) —
|
|
// browsers silently drop `secure` cookies over plain HTTP.
|
|
cookieSecure: boolean;
|
|
adminUsername: string;
|
|
adminPassword: string | undefined;
|
|
proxmox: {
|
|
host: string;
|
|
node: string;
|
|
tokenId: string;
|
|
tokenSecret: string;
|
|
};
|
|
sshHosts: SshHostConfig[];
|
|
knownDevices: Map<string, string>;
|
|
discoveryFilePath: string;
|
|
oidc: OidcConfig | undefined;
|
|
hosts: HostsConfig;
|
|
}
|
|
|
|
export interface OidcConfig {
|
|
issuerUrl: string;
|
|
clientId: string;
|
|
clientSecret: string;
|
|
redirectUri: string;
|
|
// Authentik's cert is self-signed on the LAN, same as Proxmox's.
|
|
allowInsecureTls: boolean;
|
|
}
|
|
|
|
function required(name: string): string {
|
|
const v = process.env[name];
|
|
if (!v) throw new Error(`Missing required env var: ${name}`);
|
|
return v;
|
|
}
|
|
|
|
export function loadConfig(hostsConfigPath: string): AppConfig {
|
|
const hosts = parse(readFileSync(hostsConfigPath, "utf8")) as HostsConfig;
|
|
const sshPrivateKeyPath = process.env.SSH_PRIVATE_KEY_PATH ?? "./ssh/monitor_ed25519";
|
|
|
|
// Local auth (bcrypt + session) is always available. OIDC is an *additional*
|
|
// sign-in option when configured, not a replacement — see issue #12 comments
|
|
// for why (don't want to lock out the already-saved local admin password).
|
|
const oidc: OidcConfig | undefined =
|
|
process.env.OIDC_ENABLED === "true"
|
|
? {
|
|
issuerUrl: required("OIDC_ISSUER_URL"),
|
|
clientId: required("OIDC_CLIENT_ID"),
|
|
clientSecret: required("OIDC_CLIENT_SECRET"),
|
|
redirectUri: required("OIDC_REDIRECT_URI"),
|
|
allowInsecureTls: process.env.OIDC_ALLOW_INSECURE_TLS === "true",
|
|
}
|
|
: undefined;
|
|
|
|
const sshHosts: SshHostConfig[] = (hosts.sshHosts ?? []).map((h) => ({
|
|
id: h.id,
|
|
displayName: h.displayName,
|
|
group: h.group,
|
|
host: h.host,
|
|
port: h.port ?? 22,
|
|
username: h.username,
|
|
privateKeyPath: sshPrivateKeyPath,
|
|
diskPaths: h.diskPaths ?? [{ path: "/", label: "root" }],
|
|
}));
|
|
|
|
return {
|
|
port: Number(process.env.PORT ?? 3000),
|
|
dbPath: process.env.DB_PATH ?? "./data/monitor.db",
|
|
pollIntervalSeconds: Number(process.env.POLL_INTERVAL_SECONDS ?? 30),
|
|
snapshotRetentionHours: Number(process.env.SNAPSHOT_RETENTION_HOURS ?? 24),
|
|
sessionSecret: required("SESSION_SECRET"),
|
|
cookieSecure: process.env.COOKIE_SECURE === "true",
|
|
adminUsername: process.env.ADMIN_USERNAME ?? "admin",
|
|
adminPassword: process.env.ADMIN_PASSWORD,
|
|
proxmox: {
|
|
host: hosts.proxmox.host,
|
|
node: hosts.proxmox.node,
|
|
tokenId: required("PROXMOX_TOKEN_ID"),
|
|
tokenSecret: required("PROXMOX_TOKEN_SECRET"),
|
|
},
|
|
sshHosts,
|
|
knownDevices: new Map((hosts.knownDevices ?? []).map((d) => [d.ip, d.name])),
|
|
discoveryFilePath: process.env.DISCOVERY_FILE_PATH ?? "./data/devices-raw.json",
|
|
oidc,
|
|
hosts,
|
|
};
|
|
}
|