feat(ClamAvService): Add ClamAV Manager with Docker container management capabilities.

This commit is contained in:
2025-02-03 13:34:52 +01:00
parent f71219f0ca
commit a19638b476
9 changed files with 495 additions and 36 deletions

View File

@ -0,0 +1,90 @@
import { ClamAVManager } from '../../ts/classes.clamav.manager.js';
import { execAsync } from '../../ts/plugins.js';
let clamManager: ClamAVManager | null = null;
let isCleaningUp = false;
export async function getManager(): Promise<ClamAVManager> {
if (!clamManager) {
throw new Error('ClamAV manager not initialized');
}
return clamManager;
}
export async function setupClamAV(): Promise<ClamAVManager> {
console.log('[Helper] Setting up ClamAV...');
// First cleanup any existing containers
await forceCleanupContainer();
if (!clamManager) {
console.log('[Helper] Creating new ClamAV manager instance');
clamManager = new ClamAVManager();
await clamManager.startContainer();
console.log('[Helper] ClamAV manager initialized');
} else {
console.log('[Helper] Using existing ClamAV manager instance');
}
return clamManager;
}
export async function cleanupClamAV(): Promise<void> {
if (isCleaningUp) {
console.log('[Helper] Cleanup already in progress, skipping');
return;
}
isCleaningUp = true;
console.log('[Helper] Cleaning up ClamAV...');
try {
if (clamManager) {
await clamManager.stopContainer();
console.log('[Helper] ClamAV container stopped');
}
await forceCleanupContainer();
} catch (error) {
console.error('[Helper] Error during cleanup:', error);
throw error;
} finally {
clamManager = null;
isCleaningUp = false;
}
}
async function forceCleanupContainer(): Promise<void> {
try {
// Stop any existing container
await execAsync('docker stop clamav-daemon').catch(() => {});
// Remove any existing container
await execAsync('docker rm -f clamav-daemon').catch(() => {});
console.log('[Helper] Forced cleanup of existing containers complete');
} catch (error) {
// Ignore errors as the container might not exist
}
}
// Handle interrupts
process.on('SIGINT', async () => {
console.log('\n[Helper] Received SIGINT. Cleaning up...');
try {
await cleanupClamAV();
process.exit(0);
} catch (err) {
console.error('[Helper] Error during cleanup:', err);
process.exit(1);
}
});
// Ensure cleanup on process exit
process.on('exit', () => {
if (clamManager && !isCleaningUp) {
console.log('[Helper] Process exit detected, attempting cleanup');
// We can't use async functions in exit handler, so we do our best
try {
execAsync('docker stop clamav-daemon').catch(() => {});
execAsync('docker rm -f clamav-daemon').catch(() => {});
} catch {}
}
});

View File

@ -0,0 +1,55 @@
import { expect, tap } from '../ts/plugins.js';
import { type ClamAVLogEvent, ClamAVManager } from '../ts/classes.clamav.manager.js';
import { setupClamAV, cleanupClamAV, getManager } from './helpers/clamav.helper.js';
let manager: ClamAVManager;
tap.test('setup', async () => {
manager = await setupClamAV();
expect(manager).toBeTruthy();
});
tap.test('should have initialized container and receive logs', async () => {
let logReceived = false;
// Add event listener for logs
manager.on('log', (event: ClamAVLogEvent) => {
console.log(`[Test] Received log event: ${event.type} - ${event.message}`);
logReceived = true;
});
// Wait for logs
const maxWaitTime = 5000;
const startTime = Date.now();
while (!logReceived && Date.now() - startTime < maxWaitTime) {
await new Promise(resolve => setTimeout(resolve, 100));
}
expect(logReceived).toBeTruthy('No logs received within timeout period');
// Verify container is running by checking if we can get database info
try {
const dbInfo = await manager.getDatabaseInfo();
expect(dbInfo).toBeTruthy('Container should be running and able to get database info');
} catch (error) {
console.error('Error getting database info:', error);
expect.fail('Failed to get database info - container may not be fully initialized');
}
});
tap.test('should get database info', async () => {
const dbInfo = await manager.getDatabaseInfo();
console.log('Database Info:', dbInfo);
expect(dbInfo).toBeTruthy();
});
tap.test('should update database', async () => {
await manager.updateDatabase();
});
tap.test('cleanup', async () => {
await cleanupClamAV();
});
tap.start();

View File

@ -1,35 +1,40 @@
import { expect, expectAsync, tap } from '@push.rocks/tapbundle';
import { expect, tap } from '../ts/plugins.js';
import * as smartantivirus from '../ts/index.js';
import { setupClamAV, cleanupClamAV } from './helpers/clamav.helper.js';
const EICAR_TEST_STRING = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*';
let clamService: smartantivirus.ClamAvService;
tap.test('should create a ClamAvService instance', async () => {
clamService = new smartantivirus.ClamAvService();
expect(clamService).toBeDefined();
tap.test('setup', async () => {
await setupClamAV();
});
tap.test('should scan a string', async () => {
const scanResult = await clamService.scanString('X5O!P%@AP[4\PZX54(P^)7CC)7}' + '$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*');
tap.test('should create a ClamAvService instance and initialize ClamAV', async () => {
clamService = new smartantivirus.ClamAvService();
expect(clamService).toBeTruthy();
// The manager will start the container and wait for initialization
await clamService.verifyConnection();
});
tap.test('should detect EICAR test string', async () => {
const scanResult = await clamService.scanString(EICAR_TEST_STRING);
console.log('Scan Result:', scanResult);
// expect(scanResult).toEqual({ isInfected: true, reason: 'FOUND' });
expect(scanResult.isInfected).toEqual(true);
expect(scanResult.reason).toBeTruthy();
});
tap.test('should not detect clean string', async () => {
const scanResult = await clamService.scanString('This is a clean string with no virus signature');
console.log('Clean Scan Result:', scanResult);
expect(scanResult.isInfected).toEqual(false);
expect(scanResult.reason).toBeUndefined();
});
tap.test('cleanup', async () => {
await cleanupClamAV();
});
tap.start();
/* (async () => {
try {
await clamService.updateVirusDefinitions(); // Step 2: Update definitions
await clamService.startClamDaemon(); // Step 3: Start daemon
const scanResult = await clamService.scanString('EICAR test string...');
console.log('Scan Result:', scanResult);
} catch (error) {
console.error('Error:', error);
}
})(); */