const fs = require('fs').promises; const path = require('path'); const { models } = require('../db/db'); const { Mailbox, Alias } = models; const { format, isBefore } = require('date-fns'); async function scanMailboxDirectories(callback) { const mailPath = '/var/mail'; // Verify mail directory exists try { await fs.access(mailPath); } catch (err) { console.error(`Mail directory ${mailPath} does not exist or is not accessible`); return; } const domains = await fs.readdir(mailPath); console.log(`Found ${domains.length} domains in ${mailPath}`); for (const domain of domains) { const domainPath = path.join(mailPath, domain); const stat = await fs.stat(domainPath); if (stat.isDirectory()) { const users = await fs.readdir(domainPath); console.log(`Processing domain ${domain} with ${users.length} users`); for (const user of users) { const userPath = path.join(domainPath, user); const userStat = await fs.stat(userPath); if (userStat.isDirectory()) { await callback({ user, domain, userPath }); } } } } } async function cleanupOrphanedMailboxes() { try { // Get all valid mailboxes from database - include ignore flag const validMailboxes = await Mailbox.query() .select('username', 'ignore'); // Add ignore to selection // Create a lookup map for quick checking const validMailboxMap = {}; validMailboxes.forEach(mailbox => { const [user, domain] = mailbox.username.split('@'); if (!validMailboxMap[domain]) validMailboxMap[domain] = new Map(); validMailboxMap[domain].set(user, mailbox.ignore); // Store ignore status }); await scanMailboxDirectories(async ({ user, domain, userPath }) => { // Check if mailbox exists and is not ignored const hasMailbox = validMailboxMap[domain] && validMailboxMap[domain].has(user); const isIgnored = hasMailbox && validMailboxMap[domain].get(user) === 1; // Only remove if mailbox doesn't exist or is not ignored if (!hasMailbox || !isIgnored) { console.log(`Checking mailbox ${user}@${domain} - exists: ${hasMailbox}, ignored: ${isIgnored}`); console.log(`Removing orphaned mailbox: ${user}@${domain}`); await fs.rm(userPath, { recursive: true, force: true }); } }); console.log('Mailbox cleanup completed'); } catch (error) { console.error('Error during mailbox cleanup:', error); } } async function cleanupUnmatchedAndExpired() { try { let expired = 0; let removed = 0; const mailboxes = await Mailbox.query() .select('username', 'domain', 'expires', 'ignore') .where('ignore', 0); const aliases = await Alias.query() .select('address', 'domain', 'ignore') .where('ignore', 0); // Create lookup maps for efficient matching const mailboxMap = new Map( mailboxes.map(m => [m.username, m]) ); const aliasMap = new Map( aliases.map(a => [a.address, a]) ); // Check mailboxes without matching aliases for (const [mailboxAddress, mailbox] of mailboxMap) { if (!aliasMap.has(mailboxAddress)) { console.log(`Removing mailbox without alias: ${mailboxAddress}`); await Mailbox.query() .where('username', mailbox.username) .patch({ active: 0 }); removed++; } } // Check aliases without matching mailboxes for (const [aliasAddress, alias] of aliasMap) { if (!mailboxMap.has(aliasAddress)) { console.log(`Removing alias without mailbox: ${aliasAddress}`); await Alias.query() .where('address', alias.address) .patch({ active: 0 }); removed++; } } // Check expiration for matched pairs const currentTime = format(new Date(), 'yyyy-MM-dd HH:mm:ss'); for (const [mailboxAddress, mailbox] of mailboxMap) { console.log(`Checking ${mailboxAddress}: expires ${mailbox.expires} vs current ${currentTime}`); if (aliasMap.has(mailboxAddress) && isBefore(new Date(mailbox.expires), new Date())) { console.log(`Removing expired mailbox and alias: ${mailboxAddress}`); await Mailbox.query() .where('username', mailbox.username) .where('ignore', 0) .delete(); await Alias.query() .where('address', mailboxAddress) .where('ignore', 0) .delete(); expired++; } } await scanMailboxDirectories(async ({ user, domain, userPath }) => { const mailboxAddress = `${user}@${domain}`; const mailbox = mailboxMap.get(mailboxAddress); const alias = aliasMap.get(mailboxAddress); // Check if this mailbox exists in database with ignore=1 const ignoredMailbox = await Mailbox.query() .where('username', mailboxAddress) .where('ignore', 1) .first(); // Only remove if mailbox doesn't exist OR (exists, not ignored, and expired) if (!ignoredMailbox && (!mailbox || !alias || (mailbox && mailbox.expires < currentTime))) { console.log(`Removing directory for invalid/expired mailbox: ${mailboxAddress}`); await fs.rm(userPath, { recursive: true, force: true }); } }); console.log('Cleanup of unmatched and expired items completed'); } catch (error) { console.error('Error during cleanup:', error); } } async function cleanupInactiveMailboxes() { try { const mailboxes = await Mailbox.query() .select('username', 'domain', 'active') .where('active', 0) .where('ignore', 0); const aliases = await Alias.query() .select('address', 'domain', 'active') .where('active', 0) .where('ignore', 0); for (const mailbox of mailboxes) { await Mailbox.query() .where('username', mailbox.username) .where('ignore', 0) .delete(); } for (const alias of aliases) { await Alias.query() .where('address', alias.address) .where('ignore', 0) .delete(); } console.log('Cleanup of inactive mailboxes completed'); } catch (error) { console.error('Error during cleanup:', error); } } module.exports = { cleanupOrphanedMailboxes, cleanupUnmatchedAndExpired, cleanupInactiveMailboxes };