mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
import { execFileSync } from "child_process";
|
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
import path from "path";
|
|
import { authLogger } from "../utils/logger.js";
|
|
|
|
// The Docker image's paths. An install outside Docker (a Proxmox LXC script,
|
|
// a source checkout) points these at its own nginx instead.
|
|
const nginxTemplate = () =>
|
|
process.env.TERMIX_NGINX_TEMPLATE || "/app/nginx/nginx-https.conf.template";
|
|
const nginxConf = () =>
|
|
process.env.TERMIX_NGINX_CONF || "/tmp/nginx/nginx.conf";
|
|
const nginxPid = () => process.env.TERMIX_NGINX_PID || "/tmp/nginx/nginx.pid";
|
|
|
|
/** The template with Termix's own folder in place of the image's /app. */
|
|
export function renderNginxTemplate(
|
|
template: string,
|
|
values: {
|
|
port: string;
|
|
sslPort: string;
|
|
certPath: string;
|
|
keyPath: string;
|
|
appDir?: string;
|
|
},
|
|
): string {
|
|
let rendered = template
|
|
.split("${PORT}")
|
|
.join(values.port)
|
|
.split("${SSL_PORT}")
|
|
.join(values.sslPort)
|
|
.split("${SSL_CERT_PATH}")
|
|
.join(values.certPath)
|
|
.split("${SSL_KEY_PATH}")
|
|
.join(values.keyPath);
|
|
const appDir = values.appDir?.replace(/\/+$/, "");
|
|
if (appDir && appDir !== "/app") {
|
|
rendered = rendered.replace(/(^|[\s;])\/app\//gm, `$1${appDir}/`);
|
|
}
|
|
return rendered;
|
|
}
|
|
|
|
const DATA_DIR = process.env.DATA_DIR || "./db/data";
|
|
const SSL_DIR = path.join(DATA_DIR, "ssl");
|
|
const ENV_FILE = path.join(DATA_DIR, ".env");
|
|
|
|
export function persistSSLEnv(
|
|
sslPort: string,
|
|
certPath: string,
|
|
keyPath: string,
|
|
) {
|
|
const vars: Record<string, string> = {
|
|
ENABLE_SSL: "true",
|
|
SSL_PORT: sslPort,
|
|
SSL_CERT_PATH: certPath,
|
|
SSL_KEY_PATH: keyPath,
|
|
};
|
|
|
|
let content = "";
|
|
try {
|
|
content = readFileSync(ENV_FILE, "utf8");
|
|
} catch {
|
|
// no existing .env file yet
|
|
}
|
|
|
|
for (const [key, value] of Object.entries(vars)) {
|
|
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
if (regex.test(content)) {
|
|
content = content.replace(regex, `${key}=${value}`);
|
|
} else {
|
|
if (!content.includes("# SSL Configuration")) {
|
|
content += `\n# SSL Configuration (Auto-generated)\n`;
|
|
}
|
|
content += `${key}=${value}\n`;
|
|
}
|
|
}
|
|
|
|
writeFileSync(ENV_FILE, content.trim() + "\n");
|
|
}
|
|
|
|
/**
|
|
* Regenerates the nginx config from the HTTPS template and reloads nginx so a
|
|
* newly issued/uploaded certificate is served without a full container
|
|
* restart. No-ops outside the Docker image (template/binary won't exist).
|
|
*/
|
|
export function reloadNginxWithSSL(): { applied: boolean; message: string } {
|
|
const NGINX_TEMPLATE = nginxTemplate();
|
|
const NGINX_CONF = nginxConf();
|
|
if (!existsSync(NGINX_TEMPLATE) || !existsSync(nginxPid())) {
|
|
return {
|
|
applied: false,
|
|
message:
|
|
"Termix could not find the nginx it runs with, so HTTPS is not on yet. In Docker, restart Termix with ENABLE_SSL=true. On another install, set TERMIX_NGINX_TEMPLATE, TERMIX_NGINX_CONF and TERMIX_NGINX_PID (and TERMIX_APP_DIR if it is not /app), or add a listen 443 ssl block with this certificate to your nginx config.",
|
|
};
|
|
}
|
|
|
|
const port = process.env.PORT || "8080";
|
|
const sslPort = process.env.SSL_PORT || "8443";
|
|
const certPath =
|
|
process.env.SSL_CERT_PATH || path.join(SSL_DIR, "termix.crt");
|
|
const keyPath = process.env.SSL_KEY_PATH || path.join(SSL_DIR, "termix.key");
|
|
|
|
try {
|
|
const rendered = renderNginxTemplate(readFileSync(NGINX_TEMPLATE, "utf8"), {
|
|
port,
|
|
sslPort,
|
|
certPath,
|
|
keyPath,
|
|
appDir: process.env.TERMIX_APP_DIR,
|
|
});
|
|
|
|
writeFileSync(NGINX_CONF, rendered);
|
|
|
|
execFileSync("nginx", ["-t", "-c", NGINX_CONF], { stdio: "pipe" });
|
|
execFileSync("nginx", ["-s", "reload", "-c", NGINX_CONF], {
|
|
stdio: "pipe",
|
|
});
|
|
|
|
process.env.ENABLE_SSL = "true";
|
|
process.env.SSL_PORT = sslPort;
|
|
process.env.SSL_CERT_PATH = certPath;
|
|
process.env.SSL_KEY_PATH = keyPath;
|
|
persistSSLEnv(sslPort, certPath, keyPath);
|
|
|
|
authLogger.info("nginx reloaded with HTTPS enabled", {
|
|
operation: "nginx_ssl_reload",
|
|
sslPort,
|
|
});
|
|
|
|
return {
|
|
applied: true,
|
|
message: `HTTPS is now active on port ${sslPort}. Make sure that port is published/mapped to this container.`,
|
|
};
|
|
} catch (err) {
|
|
authLogger.error("Failed to reload nginx with SSL config", err);
|
|
return {
|
|
applied: false,
|
|
message:
|
|
"Certificate installed, but nginx could not be reloaded automatically. Restart Termix with ENABLE_SSL=true to apply it.",
|
|
};
|
|
}
|
|
}
|