import app from './app.js'; import { Env, config as appConfig, createLogger, initDb, initialiseConfig, closeDb, UserRepository, logStartupInfo, Cache, RegexAccess, SelAccess, AnimeDatabase, ConfigStartupError, ProwlarrAddon, TemplateManager, maskSensitiveInfo, constants, SeaDexDataset, ensureConfigAccessKey, startAnalytics, stopAnalytics, TaskManager, } from '@aiostreams/core'; import { randomBytes } from 'crypto'; const logger = createLogger('server'); async function initialiseDatabase() { try { await initDb(appConfig.bootstrap.databaseUri); await initialiseConfig(); } catch (error) { if (error instanceof ConfigStartupError) throw error; logger.error('Failed to initialise database:', error); throw error; } } function registerPruneTask() { const maxDays = appConfig.tasks.pruning.maxDays; TaskManager.register({ id: 'prune-users', label: 'Prune inactive users', description: 'Deletes user configs that have not been accessed within the configured window.', category: 'users', kind: 'scheduled', intervalMs: appConfig.tasks.pruning.interval * 1000, enabled: maxDays >= 0, destructive: true, multiReplica: 'single', run: async () => { if (appConfig.tasks.pruning.maxDays < 0) return { ok: true, message: 'pruning disabled' }; const n = await UserRepository.pruneUsers( appConfig.tasks.pruning.maxDays ); return { ok: true, message: `pruned ${n} users` }; }, }); } function registerCacheTasks() { TaskManager.register({ id: 'clear-all-cache', label: 'Clear all cache', description: 'Wipes every registered cache backend. Destructive.', category: 'cache', kind: 'manual', enabled: true, destructive: true, multiReplica: 'all', run: async () => { await Cache.clearAll(); return { ok: true, message: 'cache cleared' }; }, }); TaskManager.register({ id: 'clear-expired-cache', label: 'Clear expired cache keys', description: 'Deletes expired SQL cache rows (memory/redis self-expire).', category: 'cache', kind: 'manual', enabled: true, destructive: false, multiReplica: 'single', run: async () => { const n = await Cache.clearExpired(); return { ok: true, message: `removed ${n} expired rows` }; }, }); } async function initialiseRedis() { if (appConfig.bootstrap.redisUri) { await Cache.testRedisConnection(); } } async function initialiseAnimeDatabase() { try { await AnimeDatabase.getInstance().initialise(); } catch (error) { logger.error('Failed to initialise AnimeDatabase:', error); } } async function initialiseSeaDexDataset() { try { await SeaDexDataset.getInstance().initialise(); } catch {} } async function initialiseProwlarr() { try { await ProwlarrAddon.fetchpreconfiguredIndexers(); } catch (error) { logger.error('Failed to initialise Prowlarr:', error); } } async function initialiseTemplates() { try { await TemplateManager.loadTemplates(); } catch (error) { logger.error('Failed to initialise templates:', error); } } async function initialiseAuth() { await ensureConfigAccessKey(); if (appConfig.nzbProxy.publicEnabled) { appConfig.bootstrap.auth.set( constants.PUBLIC_NZB_PROXY_USERNAME, appConfig.bootstrap.auth.get(constants.PUBLIC_NZB_PROXY_USERNAME) || randomBytes(32).toString('hex') ); logger.info('AIOStreams Public NZB Proxy is enabled.', { username: constants.PUBLIC_NZB_PROXY_USERNAME, password: maskSensitiveInfo( appConfig.bootstrap.auth.get(constants.PUBLIC_NZB_PROXY_USERNAME) || '' ), }); } } async function start() { try { await initialiseDatabase(); await initialiseTemplates(); logStartupInfo(); await initialiseRedis(); initialiseAnimeDatabase(); initialiseSeaDexDataset(); RegexAccess.initialise(); SelAccess.initialise(); await initialiseProwlarr(); registerPruneTask(); registerCacheTasks(); await initialiseAuth(); startAnalytics(); const server = app.listen(appConfig.bootstrap.port, (error) => { if (error) { logger.error('Failed to start server:', error); process.exit(1); } logger.info( `Server running on port ${appConfig.bootstrap.port}: ${JSON.stringify(server.address())}` ); }); } catch (error) { if (error instanceof ConfigStartupError) throw error; logger.error('Failed to start server:', error); process.exit(1); } } async function shutdown() { TaskManager.stopAll(); await stopAnalytics().catch(() => undefined); await Cache.close(); RegexAccess.cleanup(); SelAccess.cleanup(); await closeDb(); } process.on('SIGTERM', async () => { logger.info('SIGTERM received. Shutting down gracefully...'); await shutdown(); process.exit(0); }); process.on('SIGINT', async () => { logger.info('SIGINT received. Shutting down gracefully...'); await shutdown(); process.exit(0); }); start().catch((error) => { if (error instanceof ConfigStartupError) { // The message is already a pre-formatted human-friendly banner — print // it verbatim and exit 1 without dumping a node stack trace. process.stderr.write(`${error.message}\n`); process.exit(1); } logger.error('Failed to start server:', error); process.exit(1); });