config.ts
⎇
Raw
1import path from "node:path";
2
3const env = process.env;
4
5/**
6 * parseInt with a default that distinguishes "unset" from "explicit 0".
7 *
8 * `min` clamps the result. Pass it for any value where 0 is not a coherent
9 * setting — a pool of 0 workers, 0 concurrent jobs or a 0-second timeout
10 * wedges or crashes the feature rather than disabling it. Values that fail
11 * closed at 0 (byte caps) deliberately have no minimum.
12 */
13function intEnv(
14 value: string | undefined,
15 defaultValue: number,
16 min = Number.NEGATIVE_INFINITY,
17): number {
18 if (value === undefined || value === "") return defaultValue;
19 const parsed = parseInt(value, 10);
20 return Number.isFinite(parsed) ? Math.max(min, parsed) : defaultValue;
21}
22
23const PORT = intEnv(env.PORT, 3000, 1);
24
25const config = {
26 OWNER_DISPLAY_NAME: env.OWNER_DISPLAY_NAME ?? "Admin",
27 INLINE_MAX_BYTES: intEnv(env.INLINE_MAX_BYTES, 524288),
28 MAX_UPLOAD_BYTES: intEnv(env.MAX_UPLOAD_BYTES, 10 * 1024 * 1024),
29 MAX_USER_UPLOAD_BYTES: intEnv(env.MAX_USER_UPLOAD_BYTES, 2 * 1024 * 1024),
30 TRUSTED_PROXY: !!env.TRUSTED_PROXY,
31 RATE_LIMIT_DISABLED: !!env.RATE_LIMIT_DISABLED,
32 SSH_DISABLED: !!env.SSH_DISABLED,
33 SCANNED_REPO_PRIVATE:
34 env.SCANNED_REPO_PRIVATE !== "0" &&
35 env.SCANNED_REPO_PRIVATE !== "false",
36 PORT,
37 SSH_PORT: intEnv(env.SSH_PORT, 2222, 1),
38 REGISTRATION_TYPE: (env.REGISTRATION_TYPE ?? "enabled") as
39 | "enabled"
40 | "disabled"
41 | "queue",
42 REGISTER_QUESTION: env.REGISTER_QUESTION ?? "",
43 BASE_URL: env.BASE_URL ?? `http://localhost:${PORT}`,
44 // Derived from BASE_URL after the literal closes (see below). Declared
45 // here so consumers get a typed config object.
46 PUBLIC_HTTPS: false,
47 PUBLIC_ORIGIN: "",
48 DATA_DIR: path.resolve(env.DATA_DIR ?? "./data"),
49 HIGHLIGHT_WORKERS: intEnv(env.HIGHLIGHT_WORKERS, 4, 1),
50 COMMITTER_NAME: env.COMMITTER_NAME ?? env.OWNER_DISPLAY_NAME ?? "Admin",
51 COMMITTER_EMAIL: "",
52 EXTRA_ALLOWED_SIGNERS_PATH: env.EXTRA_ALLOWED_SIGNERS_PATH ?? null,
53 MAX_TITLE_BYTES: intEnv(env.MAX_TITLE_BYTES, 500),
54 MAX_TEXT_BODY_BYTES: intEnv(env.MAX_TEXT_BODY_BYTES, 100_000),
55 MAX_USERNAME_BYTES: intEnv(env.MAX_USERNAME_BYTES, 64),
56 MAX_PASSWORD_BYTES: intEnv(env.MAX_PASSWORD_BYTES, 1024),
57 CI_DOCKER_SOCKET: env.CI_DOCKER_SOCKET ?? "",
58 CI_MAX_HISTORY: intEnv(env.CI_MAX_HISTORY, 50),
59 CI_MAX_CONCURRENT: intEnv(env.CI_MAX_CONCURRENT, 2, 1),
60 CI_DEFAULT_TIMEOUT: intEnv(env.CI_DEFAULT_TIMEOUT, 3600, 1),
61 // Cap on simultaneous source archive generation jobs (one release with
62 // include_source_code spawns three git-archive + compressor pipelines
63 // back-to-back). Without this cap, an admin firing several releases in
64 // quick succession can saturate CPU. Excess jobs are queued in memory.
65 MAX_CONCURRENT_ARCHIVE_JOBS: intEnv(env.MAX_CONCURRENT_ARCHIVE_JOBS, 2, 1),
66 // Cap on any server-side render that holds the whole content in
67 // RAM and runs synchronous CPU work on it (blob view, commit/patch
68 // diff, markdown). Above this, the UI shows a "too large to
69 // preview" stub and links to the raw endpoint. Setting to 0
70 // disables inline rendering entirely.
71 MAX_RENDER_BYTES: intEnv(env.MAX_RENDER_BYTES, 10 * 1024 * 1024),
72 // Cap on the streamed /raw download. 0 means no limit (current
73 // behaviour) — useful on a trusted LAN where you actually want to
74 // pull large blobs out of the browser. Public deployments should
75 // pin this to something sane.
76 MAX_RAW_DOWNLOAD_BYTES: intEnv(env.MAX_RAW_DOWNLOAD_BYTES, 0),
77};
78
79// Derived values that depend on other config fields
80const baseUrl = new URL(config.BASE_URL);
81config.PUBLIC_HTTPS = baseUrl.protocol === "https:";
82config.PUBLIC_ORIGIN = baseUrl.origin;
83config.COMMITTER_EMAIL =
84 env.COMMITTER_EMAIL ?? `${config.OWNER_DISPLAY_NAME}@${baseUrl.hostname}`;
85
86export default config;
87