Skip to content

Configuration

Every setting the api and workers read, all from environment variables (the host .env). The server validates them once at boot and refuses to start with a clear message if one is missing or malformed — apps/server/src/config.ts is the single reader and .env.example mirrors it.

Booleans accept true / false / 1 / 0; an empty value means "use the default". Lists are comma-separated.

Core

VariableDefaultDescription
NODE_ENVdevelopmentdevelopment, production or test. In production the console's index.html is cached in memory.
PORT8097Port the api listens on.
HOST127.0.0.1Bind address. Keep loopback behind host nginx.
PUBLIC_URLhttp://localhost:5173Public origin of the console and API. Used for the Google redirect URI and invite links.
APP_SECRETrequired≥ 32 characters (openssl rand -hex 32). Root key for encrypting TOTP and signing secrets at rest and signing short-lived tickets. Rotating it signs everyone out and invalidates stored two-factor and callback signing secrets.

Database & Redis

VariableDefaultDescription
DATABASE_URLrequiredPostgres connection string, e.g. postgres://developer_utils:…@127.0.0.1:5432/developer_utils.
REDIS_URLredis://127.0.0.1:6379/5Redis for queues, rate limits and login throttles. Use a dedicated DB index.
QUEUE_PREFIXduPrefix for every BullMQ queue and Redis key, so queues can't collide with another stack's.

Console sign-in

VariableDefaultDescription
PASSWORD_LOGINtrueAllow email + password accounts (and password invite acceptance).
GOOGLE_CLIENT_IDGoogle OAuth client id. Google sign-in is on only when this and the secret are set.
GOOGLE_CLIENT_SECRETGoogle OAuth client secret.
GOOGLE_ALLOWED_DOMAINS(empty)Google Workspace / email domains allowed to sign in with Google (e.g. finnoto.com). Invited people can use Google with any domain matching their invite email.
GOOGLE_AUTO_PROVISIONtrueCreate a member account on first Google sign-in from an allowed domain. false makes Google sign-in invite-only.
SESSION_TTL_DAYS14Console session lifetime; extended as you use the console.
COOKIE_SECUREtrueMark the session cookie Secure. Only turn off for plain-http local development.
TRUST_PROXYtrueRead the client IP from CF-Connecting-IP / X-Forwarded-For / X-Real-IP (correct behind Cloudflare + nginx).

Execution

VariableDefaultDescription
RUNNERlambdalambda for real deployments; mock simulates every utility (with their exact response shapes) for local development and tests.
MOCK_LATENCY_FACTOR1Scales the mock runner's simulated latency (tests use 0.02).
LAMBDA_TRANSPORTinvokeinvoke calls the functions directly via the AWS SDK (up to 15 min). http calls their HTTP endpoints instead (capped at 29 s).
AWS_REGIONap-south-1Region of the utility functions (and of the Secrets Manager secret, if used).
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEYThe gateway's cloud credentials. Read by the AWS SDK's standard credential chain, so an instance role or profile works too.
SYNC_TIMEOUT_MS90000How long a sync request waits before answering 202 with the job id (the job keeps running). Keep it under Cloudflare's 100 s edge limit.
WORKER_CONCURRENCY8Async jobs each worker replica runs at once.
JOB_MAX_ATTEMPTS31–5. Attempts per job for transient failures (throttling, 502/503, network). A utility's own 4xx/5xx is never retried.

Per-utility wiring

Each utility reads UTILITY_<SLUG>_*, where <SLUG> is the slug upper-cased with -_ (html-pdfUTILITY_HTML_PDF_…).

SuffixDescription
_ENABLEDfalse hides the utility from /v1 and the catalog (404). Default: enabled.
_FUNCTIONFunction name to invoke. Defaults below.
_URLThe function's HTTP endpoint — used only with LAMBDA_TRANSPORT=http.
_JWT_SECRETThe signing key the function validates its token against (raw value).
_JWT_SECRET_NAMEOr: the Secrets Manager secret holding that key (fetched once and cached).
UtilitySlugVariable prefixDefault _FUNCTION
HTML → PDFhtml-pdfUTILITY_HTML_PDF_HtmlToPdfFunction
Split PDFsplit-pdfUTILITY_SPLIT_PDF_SplitPdfFunction
Zip fileszip-filesUTILITY_ZIP_FILES_ZipFilesFunction
Unzip archiveunzip-filesUTILITY_UNZIP_FILES_UnzipFilesFunction
XLS → XLSXxls-to-xlsxUTILITY_XLS_TO_XLSX_xls-to-xlsx-converter

