import dotenv from "dotenv"; import { promises as fs } from "fs"; import { readFileSync } from "fs"; import path from "path"; import { fileURLToPath } from "url"; import { AutoSSLSetup } from "./utils/auto-ssl-setup.js"; import { AuthManager } from "./utils/auth-manager.js"; import { DataCrypto } from "./utils/data-crypto.js"; import { ensureDatabaseLayerPreupgradeBackup } from "./utils/database-layer-preupgrade-backup.js"; import { DatabaseSaveTrigger } from "./utils/database-save-trigger.js"; import { SystemCrypto } from "./utils/system-crypto.js"; import { systemLogger, versionLogger, setGlobalLogLevel, } from "./utils/logger.js"; async function provisionLocalDesktopUserIfNeeded(): Promise { const { createCurrentUserRepository, createCurrentRoleRepository } = await import("./database/repositories/factory.js"); const { AuthManager } = await import("./utils/auth-manager.js"); const crypto = await import("crypto"); const userRepository = createCurrentUserRepository(); const existingCount = await userRepository.countAll(); if (existingCount > 0) { const allUsers = await userRepository.listAll(); for (const user of allUsers) { try { await AuthManager.getInstance().registerUser(user.id); } catch (dekError) { systemLogger.error( "Failed to verify/provision data-encryption key for existing user", dekError, { operation: "desktop_dek_healing", userId: user.id }, ); } } return; } const id = crypto.randomUUID(); const { isFirstUser } = await userRepository.createFirstLocalUser({ id, username: "local", passwordHash: "", isOidc: false, clientId: "", clientSecret: "", issuerUrl: "", authorizationUrl: "", tokenUrl: "", identifierPath: "", namePath: "", scopes: "openid email profile", totpSecret: null, totpEnabled: false, totpBackupCodes: null, }); try { await createCurrentRoleRepository().assignRoleNameToUser({ userId: id, roleName: isFirstUser ? "admin" : "user", grantedBy: id, }); } catch (roleError) { systemLogger.error( "Failed to assign default role to auto-provisioned local user", roleError, { operation: "desktop_auto_provision_role" }, ); } await AuthManager.getInstance().registerUser( id, crypto.randomBytes(32).toString("hex"), ); systemLogger.success("Auto-provisioned local desktop user", { operation: "desktop_auto_provision", userId: id, }); } (async () => { const initStartTime = Date.now(); try { dotenv.config({ quiet: true }); const dataDir = process.env.DATA_DIR || "./db/data"; const envPath = path.join(dataDir, ".env"); try { await fs.access(envPath); const persistentConfig = dotenv.config({ path: envPath, quiet: true }); if (persistentConfig.parsed) { Object.assign(process.env, persistentConfig.parsed); } } catch { // expected - env file may not exist } systemLogger.info("Termix backend initialization started", { operation: "backend_init_start", nodeEnv: process.env.NODE_ENV || "production", port: process.env.PORT || 4090, }); let version = process.env.VERSION || "unknown"; if (version === "unknown") { const candidates = [ path.join(process.cwd(), "package.json"), path.join( path.dirname(fileURLToPath(import.meta.url)), "../../../package.json", ), ]; for (const packageJsonPath of candidates) { try { const packageJson = JSON.parse( readFileSync(packageJsonPath, "utf-8"), ); if (packageJson.version) { version = packageJson.version; break; } } catch { // try the next location } } } process.env.VERSION = version; versionLogger.info(`Termix Backend starting - Version: ${version}`, { operation: "startup", version: version, }); const systemCrypto = SystemCrypto.getInstance(); await systemCrypto.initializeJWTSecret(); await systemCrypto.initializeDatabaseKey(); await systemCrypto.initializeEncryptionKey(); await systemCrypto.initializeInternalAuthToken(); ensureDatabaseLayerPreupgradeBackup({ dataDir, version }); await AutoSSLSetup.initialize(); systemLogger.success("SSL setup completed", { operation: "backend_init_ssl", sslEnabled: process.env.ENABLE_SSL === "true", }); const dbModule = await import("./database/db/index.js"); await dbModule.initializeDatabase(); systemLogger.success("Database initialized", { operation: "backend_init_db", }); const { UserKeyManager } = await import("./utils/user-keys.js"); await UserKeyManager.getInstance().initialize(); const { runBootDekMigration } = await import("./utils/crypto-migration/dek-migration.js"); await runBootDekMigration({ cleanupLegacy: true }); const { runLegacySharedCredentialCleanup } = await import("./utils/crypto-migration/legacy-share-cleanup.js"); await runLegacySharedCredentialCleanup(); const authManager = AuthManager.getInstance(); await authManager.initialize(); DataCrypto.initialize(); const { runSharedHostSecretsMigration } = await import("./utils/crypto-migration/shared-host-secrets-migration.js"); await runSharedHostSecretsMigration(); if (process.env.ELECTRON_EMBEDDED === "true") { await provisionLocalDesktopUserIfNeeded(); } import("./utils/opkssh-binary-manager.js").then( ({ OPKSSHBinaryManager }) => { OPKSSHBinaryManager.ensureBinary().catch((error) => { const dataDir = process.env.DATA_DIR || path.join(process.cwd(), "db", "data"); systemLogger.warn( "Failed to initialize OPKSSH binary - OPKSSH authentication will not be available", { operation: "opkssh_binary_init_failed", error: error instanceof Error ? error.message : "Unknown error", stack: error instanceof Error ? error.stack : undefined, platform: process.platform, arch: process.arch, dataDir, }, ); }); }, ); const { serverReady } = await import("./database/database.js"); await serverReady; await import("./hosts/terminal/index.js"); await import("./hosts/tunnel/index.js"); await import("./hosts/file-manager/index.js"); await import("./hosts/metrics/index.js"); await import("./hosts/docker/index.js"); await import("./hosts/docker/console.js"); await import("./hosts/tmux/index.js"); await import("./hosts/serial.js"); await import("./services/dashboard.js"); await import("./services/homepage.js"); // Initialize log level from database settings const { getCurrentSettingValue } = await import("./database/repositories/factory.js"); const logLevel = getCurrentSettingValue("log_level"); if (logLevel) { setGlobalLogLevel(logLevel); systemLogger.info(`Log level set to: ${logLevel}`, { operation: "log_level_init", }); } // Initialize Guacamole server for RDP/VNC/Telnet support const guacEnabled = getCurrentSettingValue("guac_enabled") !== "false"; if (process.env.ENABLE_GUACAMOLE !== "false" && guacEnabled) { import("./hosts/guacamole/guacamole-server.js") .then(() => { systemLogger.info("Guacamole server initialized", { operation: "guac_init", }); }) .catch((error) => { systemLogger.warn( "Failed to initialize Guacamole server (guacd may not be available)", { operation: "guac_init_skip", error: error instanceof Error ? error.message : "Unknown error", }, ); }); } const { startAnalyticsHeartbeat } = await import("./utils/analytics.js"); startAnalyticsHeartbeat(); systemLogger.success("Termix backend started successfully", { operation: "backend_init_complete", port: process.env.PORT || 4090, ssl: process.env.ENABLE_SSL === "true", duration: Date.now() - initStartTime, }); const gracefulShutdown = async (signal: string) => { systemLogger.info(`Received ${signal}, initiating graceful shutdown...`, { operation: "shutdown", }); try { await DatabaseSaveTrigger.forceSave("shutdown_explicit_save"); systemLogger.info("Database saved to disk before exit", { operation: "shutdown_db_saved", }); } catch (error) { systemLogger.error("Failed to save database during shutdown", error, { operation: "shutdown_db_save_failed", }); } process.exit(0); }; process.on("SIGINT", () => gracefulShutdown("SIGINT")); process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); process.on("message", (msg: { type?: string }) => { if (msg?.type === "shutdown") { gracefulShutdown("IPC shutdown"); } }); process.on("uncaughtException", (error) => { systemLogger.error("Uncaught exception occurred", error, { operation: "error_handling", }); process.exit(1); }); process.on("unhandledRejection", (reason) => { systemLogger.error("Unhandled promise rejection", reason, { operation: "error_handling", }); process.exit(1); }); } catch (error) { systemLogger.error("Failed to initialize backend services", error, { operation: "startup_failed", }); process.exit(1); } })();