Files
Termix/src/backend/starter.ts
T
TomyJan 3375733789 fix: resolve backend build errors, preserve sudo file read buffers, and format code (#798)
* fix: resolve backend TypeScript build errors and preserve sudo file read buffers

* style: format code

* fix: ssh2 ESM import failure

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 13:18:42 -05:00

245 lines
7.6 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 { 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();
await AutoSSLSetup.initialize();
systemLogger.success("SSL setup completed", {
operation: "backend_init_ssl",
sslEnabled: process.env.SSL_ENABLED === "true",
});
const dbModule = await import("./database/db/index.js");
await dbModule.initializeDatabase();
systemLogger.success("Database initialized", {
operation: "backend_init_db",
});
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,
},
);
});
},
);
await import("./database/database.js");
await import("./ssh/terminal.js");
await import("./ssh/tunnel.js");
await import("./ssh/file-manager.js");
await import("./ssh/server-stats.js");
await import("./ssh/docker.js");
await import("./ssh/docker-console.js");
await import("./dashboard.js");
// Initialize log level from database settings
const { getDb: getDbForSettings } = await import("./database/db/index.js");
const settingsDb = getDbForSettings();
const logLevelRow = settingsDb.$client
.prepare("SELECT value FROM settings WHERE key = 'log_level'")
.get() as { value: string } | undefined;
if (logLevelRow) {
setGlobalLogLevel(logLevelRow.value);
systemLogger.info(`Log level set to: ${logLevelRow.value}`, {
operation: "log_level_init",
});
}
// Initialize Guacamole server for RDP/VNC/Telnet support
const { getDb: getDbForGuac } = await import("./database/db/index.js");
const guacDb = getDbForGuac();
const guacEnabledRow = guacDb.$client
.prepare("SELECT value FROM settings WHERE key = 'guac_enabled'")
.get() as { value: string } | undefined;
const guacEnabled = guacEnabledRow
? guacEnabledRow.value !== "false"
: true;
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.SSL_ENABLED === "true",
duration: Date.now() - initStartTime,
});
const gracefulShutdown = async (signal: string) => {
systemLogger.info(`Received ${signal}, initiating graceful shutdown...`, {
operation: "shutdown",
});
try {
const { saveMemoryDatabaseToFile } =
await import("./database/db/index.js");
await saveMemoryDatabaseToFile();
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);
}
})();