#!/usr/bin/env node "use strict"; const fs = require("fs"); const path = require("path"); function findModuleDirectory(root, moduleName, depth = 0) { if (depth > 5) { return ""; } let entries; try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch (_error) { return ""; } for (const entry of entries) { if (!entry.isDirectory()) { continue; } const fullPath = path.join(root, entry.name); if (entry.name === moduleName && fs.existsSync(path.join(fullPath, "package.json"))) { return fullPath; } } for (const entry of entries) { if (!entry.isDirectory()) { continue; } const fullPath = path.join(root, entry.name); const found = findModuleDirectory(fullPath, moduleName, depth + 1); if (found) { return found; } } return ""; } function loadDatabaseModule() { const candidates = [ "better-sqlite3", "/app/node_modules/better-sqlite3", "/app/server/node_modules/better-sqlite3", "/app/src/node_modules/better-sqlite3" ]; for (const root of ["/app/node_modules", "/app/server/node_modules", "/app/src/node_modules"]) { const found = findModuleDirectory(root, "better-sqlite3"); if (found) { candidates.push(found); } } for (const moduleName of candidates) { try { return require(moduleName); } catch (_error) { // Try the next location used by the upstream Uptime Kuma image. } } return null; } function nowSql() { return new Date().toISOString().replace("T", " ").slice(0, 19); } function defaultForColumn(column, monitor, userId) { const accepted = monitor.accepted_statuscodes || ["200-399"]; const defaults = { name: monitor.name, type: monitor.type, active: 1, user_id: userId, interval: monitor.interval || 60, retry_interval: monitor.retry_interval || 60, resend_interval: monitor.resend_interval || 0, maxretries: monitor.maxretries || 3, accepted_statuscodes: JSON.stringify(accepted), url: monitor.url || "", method: monitor.method || "GET", hostname: monitor.hostname || "", port: monitor.port || null, dns_resolve_type: monitor.dns_resolve_type || "A", dns_resolve_server: monitor.dns_resolve_server || "1.1.1.1", upside_down: 0, weight: monitor.weight || 2000, maxredirects: monitor.maxredirects || 10, timeout: monitor.timeout || 48, expiryNotification: 0, ignoreTls: monitor.ignoreTls ? 1 : 0, created_date: nowSql(), modified_date: nowSql(), body: "", headers: "", basic_auth_user: "", basic_auth_pass: "", authMethod: "", mqtt_success_message: "", database_connection_string: "", database_query: "", packet_size: 56 }; return Object.prototype.hasOwnProperty.call(defaults, column.name) ? defaults[column.name] : ""; } function monitorValues(monitor, columns, userId, includeCreatedDate) { const values = {}; const supported = new Set(columns.map((column) => column.name)); const requested = { name: monitor.name, type: monitor.type, active: 1, user_id: userId, interval: monitor.interval || 60, retry_interval: monitor.retry_interval || 60, resend_interval: monitor.resend_interval || 0, maxretries: monitor.maxretries || 3, accepted_statuscodes: JSON.stringify(monitor.accepted_statuscodes || ["200-399"]), method: monitor.method || "GET", url: monitor.url || "", hostname: monitor.hostname || "", port: monitor.port || null, dns_resolve_type: monitor.dns_resolve_type || "A", dns_resolve_server: monitor.dns_resolve_server || "1.1.1.1", maxredirects: monitor.maxredirects || 10, timeout: monitor.timeout || 48, ignoreTls: monitor.ignoreTls ? 1 : 0, modified_date: nowSql() }; if (includeCreatedDate) { requested.created_date = nowSql(); } for (const [key, value] of Object.entries(requested)) { if (supported.has(key)) { values[key] = value; } } for (const column of columns) { if (column.pk || Object.prototype.hasOwnProperty.call(values, column.name)) { continue; } if (column.notnull && column.dflt_value === null) { values[column.name] = defaultForColumn(column, monitor, userId); } } return values; } function upsertMonitor(database, monitor, columns, userId) { const existing = database .prepare("select id from monitor where user_id = ? and name = ? limit 1") .get(userId, monitor.name); if (existing) { const values = monitorValues(monitor, columns, userId, false); delete values.name; delete values.user_id; const assignments = Object.keys(values).map((key) => `"${key}" = @${key}`); database .prepare(`update monitor set ${assignments.join(", ")} where id = @id`) .run({ ...values, id: existing.id }); console.log(`updated ${monitor.name}`); return; } const values = monitorValues(monitor, columns, userId, true); const keys = Object.keys(values); database .prepare( `insert into monitor (${keys.map((key) => `"${key}"`).join(", ")}) values (${keys .map((key) => `@${key}`) .join(", ")})` ) .run(values); console.log(`created ${monitor.name}`); } function main() { const monitorFile = process.argv[2] || "/tmp/homelab-kuma-monitors.json"; const databasePath = process.argv[3] || "/app/data/kuma.db"; const monitors = JSON.parse(fs.readFileSync(monitorFile, "utf8")); const Database = loadDatabaseModule(); if (!Database) { console.log("Could not load better-sqlite3 from the Uptime Kuma container; skipping monitor seed"); return; } const database = new Database(databasePath); const userTable = database .prepare("select name from sqlite_master where type = 'table' and name = 'user'") .get(); if (!userTable) { console.log("Uptime Kuma user table is missing; skipping monitor seed"); return; } const user = database.prepare("select id from user order by id limit 1").get(); if (!user) { console.log("Uptime Kuma initial setup has not been completed; skipping monitor seed"); return; } const columns = database.prepare("pragma table_info(monitor)").all(); if (!columns.length) { throw new Error("Uptime Kuma monitor table is missing"); } const transaction = database.transaction(() => { for (const monitor of monitors) { upsertMonitor(database, monitor, columns, user.id); } }); transaction(); } main();