Building cleanbot: Automated File Maintenance with Node.js

• 3 min read • by Kurniawan Satria

Automating disk hygiene, organizing download directories by MIME type, and managing scheduled cleanup tasks without external dependencies.

Every developer’s download folder eventually devolves into a multi-gigabyte graveyard of installer executables, temporary zip archives, random screenshots, and duplicate PDFs. While graphical disk cleanup utilities exist, they are often bloated, ad-supported, or require manual clicks.

To solve this once and for all, I built cleanbot—a lightweight, zero-dependency background maintenance tool written in pure Node.js that runs autonomously on a daily schedule.

The Design Philosophy: Zero Dependencies

Many command-line utilities quickly pull in dozens of npm packages (glob, fs-extra, winston, axios) for simple tasks. For a background scheduler that runs silently in the background, minimizing attack surface and memory footprint is paramount.

cleanbot was built using strictly Node.js standard libraries:

  • fs/promises for asynchronous filesystem operations.
  • path for cross-platform directory resolution.
  • Native fetch for dispatching Discord webhook telemetry notifications.

1. Extension-Based Auto Sorting

The first responsibility of the daemon is classifying scattered files in Downloads/ into dedicated categorization buckets (Images/, Videos/, Documents/, Archives/, Executables/):

const sortRules = {
  Images: ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg'],
  Videos: ['.mp4', '.mkv', '.mov', '.avi'],
  Documents: ['.pdf', '.docx', '.xlsx', '.pptx', '.txt'],
  Archives: ['.zip', '.rar', '.7z', '.tar', '.gz'],
  Executables: ['.exe', '.msi', '.apk'],
};

export async function sortDownloads(downloadsDir, rules) {
  const entries = await fs.readdir(downloadsDir, { withFileTypes: true });

  for (const entry of entries) {
    if (entry.isDirectory()) continue;

    const ext = path.extname(entry.name).toLowerCase();
    for (const [folder, extensions] of Object.entries(rules)) {
      if (extensions.includes(ext)) {
        const destDir = path.join(downloadsDir, folder);
        await fs.mkdir(destDir, { recursive: true });
        await fs.rename(
          path.join(downloadsDir, entry.name),
          path.join(destDir, entry.name)
        );
        break;
      }
    }
  }
}

2. Age-Based File Expiration & Safe Temp Purging

Temporary files and downloads older than 30 days are automatically deleted to reclaim disk space. Crucially, active lock contention is caught gracefully without terminating the process:

export async function cleanOldFiles(targetDir, maxAgeDays = 30) {
  const cutoffTime = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
  const entries = await fs.readdir(targetDir, { withFileTypes: true });

  for (const entry of entries) {
    const fullPath = path.join(targetDir, entry.name);
    try {
      const stats = await fs.stat(fullPath);
      if (stats.isFile() && stats.mtimeMs < cutoffTime) {
        await fs.unlink(fullPath);
      }
    } catch {
      // Gracefully bypass locked system files or permissions
    }
  }
}

3. Discord Telemetry via Webhook

When the daily cleanup completes, cleanbot compiles a summary log and posts a formatted notification to a private Discord channel using Discord’s Webhook API.

await fetch(process.env.DISCORD_WEBHOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    embeds: [
      {
        title: '🧹 Daily File Maintenance Completed',
        description: `Cleaned temp directories and sorted downloads successfully.`,
        color: 0xe0533c,
        timestamp: new Date().toISOString(),
      },
    ],
  }),
});

Scheduling the Task

Whether running on Windows via Task Scheduler (schtasks) or on Linux via a systemd timer / cron job, cleanbot executes silently in less than 500 milliseconds, keeps the system clean, and logs its output without any manual overhead.

Small automations like this compound over time, freeing up mental bandwidth and keeping machines tidy automatically.