config.ts
| 1 | import path from "node:path"; |
| 2 | |
| 3 | const 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 | */ |
| 13 | function 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 | |
| 23 | const PORT = intEnv(env.PORT, 3000, 1); |
| 24 | |
| 25 | const 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 |
| 80 | const baseUrl = new URL(config.BASE_URL); |
| 81 | config.PUBLIC_HTTPS = baseUrl.protocol === "https:"; |
| 82 | config.PUBLIC_ORIGIN = baseUrl.origin; |
| 83 | config.COMMITTER_EMAIL = |
| 84 | env.COMMITTER_EMAIL ?? `${config.OWNER_DISPLAY_NAME}@${baseUrl.hostname}`; |
| 85 | |
| 86 | export default config; |
| 87 |