2weekmail/api/scripts/cleanup.js
2025-03-22 02:50:26 +00:00

200 lines
6.4 KiB
JavaScript

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
};