107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
import { fileURLToPath } from 'node:url';
|
|
import { randomBytes } from 'node:crypto';
|
|
export type Config = {
|
|
production: boolean;
|
|
appUrl: string;
|
|
databasePath: string;
|
|
sessionSecret: string;
|
|
discoverySecret: string;
|
|
discoveryEnabled: boolean;
|
|
trustedProxies: string[];
|
|
port: number;
|
|
host: string;
|
|
guestDays: number;
|
|
requestSeconds: number;
|
|
uploadStartHours: number;
|
|
spaceMinutes: number;
|
|
spaceMax: number;
|
|
offlineGraceMs: number;
|
|
maxUrlBytes: number;
|
|
maxItems: number;
|
|
urlRetentionDays: number;
|
|
sessionRate: number;
|
|
ipRate: number;
|
|
pairRate: number;
|
|
rejectCooldownMs: number;
|
|
uploadDir: string;
|
|
maxFileBytes: number;
|
|
guestQuota: number;
|
|
totalGuestQuota: number;
|
|
minFreeBytes: number;
|
|
fileRetentionDays: number;
|
|
orphanHours: number;
|
|
undoSeconds: number;
|
|
registrationMode: 'open' | 'closed';
|
|
setupToken?: string;
|
|
mailFrom: string;
|
|
resendApiKey?: string;
|
|
userQuota: number;
|
|
emailRequestDays: number;
|
|
webRoot?: string;
|
|
};
|
|
export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {
|
|
const production = env.NODE_ENV === 'production';
|
|
const num = (key: string, fallback: number, min = 1) => {
|
|
const n = Number(env[key] ?? fallback);
|
|
if (!Number.isSafeInteger(n) || n < min) throw Error(`Invalid ${key}`);
|
|
return n;
|
|
};
|
|
const secret = (key: string) => {
|
|
const v = env[key];
|
|
if (production && (!v || v.length < 32 || v.startsWith('replace-')))
|
|
throw Error(`${key} must be a random secret (32+ characters)`);
|
|
return v && v.length >= 32 ? v : randomBytes(32).toString('base64url');
|
|
};
|
|
const port = num('PORT', 0, 0);
|
|
if (production && port === 0) throw Error('Set PORT explicitly before deployment');
|
|
const appUrl = env.APP_URL ?? 'http://127.0.0.1';
|
|
if (production && !appUrl.startsWith('https://'))
|
|
throw Error('Production APP_URL must use HTTPS');
|
|
return {
|
|
production,
|
|
appUrl,
|
|
databasePath:
|
|
env.DATABASE_PATH ?? fileURLToPath(new URL('../../../.data/hop.sqlite', import.meta.url)),
|
|
sessionSecret: secret('SESSION_SECRET'),
|
|
discoverySecret: secret('DISCOVERY_SECRET'),
|
|
discoveryEnabled:
|
|
env.SAME_NETWORK_DISCOVERY_ENABLED === undefined
|
|
? !production
|
|
: env.SAME_NETWORK_DISCOVERY_ENABLED === 'true',
|
|
trustedProxies: (env.TRUSTED_PROXY_CIDRS ?? '')
|
|
.split(',')
|
|
.map((v) => v.trim())
|
|
.filter(Boolean),
|
|
port,
|
|
host: env.HOST ?? '127.0.0.1',
|
|
guestDays: num('GUEST_SESSION_RETENTION_DAYS', 30),
|
|
requestSeconds: num('LOCAL_REQUEST_TIMEOUT_SECONDS', 60),
|
|
uploadStartHours: num('ACCEPTED_UPLOAD_START_HOURS', 24),
|
|
spaceMinutes: num('SPACE_RETENTION_MINUTES', 10),
|
|
spaceMax: num('SPACE_MAX_DEVICES', 10),
|
|
offlineGraceMs: num('OFFLINE_GRACE_MS', 3000, 0),
|
|
maxUrlBytes: num('MAX_URL_PAYLOAD_SIZE_KB', 1024) * 1024,
|
|
maxItems: num('MAX_ITEMS_PER_TRANSFER', 20),
|
|
urlRetentionDays: Math.min(7, num('DEFAULT_URL_RETENTION_DAYS', 7)),
|
|
sessionRate: num('GUEST_REQUESTS_PER_MINUTE', 5),
|
|
ipRate: num('IP_REQUESTS_PER_MINUTE', 20),
|
|
pairRate: num('PAIR_REQUESTS_PER_MINUTE', 2),
|
|
rejectCooldownMs: num('REJECT_COOLDOWN_SECONDS', 300) * 1000,
|
|
uploadDir: env.UPLOAD_DIR ?? fileURLToPath(new URL('../../../.data/uploads', import.meta.url)),
|
|
maxFileBytes: num('MAX_FILE_SIZE_MB', 2048) * 1048576,
|
|
guestQuota: num('GUEST_STORAGE_LIMIT_MB', 2048) * 1048576,
|
|
totalGuestQuota: num('TOTAL_GUEST_STORAGE_LIMIT_MB', 20480) * 1048576,
|
|
minFreeBytes: num('MIN_FREE_DISK_MB', 5120, 0) * 1048576,
|
|
fileRetentionDays: Math.min(7, num('DEFAULT_FILE_RETENTION_DAYS', 7)),
|
|
orphanHours: num('ORPHAN_UPLOAD_RETENTION_HOURS', 24),
|
|
undoSeconds: num('DELETE_UNDO_SECONDS', 5),
|
|
registrationMode: env.REGISTRATION_MODE === 'open' ? 'open' : 'closed',
|
|
setupToken: env.SETUP_TOKEN,
|
|
resendApiKey: env.RESEND_API_KEY?.trim() || undefined,
|
|
mailFrom: env.MAIL_FROM ?? 'hop. <hello@noname.company>',
|
|
userQuota: num('USER_STORAGE_LIMIT_MB', 10240) * 1048576,
|
|
emailRequestDays: num('EMAIL_REQUEST_RETENTION_DAYS', 7),
|
|
webRoot: env.WEB_ROOT,
|
|
};
|
|
}
|