refactor(caddy): remove redundant GhostGrid domain fields, keep only custom routes

caddy_prod_domain and caddy_dev_domain are already handled by the Proxmox deploy
process. The Caddy integration is a generic TLS proxy for additional services
(Semaphore, Netbox, etc.) — the custom routes list is the sole mechanism.
This commit is contained in:
Brückner
2026-06-08 08:45:24 +02:00
parent 7afb4829bc
commit f1200425af
4 changed files with 1214 additions and 94 deletions

View File

@ -10,112 +10,114 @@ db.pragma('journal_mode = WAL');
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'User',
email TEXT NOT NULL UNIQUE,
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'User',
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS devices (
id TEXT PRIMARY KEY,
hostname TEXT NOT NULL,
ip TEXT NOT NULL,
location TEXT NOT NULL,
notes TEXT,
type TEXT NOT NULL,
status TEXT NOT NULL,
id TEXT PRIMARY KEY,
hostname TEXT NOT NULL,
ip TEXT NOT NULL,
location TEXT NOT NULL,
notes TEXT,
type TEXT NOT NULL,
status TEXT NOT NULL,
emergencySheet TEXT NOT NULL,
lastCheckedAt TEXT
lastCheckedAt TEXT,
checkMkUrl TEXT NOT NULL DEFAULT '',
cmkHostname TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS labs (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
contactPerson TEXT NOT NULL,
location TEXT NOT NULL,
deviceIds TEXT NOT NULL,
topology TEXT NOT NULL
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
contactPerson TEXT NOT NULL,
location TEXT NOT NULL,
deviceIds TEXT NOT NULL,
topology TEXT NOT NULL,
semaphoreSetupTemplateId TEXT NOT NULL DEFAULT '',
semaphoreTeardownTemplateId TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS bookings (
id TEXT PRIMARY KEY,
labId TEXT NOT NULL,
userId TEXT NOT NULL,
startDateTime TEXT NOT NULL,
endDateTime TEXT NOT NULL,
notes TEXT,
status TEXT NOT NULL,
notified INTEGER DEFAULT 0,
emailSent INTEGER DEFAULT 0
id TEXT PRIMARY KEY,
labId TEXT NOT NULL,
userId TEXT NOT NULL,
startDateTime TEXT NOT NULL,
endDateTime TEXT NOT NULL,
notes TEXT,
status TEXT NOT NULL,
notified INTEGER NOT NULL DEFAULT 0,
emailSent INTEGER NOT NULL DEFAULT 0,
ansibleSetupTriggered INTEGER NOT NULL DEFAULT 0,
ansibleTeardownTriggered INTEGER NOT NULL DEFAULT 0,
ansibleSetupJobId TEXT NOT NULL DEFAULT '',
ansibleTeardownJobId TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS logs (
id TEXT PRIMARY KEY,
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
type TEXT NOT NULL,
message TEXT NOT NULL,
deviceId TEXT,
userId TEXT
type TEXT NOT NULL,
message TEXT NOT NULL,
deviceId TEXT,
userId TEXT
);
CREATE TABLE IF NOT EXISTS links (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
url TEXT NOT NULL,
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
url TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
category TEXT NOT NULL DEFAULT '',
color TEXT NOT NULL DEFAULT 'emerald',
createdBy TEXT,
createdAt TEXT NOT NULL
category TEXT NOT NULL DEFAULT '',
color TEXT NOT NULL DEFAULT 'emerald',
createdBy TEXT,
createdAt TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS caddy (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostname TEXT NOT NULL,
upstream TEXT NOT NULL,
tls INTEGER NOT NULL DEFAULT 1,
compress INTEGER NOT NULL DEFAULT 1,
created_at TEXT DEFAULT (datetime('now'))
);
`);
// Lightweight migrations for columns added after the initial release.
// CREATE TABLE IF NOT EXISTS never alters an existing table, so add them by hand.
function ensureColumn(table: string, column: string, ddl: string) {
const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];
if (!cols.some(c => c.name === column)) {
db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
}
}
// Seed default settings — INSERT OR IGNORE writes a key only if it is absent.
const DEFAULT_SETTINGS: Record<string, string> = {
azure_enabled: 'false',
azure_client_id: '',
azure_tenant_id: '',
azure_client_secret: '',
azure_redirect_uri: '',
azure_allowed_group: '',
checkmk_enabled: 'false',
checkmk_api_url: '',
checkmk_api_user: 'automation',
checkmk_api_secret: '',
checkmk_sync_interval_ms: '60000',
semaphore_enabled: 'false',
semaphore_api_url: '',
semaphore_api_token: '',
semaphore_project_id: '',
caddy_enabled: 'false',
caddy_admin_url: 'http://localhost:2019',
};
ensureColumn('devices', 'checkMkUrl', "checkMkUrl TEXT NOT NULL DEFAULT ''");
ensureColumn('devices', 'cmkHostname', "cmkHostname TEXT NOT NULL DEFAULT ''");
ensureColumn('labs', 'semaphoreSetupTemplateId', "semaphoreSetupTemplateId TEXT NOT NULL DEFAULT ''");
ensureColumn('labs', 'semaphoreTeardownTemplateId', "semaphoreTeardownTemplateId TEXT NOT NULL DEFAULT ''");
ensureColumn('bookings', 'ansibleSetupTriggered', 'ansibleSetupTriggered INTEGER NOT NULL DEFAULT 0');
ensureColumn('bookings', 'ansibleTeardownTriggered', 'ansibleTeardownTriggered INTEGER NOT NULL DEFAULT 0');
ensureColumn('bookings', 'ansibleSetupJobId', "ansibleSetupJobId TEXT NOT NULL DEFAULT ''");
ensureColumn('bookings', 'ansibleTeardownJobId', "ansibleTeardownJobId TEXT NOT NULL DEFAULT ''");
// Seed default settings (INSERT OR IGNORE = only if key absent)
const _insertDefault = db.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)');
const _defaultSettings: [string, string][] = [
['azure_enabled', 'false'],
['azure_client_id', ''],
['azure_tenant_id', ''],
['azure_client_secret', ''],
['azure_redirect_uri', ''],
['azure_allowed_group', ''],
['checkmk_enabled', 'false'],
['checkmk_api_url', ''],
['checkmk_api_user', 'automation'],
['checkmk_api_secret', ''],
['checkmk_sync_interval_ms', '60000'],
['semaphore_enabled', 'false'],
['semaphore_api_url', ''],
['semaphore_api_token', ''],
['semaphore_project_id', ''],
];
for (const [k, v] of _defaultSettings) _insertDefault.run(k, v);
const seedSetting = db.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)');
for (const [key, value] of Object.entries(DEFAULT_SETTINGS)) seedSetting.run(key, value);
export function getSetting(key: string): string {
const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined;
@ -131,4 +133,28 @@ export function getAllSettings(): Record<string, string> {
return Object.fromEntries(rows.map(r => [r.key, r.value]));
}
export interface CaddyRoute {
id: number;
hostname: string;
upstream: string;
tls: number;
compress: number;
created_at: string;
}
export function getCaddyRoutes(): CaddyRoute[] {
return db.prepare('SELECT * FROM caddy ORDER BY id ASC').all() as CaddyRoute[];
}
export function addCaddyRoute(hostname: string, upstream: string, tls: boolean, compress: boolean): CaddyRoute {
const { lastInsertRowid } = db.prepare(
'INSERT INTO caddy (hostname, upstream, tls, compress) VALUES (?, ?, ?, ?)'
).run(hostname, upstream, tls ? 1 : 0, compress ? 1 : 0);
return db.prepare('SELECT * FROM caddy WHERE id = ?').get(lastInsertRowid) as CaddyRoute;
}
export function deleteCaddyRoute(id: number): void {
db.prepare('DELETE FROM caddy WHERE id = ?').run(id);
}
export default db;