aiostreams / packages /server /src /server.ts
f4b404's picture
Upload folder using huggingface_hub
f55c62a verified
Raw
History Blame Contribute Delete
5.47 kB
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);
});