If neither _JWT_SECRET nor _JWT_SECRET_NAME is set for a utility, its jobs fail with upstream_error naming the variable to set.

Storage

VariableDefaultDescription
STORAGE_MODEr2r2 copies every output to R2 and returns R2 links. passthrough returns the functions' own S3 links unchanged. With r2 and the real runner, the three R2_ENDPOINT/R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEY variables are required.
SOURCE_S3_BUCKETfinnoto-dataThe bucket the functions write their outputs to.
SOURCE_S3_REGIONap-south-1Its region.
SOURCE_S3_ENDPOINTOnly for a local S3-compatible store (MinIO) in development.
SOURCE_S3_ACCESS_KEY_ID / SOURCE_S3_SECRET_ACCESS_KEYExplicit credentials for the source bucket; otherwise the AWS credential chain is used.
SOURCE_DELETE_AFTER_COPYfalseDelete the function's S3 copy once it's on R2 (needs s3:DeleteObject).
R2_ENDPOINThttps://<account id>.r2.cloudflarestorage.com.
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEYAn R2 API token with Object Read & Write on the bucket.
R2_BUCKETdeveloper-utilsThe R2 bucket.
R2_PUBLIC_BASE_URLThe bucket's public custom domain (e.g. https://files.servicelabs.dev). Unset → links are presigned.
R2_PRESIGN_TTL_SECONDS604800Lifetime of presigned links (max 7 days). Links within a day of expiry are re-signed whenever the job is read.
R2_KEY_PREFIX(empty)Optional prefix for every object key (e.g. prod/).

Email

Invites, password resets, security notices (password changed, 2FA on/off, new API key), key-expiry reminders and the daily digest are sent through Emithook's /v1/emails.

VariableDefaultDescription
EMITHOOK_API_KEY(unset)Emithook API key allowed to send email. Unset → emails are only logged (dev/test), and "Forgot password?" is hidden.
EMITHOOK_API_URLhttps://api.emithook.comEmithook API base URL.
EMAIL_FROMServiceLabs <[email protected]>From address. Its domain must be one of the Emithook org's verified sending identities (the platform identity, or a custom domain enabled for outbound sending), or Emithook rejects the send.
EMAIL_REPLY_TO(unset)Optional Reply-To.
DIGEST_TZAsia/KolkataTimezone of the daily digest: it covers the previous calendar day there.
DIGEST_HOUR9Local hour (0–23) the digest is sent; key-expiry reminders go out 15 minutes later.

Callbacks & limits

VariableDefaultDescription
CALLBACK_ALLOW_HTTPfalseAccept http:// callback URLs (development only).
CALLBACK_ALLOW_PRIVATE_NETWORKSfalseAllow callbacks to private / loopback / link-local addresses (development only — in production this would let callers reach the host's internal services).
CALLBACK_TIMEOUT_MS10000Per-attempt timeout for a callback delivery.
DEFAULT_RATE_LIMIT_PER_MIN600Rate limit given to new API keys (and to every key a guest creates). Admins can change it per key.
JOB_RETENTION_DAYS90Finished jobs (inputs, results, timelines) older than this are deleted daily. Usage rollups are kept.

Logging

VariableDefaultDescription
LOG_LEVELinfodebug, info, warn or error. Logs are JSON lines on stdout/stderr — one object per line, jq-friendly.

A minimal production .env

ini
NODE_ENV=production
PUBLIC_URL=https://app.servicelabs.dev
APP_SECRET=<openssl rand -hex 32>
DATABASE_URL=postgres://developer_utils:<password>@127.0.0.1:5432/developer_utils
REDIS_URL=redis://:<password>@127.0.0.1:6379/5

GOOGLE_CLIENT_ID=<…>
GOOGLE_CLIENT_SECRET=<…>
GOOGLE_ALLOWED_DOMAINS=finnoto.com

AWS_ACCESS_KEY_ID=<gateway user>
AWS_SECRET_ACCESS_KEY=<…>
UTILITY_HTML_PDF_JWT_SECRET=<…>
UTILITY_ZIP_FILES_JWT_SECRET=<…>
UTILITY_UNZIP_FILES_JWT_SECRET=<…>
UTILITY_SPLIT_PDF_JWT_SECRET_NAME=<secrets manager name>
UTILITY_XLS_TO_XLSX_JWT_SECRET=<…>

R2_ENDPOINT=https://<account id>.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=<…>
R2_SECRET_ACCESS_KEY=<…>
R2_BUCKET=developer-utils
R2_PUBLIC_BASE_URL=https://files.servicelabs.dev

ServiceLabs · a Finnoto company