import { exec } from 'child_process' import { promisify } from 'util' const execAsync = promisify(exec) export class AutomatedCleanupService { private cleanupInterval: NodeJS.Timeout | null = null private isRunning = false start(intervalMs: number = 60000) { // Default: every minute if (this.isRunning) { console.log('โš ๏ธ Cleanup service already running') return } this.isRunning = true console.log(`๐Ÿš€ Starting automated cleanup service (interval: ${intervalMs}ms)`) this.cleanupInterval = setInterval(async () => { await this.performCleanup() }, intervalMs) // Run initial cleanup this.performCleanup().catch(console.error) } stop() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval) this.cleanupInterval = null } this.isRunning = false console.log('๐Ÿ›‘ Automated cleanup service stopped') } private async performCleanup(): Promise { try { console.log('๐Ÿงน Running periodic browser cleanup...') // Check for chromium processes const { stdout } = await execAsync('ps aux | grep -E "(chromium|chrome)" | grep -v grep | wc -l') const processCount = parseInt(stdout.trim(), 10) if (processCount > 0) { console.log(`๐Ÿ” Found ${processCount} browser processes running`) // Get process list for logging try { const { stdout: processList } = await execAsync('ps aux | grep -E "(chromium|chrome)" | grep -v grep | head -10') console.log('๐Ÿ“‹ Current browser processes:') console.log(processList) } catch (listError) { console.log('Could not list processes:', listError) } // Kill old/stuck processes (but exclude our own Node.js process) const currentPid = process.pid const killCommands = [ // Graceful shutdown first (exclude our process and children) `pkill -TERM -f "chromium.*--remote-debugging-port" | grep -v ${currentPid} 2>/dev/null || true`, `pkill -TERM -f "chromium.*--user-data-dir" | grep -v ${currentPid} 2>/dev/null || true`, // Wait a bit 'sleep 2', // Force kill stubborn processes (exclude our process and children) `pkill -KILL -f "chromium.*--remote-debugging-port" | grep -v ${currentPid} 2>/dev/null || true`, `pkill -KILL -f "chromium.*--user-data-dir" | grep -v ${currentPid} 2>/dev/null || true`, `pkill -KILL -f "/usr/lib/chromium/chromium" | grep -v ${currentPid} 2>/dev/null || true`, // Clean up zombies 'pkill -9 -f "chromium.*defunct" 2>/dev/null || true' ] for (const command of killCommands) { try { if (command === 'sleep 2') { await new Promise(resolve => setTimeout(resolve, 2000)) } else { await execAsync(command) } } catch (error) { // Ignore errors from kill commands } } // Check results const { stdout: afterCleanup } = await execAsync('ps aux | grep -E "(chromium|chrome)" | grep -v grep | wc -l') const remainingProcesses = parseInt(afterCleanup.trim(), 10) if (remainingProcesses < processCount) { console.log(`โœ… Cleanup successful: ${processCount - remainingProcesses} processes terminated`) } else { console.log(`โš ๏ธ No processes were terminated, ${remainingProcesses} still running`) } // Clean up temp files try { await execAsync('rm -rf /tmp/.org.chromium.Chromium.* 2>/dev/null || true') await execAsync('rm -rf /tmp/puppeteer_dev_chrome_profile-* 2>/dev/null || true') console.log('๐Ÿ—‘๏ธ Cleaned up temporary files') } catch (tempCleanupError) { console.log('โš ๏ธ Could not clean temp files:', tempCleanupError) } } else { console.log('โœ… No browser processes found - system clean') } } catch (error) { console.error('โŒ Error during periodic cleanup:', error) } } // Force cleanup method for immediate use async forceCleanup(): Promise { console.log('๐Ÿ”ง Running force cleanup...') await this.performCleanup() } } // Create singleton instance export const automatedCleanupService = new AutomatedCleanupService() // Auto-start in Docker environment (unless disabled) if (process.env.DOCKER_ENV === 'true' && process.env.DISABLE_AUTO_CLEANUP !== 'true') { console.log('๐Ÿณ Docker environment detected - starting automated cleanup service') automatedCleanupService.start(30000) // Every 30 seconds in Docker } else if (process.env.DOCKER_ENV === 'true' && process.env.DISABLE_AUTO_CLEANUP === 'true') { console.log('๐Ÿณ Docker environment detected but auto cleanup is disabled via DISABLE_AUTO_CLEANUP=true') }