import { readFileSync } from 'node:fs'; function text(name, fallback) { const value = process.env[name]; return value === undefined || value === '' ? fallback : value; } function secret(name, fileName) { const direct = process.env[name]; if (direct) { return direct; } const path = process.env[fileName]; if (!path) { throw new Error(`${name} or ${fileName} is required`); } return readFileSync(path, 'utf8').trim(); } function clientSecrets(variableName) { const entries = text(variableName, '') .split(',') .map((entry) => entry.trim()) .filter(Boolean); if (entries.length === 0) { throw new Error(`${variableName} is required`); } const result = Object.create(null); for (const entry of entries) { const separator = entry.indexOf('='); const client = entry.slice(0, separator); const path = entry.slice(separator + 1); if (separator < 1 || !/^[a-z0-9][a-z0-9_-]{1,31}$/.test(client) || !path) { throw new Error(`Invalid ${variableName} client entry: ${entry}`); } const value = readFileSync(path, 'utf8').trim(); if (value.length < 32) { throw new Error(`${variableName} secret for ${client} must be at least 32 characters`); } if (result[client]) { throw new Error(`Duplicate ${variableName} client: ${client}`); } result[client] = value; } return result; } export function loadConfig() { const webhookSecret = secret('GITEA_WEBHOOK_SECRET', 'GITEA_WEBHOOK_SECRET_FILE'); const adminSecret = secret('IMAGE_ADMIN_SECRET', 'IMAGE_ADMIN_SECRET_FILE'); if (webhookSecret.length < 32 || adminSecret.length < 32) { throw new Error('Webhook and admin secrets must be at least 32 characters'); } const allowedBranches = text('IMAGE_ALLOWED_BRANCHES', 'master') .split(',') .map((branch) => branch.trim()) .filter(Boolean); return { port: Number(text('PORT', '8081')), repositoryPath: text('IMAGE_REPOSITORY_PATH', '/data/image'), remoteUrl: text('IMAGE_REMOTE_URL', 'https://gitea.hided.net/devsam/image.git'), repositoryFullName: text('IMAGE_REPOSITORY_FULL_NAME', 'devsam/image'), defaultBranch: text('IMAGE_DEFAULT_BRANCH', 'master'), allowedBranches, statePath: text('IMAGE_STATE_PATH', '/var/lib/image-hook/state.json'), publicBases: text('IMAGE_PUBLIC_BASES', 'https://sam.hided.net/image,https://sam-image.hided.net') .split(',') .map((base) => base.trim().replace(/\/$/, '')) .filter(Boolean), webhookSecret, adminSecret, syncClientSecrets: clientSecrets('IMAGE_SYNC_CLIENT_SECRET_FILES'), uploadClientSecrets: clientSecrets('IMAGE_UPLOAD_CLIENT_SECRET_FILES'), maxBodyBytes: Number(text('MAX_BODY_BYTES', '1048576')), maxUploadBytes: Number(text('MAX_UPLOAD_BYTES', '51200')), uploadRoot: text('IMAGE_UPLOAD_ROOT', '/var/lib/image-hook/uploads'), uploadStatePath: text('IMAGE_UPLOAD_STATE_PATH', '/var/lib/image-hook/upload-state.json'), }; }