Files
Termix/src/backend/starter.ts
T
LukeGus 5e7090542d refactor(ssh): split tunnel module into layered directory
ssh/tunnel/{index,routes,manager}.ts: server boot in index, HTTP handlers
in routes, tunnel state and engine (connect/retry/autostart) in manager.
Code motion only; port 30003 and endpoints unchanged.
2026-07-16 04:26:31 -05:00

253 lines
8.0 KiB
TypeScript

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 () => {
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 = "unknown";
const versionSources = [
() => process.env.VERSION,
() => {
try {
const packageJsonPath = path.join(process.cwd(), "package.json");
const packageJson = JSON.parse(
readFileSync(packageJsonPath, "utf-8"),
);
return packageJson.version;
} catch {
return null;
}
},
() => {
try {
const __filename = fileURLToPath(import.meta.url);
const packageJsonPath = path.join(
path.dirname(__filename),
"../../../package.json",
);
const packageJson = JSON.parse(
readFileSync(packageJsonPath, "utf-8"),
);
return packageJson.version;
} catch {
return null;
}
},
() => {
try {
const packageJsonPath = path.join("/app", "package.json");
const packageJson = JSON.parse(
readFileSync(packageJsonPath, "utf-8"),
);
return packageJson.version;
} catch {
return null;
}
},
];
for (const getVersion of versionSources) {
try {
const foundVersion = getVersion();
if (foundVersion && foundVersion !== "unknown") {
version = foundVersion;
break;
}
} catch {
continue;
}
}
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();
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 dbServer = await import("./database/database.js");
await (dbServer as unknown as { serverReady: Promise<void> }).serverReady;
await import("./ssh/terminal.js");
await import("./ssh/tunnel/index.js");
await import("./ssh/file-manager.js");
await import("./ssh/host-metrics.js");
await import("./ssh/docker/index.js");
await import("./ssh/docker/console.js");
await import("./ssh/tmux-monitor.js"); // --- tmux-monitor ---
await import("./serial/serial.js");
await import("./dashboard.js");
await import("./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("./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",
},
);
});
}
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);
}
})();