Initial hop. implementation and UGREEN NAS deployment guide
This commit is contained in:
106
apps/server/src/config.ts
Normal file
106
apps/server/src/config.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user