import { randomUUID } from 'node:crypto'; import { resolve } from 'node:path'; import { defaultImportResourcePolicy } from '../src/importers/resource-policy'; import { createAssessorHttpServer } from './assessor-http-server'; import { NodeImportWorkerExecutor } from './import-worker-client'; import { createRepositoryRuntime } from './repository-factory'; import { assertArtifactStoreOutsidePublicRoot, FileSystemBackendArtifactStore } from './artifacts/backend-artifact-store'; import { GpoBackupSubmissionService } from './gpo/gpo-backup-submission-service'; import { GpoBackupJobProcessor, GpoBackupJobRunner } from './gpo/gpo-backup-job-processor'; import { GpoArtifactReconciler, GpoArtifactReconciliationRunner } from './gpo/gpo-artifact-reconciler'; import { GpoBackupManagementService } from './gpo/gpo-backup-management-service'; import { NodeGpoInventoryWorkerExecutor } from './gpo/gpo-inventory-worker-client'; import { MicrosoftSecurityBaselineService } from './gpo/microsoft-security-baseline-service'; import { GpoPolicySnapshotService } from './gpo/gpo-policy-snapshot-service'; import { GpoPolicyEvaluationService } from './gpo/gpo-policy-evaluation-service'; import { GpoPolicyFindingService } from './gpo/gpo-policy-finding-service'; import { GpoPolicyAnalysisOverviewService } from './gpo/gpo-policy-analysis-overview-service'; function boundedInteger(value: string | undefined, fallback: number, minimum: number, maximum: number, name: string): number { if (value === undefined || value.trim() === '') return fallback; const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) { throw new Error(`${name} muss eine Ganzzahl zwischen ${minimum} und ${maximum} sein.`); } return parsed; } async function main(): Promise { const host = process.env.ASSESSOR_HOST?.trim() || '127.0.0.1'; const parsedPort = Number(process.env.ASSESSOR_PORT ?? 4173); if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) { throw new Error('ASSESSOR_PORT muss eine Ganzzahl zwischen 1 und 65535 sein.'); } const importExecutor = new NodeImportWorkerExecutor({ workerUrl: new URL('./import-worker.mjs', import.meta.url), timeoutMs: boundedInteger(process.env.ASSESSOR_PARSER_TIMEOUT_MS, 30_000, 1_000, 300_000, 'ASSESSOR_PARSER_TIMEOUT_MS'), maximumConcurrent: boundedInteger(process.env.ASSESSOR_PARSER_MAX_CONCURRENT, 2, 1, 8, 'ASSESSOR_PARSER_MAX_CONCURRENT'), maximumQueued: boundedInteger(process.env.ASSESSOR_PARSER_MAX_QUEUE, 8, 0, 100, 'ASSESSOR_PARSER_MAX_QUEUE'), maximumQueuedBytes: boundedInteger(process.env.ASSESSOR_PARSER_MAX_QUEUE_MB, 256, 16, 2_048, 'ASSESSOR_PARSER_MAX_QUEUE_MB') * 1024 * 1024, maximumOldGenerationMb: boundedInteger(process.env.ASSESSOR_PARSER_MEMORY_MB, 512, 128, 2_048, 'ASSESSOR_PARSER_MEMORY_MB') }); const staticRoot = resolve(process.cwd(), 'dist'); const artifactRoot = resolve(process.env.ASSESSOR_ARTIFACT_ROOT?.trim() || resolve(process.cwd(), '.assessor-data', 'artifacts')); assertArtifactStoreOutsidePublicRoot(artifactRoot, staticRoot); const gpoInventoryExecutor = new NodeGpoInventoryWorkerExecutor({ workerUrl: new URL('./gpo-inventory-worker.mjs', import.meta.url), timeoutMs: boundedInteger(process.env.ASSESSOR_GPO_TIMEOUT_MS, 30_000, 1_000, 300_000, 'ASSESSOR_GPO_TIMEOUT_MS'), maximumConcurrent: boundedInteger(process.env.ASSESSOR_GPO_MAX_CONCURRENT, 1, 1, 4, 'ASSESSOR_GPO_MAX_CONCURRENT'), maximumSourceBytes: defaultImportResourcePolicy.maximumFileBytes, maximumOldGenerationMb: boundedInteger(process.env.ASSESSOR_GPO_MEMORY_MB, 1_024, 256, 2_048, 'ASSESSOR_GPO_MEMORY_MB'), maximumYoungGenerationMb: boundedInteger(process.env.ASSESSOR_GPO_YOUNG_MEMORY_MB, 128, 64, 512, 'ASSESSOR_GPO_YOUNG_MEMORY_MB') }); const runtime = await createRepositoryRuntime(); const artifactStore = new FileSystemBackendArtifactStore(artifactRoot); const gpoBackupSubmissionService = new GpoBackupSubmissionService( runtime.repository, artifactStore, gpoInventoryExecutor, defaultImportResourcePolicy.maximumFileBytes ); const gpoBackupManagementService = new GpoBackupManagementService(runtime.repository, artifactStore); const microsoftSecurityBaselineService = new MicrosoftSecurityBaselineService( runtime.repository, artifactStore, defaultImportResourcePolicy.maximumFileBytes, gpoInventoryExecutor ); const gpoPolicySnapshotService = new GpoPolicySnapshotService(runtime.repository); const gpoPolicyEvaluationService = new GpoPolicyEvaluationService(runtime.repository); const gpoPolicyFindingService = new GpoPolicyFindingService(runtime.repository); const gpoPolicyAnalysisOverviewService = new GpoPolicyAnalysisOverviewService(runtime.repository); await microsoftSecurityBaselineService.refreshDetectedMetadata(); const baselineBackfill = await microsoftSecurityBaselineService.reprocessPending(); if (baselineBackfill.failed) console.warn('Microsoft-Baseline-Backfill:', baselineBackfill.analyzed, 'analysiert,', baselineBackfill.failed, 'fehlgeschlagen.'); const gpoJobProcessor = new GpoBackupJobProcessor(runtime.repository, artifactStore, gpoInventoryExecutor, { maximumArtifactBytes: defaultImportResourcePolicy.maximumFileBytes, leaseDurationMs: boundedInteger(process.env.ASSESSOR_GPO_JOB_LEASE_MS, 60_000, 1_000, 3_600_000, 'ASSESSOR_GPO_JOB_LEASE_MS'), retryDelayMs: boundedInteger(process.env.ASSESSOR_GPO_JOB_RETRY_MS, 5_000, 0, 300_000, 'ASSESSOR_GPO_JOB_RETRY_MS') }); const gpoJobRunner = new GpoBackupJobRunner( gpoJobProcessor, `gpo-worker-${randomUUID()}`, boundedInteger(process.env.ASSESSOR_GPO_JOB_POLL_MS, 1_000, 100, 60_000, 'ASSESSOR_GPO_JOB_POLL_MS') ); const gpoArtifactReconciliationRunner = new GpoArtifactReconciliationRunner( new GpoArtifactReconciler(runtime.repository, artifactStore, { gracePeriodMs: boundedInteger(process.env.ASSESSOR_ARTIFACT_ORPHAN_GRACE_MS, 24 * 60 * 60 * 1_000, 60_000, 7 * 24 * 60 * 60 * 1_000, 'ASSESSOR_ARTIFACT_ORPHAN_GRACE_MS'), maximumRemovals: boundedInteger(process.env.ASSESSOR_ARTIFACT_ORPHAN_MAX_REMOVALS, 100, 1, 10_000, 'ASSESSOR_ARTIFACT_ORPHAN_MAX_REMOVALS') }), boundedInteger(process.env.ASSESSOR_ARTIFACT_RECONCILE_INTERVAL_MS, 60 * 60 * 1_000, 60_000, 24 * 60 * 60 * 1_000, 'ASSESSOR_ARTIFACT_RECONCILE_INTERVAL_MS'), error => console.error('GPO-Artefakt-Reconciliation fehlgeschlagen.', error instanceof Error ? error.message : 'Unbekannter Fehler') ); const server = createAssessorHttpServer({ repository: runtime.repository, persistence: runtime.persistence, staticRoot, releaseId: process.env.ASSESSOR_RELEASE_ID?.trim() || undefined, developmentInstanceId: process.env.ASSESSOR_DEVELOPMENT_INSTANCE_ID?.trim() || undefined, gpoBackupSubmissionService, gpoBackupManagementService, microsoftSecurityBaselineService, gpoPolicySnapshotService, gpoPolicyEvaluationService, gpoPolicyFindingService, gpoPolicyAnalysisOverviewService, importExecutor }); server.listen(parsedPort, host, () => { gpoArtifactReconciliationRunner.start(); gpoJobRunner.start(); console.log(`ASSESSOR läuft lokal unter http://${host}:${parsedPort}`); console.log(`Persistenz: ${runtime.persistence === 'postgresql' ? 'PostgreSQL' : 'Arbeitsspeicher'}`); }); let shuttingDown = false; const shutdown = (signal: string) => { if (shuttingDown) return; shuttingDown = true; console.log(`${signal}: ASSESSOR wird beendet.`); void Promise.all([gpoJobRunner.stop(), gpoArtifactReconciliationRunner.stop()]).then(() => { server.close(async error => { try { await runtime.close(); } catch (closeError) { console.error(closeError); process.exitCode = 1; } if (error) { console.error(error); process.exitCode = 1; } }); }); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); } void main().catch(error => { console.error('ASSESSOR konnte nicht gestartet werden.'); console.error(error instanceof Error ? error.message : error); process.exitCode = 1; });