aboutsummaryrefslogtreecommitdiffstats
path: root/gateway/src/config.js
blob: 33cad84706a2e3d061784b1031c66e9f95be8d88 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Loads the same config.toml + .env the Flask dashboard uses (see config.py).
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { parse as parseToml } from "smol-toml";

export const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");

// [section, key, ENV_VAR, type]. Keep in sync with ENV_OVERRIDES in config.py.
const ENV_OVERRIDES = [
  ["web", "host", "WEB_HOST", "string"],
  ["web", "port", "WEB_PORT", "int"],
  ["web", "dashboard_port", "DASHBOARD_PORT", "int"],
  ["server", "host", "TS_HOST", "string"],
  ["server", "webquery_port", "TS_WEBQUERY_PORT", "int"],
  ["server", "server_id", "TS_SERVER_ID", "string"],
  ["server", "voice_port", "TS_VOICE_PORT", "int"],
  ["server", "voice_port", "TS_PORT", "int"], // legacy name from the old .env.template
  ["server", "ts_domain", "TS_DOMAIN", "string"],
  ["voice", "enabled", "VOICE_ENABLED", "bool"],
  ["voice", "trust_proxy", "VOICE_TRUST_PROXY", "bool"],
];

function envValue(raw, kind) {
  const value = raw.trim();
  if (kind === "bool") return ["1", "true", "yes", "on"].includes(value.toLowerCase());
  if (kind === "int") {
    if (!/^\d+$/.test(value)) throw new Error("not an integer");
    return Number(value);
  }
  return value;
}

function toInt(value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER) {
  const n = Number(value);
  return Number.isInteger(n) && n >= min && n <= max ? n : fallback;
}

export function loadConfig(env = process.env) {
  const envFile = resolve(ROOT_DIR, ".env");
  // Like python-dotenv's load_dotenv(): existing variables are not overridden.
  if (env === process.env && existsSync(envFile)) process.loadEnvFile(envFile);

  const configFile = env.TS6_CONFIG_FILE || resolve(ROOT_DIR, "config.toml");
  let raw = {};
  if (existsSync(configFile)) raw = parseToml(readFileSync(configFile, "utf8"));
  else console.warn(`[config] ${configFile} not found, using defaults`);

  const section = (name) => ({ ...(raw[name] ?? {}) });
  const web = section("web");
  const server = section("server");
  const voice = section("voice");

  const sections = { web, server, voice };
  const applied = new Set();
  for (const [name, key, envName, kind] of ENV_OVERRIDES) {
    const value = env[envName];
    const id = `${name}.${key}`;
    if (value === undefined || value.trim() === "" || applied.has(id)) continue;
    try {
      sections[name][key] = envValue(value, kind);
      applied.add(id);
    } catch {
      console.warn(`[config] ignoring invalid ${envName}=${JSON.stringify(value)}`);
    }
  }

  const allowedOrigins = Array.isArray(voice.allowed_origins)
    ? voice.allowed_origins.filter((o) => typeof o === "string" && o).map((o) => o.replace(/\/+$/, "").toLowerCase())
    : [];

  return {
    web: {
      host: String(web.host ?? "0.0.0.0"),
      port: toInt(web.port, 5000, 1, 65535),
      dashboardPort: toInt(web.dashboard_port, 5001, 1, 65535),
    },
    server: {
      host: String(server.host ?? "").trim(),
      voicePort: toInt(server.voice_port, 9987, 1, 65535),
      // Secret: only ever read from the environment / .env.
      password: env.TS_SERVER_PASSWORD ?? "",
    },
    voice: {
      enabled: voice.enabled !== false,
      maxSessions: toInt(voice.max_sessions, 8, 1, 1000),
      maxSessionsPerIp: toInt(voice.max_sessions_per_ip, 2, 1, 1000),
      maxFailedLogins: toInt(voice.max_failed_logins, 5, 1, 1000),
      lockoutMs: toInt(voice.lockout_minutes, 10, 1, 24 * 60) * 60_000,
      nicknamePrefix: String(voice.nickname_prefix ?? ""),
      defaultChannel: String(voice.default_channel ?? ""),
      identitySecurityLevel: toInt(voice.identity_security_level, 8, 0, 40),
      trustProxy: voice.trust_proxy === true,
      allowedOrigins,
    },
  };
}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage