diff --git a/bin/storage-daemon.js b/bin/storage-daemon.js index 9d3cf9c..a858f3f 100755 --- a/bin/storage-daemon.js +++ b/bin/storage-daemon.js @@ -19,13 +19,14 @@ Usage: storage-daemon Commands: start Start the daemon - stop Stop the daemon + stop Stop the daemon restart Restart the daemon status Show daemon status logs Show daemon logs dashboard Open web dashboard config Configuration options (interactive/file) configure Interactive configuration setup + cleanup Run cleanup to free disk space install Install daemon service uninstall Remove daemon service version Show version @@ -34,6 +35,7 @@ Examples: storage-daemon start storage-daemon status storage-daemon dashboard + storage-daemon cleanup `); } @@ -87,6 +89,16 @@ function main() { process.exit(1); } break; + + case 'cleanup': + const cleanupScript = path.join(DAEMON_DIR, 'src/cleanup.js'); + if (fs.existsSync(cleanupScript)) { + runCommand('node', [cleanupScript]); + } else { + console.error('Cleanup script not found'); + process.exit(1); + } + break; case 'install': const installScript = path.join(DAEMON_DIR, 'install.sh'); diff --git a/mac-cleanup.sh b/mac-cleanup.sh new file mode 100755 index 0000000..03a1341 --- /dev/null +++ b/mac-cleanup.sh @@ -0,0 +1,217 @@ +#!/bin/bash + +############################################## +# Mac Disk Space Cleanup Script +# Safely removes temporary and cache files +############################################## + +echo "==============================================" +echo " Mac Disk Space Cleanup" +echo "==============================================" +echo "" + +# Function to format bytes +format_bytes() { + numfmt --to=iec-i --suffix=B $1 2>/dev/null || echo "$1 bytes" +} + +# Function to get directory size +get_size() { + du -sb "$1" 2>/dev/null | cut -f1 +} + +total_freed=0 + +echo "Analyzing disk usage before cleanup..." +df -h ~ | tail -1 +echo "" + +# 1. CloudKit Temporary Files (BIGGEST SAVING - ~50GB) +echo "==============================================" +echo "1. Cleaning CloudKit temporary files..." +echo "==============================================" +cloudkit_path="$HOME/Library/Caches/CloudKit/com.apple.bird" +if [ -d "$cloudkit_path" ]; then + before=$(get_size "$cloudkit_path") + echo "Before: $(format_bytes $before)" + + # Remove temporary MMCS files + find "$cloudkit_path" -type f -name "tmpm-*" -delete 2>/dev/null + + after=$(get_size "$cloudkit_path") + freed=$((before - after)) + total_freed=$((total_freed + freed)) + echo "Freed: $(format_bytes $freed)" +else + echo "CloudKit cache not found - skipping" +fi +echo "" + +# 2. Claude Logs +echo "==============================================" +echo "2. Cleaning Claude logs..." +echo "==============================================" +if [ -d "$HOME/.claude/logs" ]; then + before=$(get_size "$HOME/.claude/logs") + echo "Before: $(format_bytes $before)" + + rm -rf "$HOME/.claude/logs/GoogleDrive-"* 2>/dev/null + + after=$(get_size "$HOME/.claude/logs") + freed=$((before - after)) + total_freed=$((total_freed + freed)) + echo "Freed: $(format_bytes $freed)" +else + echo "Claude logs not found - skipping" +fi +echo "" + +# 3. Claude Debug Files +echo "==============================================" +echo "3. Cleaning Claude debug files..." +echo "==============================================" +if [ -d "$HOME/.claude/debug" ]; then + before=$(get_size "$HOME/.claude/debug") + echo "Before: $(format_bytes $before)" + + rm -rf "$HOME/.claude/debug"/*.txt 2>/dev/null + + after=$(get_size "$HOME/.claude/debug") + freed=$((before - after)) + total_freed=$((total_freed + freed)) + echo "Freed: $(format_bytes $freed)" +else + echo "Claude debug not found - skipping" +fi +echo "" + +# 4. Old Messages Videos (>90 days) +echo "==============================================" +echo "4. Cleaning old Messages videos (>90 days)..." +echo "==============================================" +if [ -d "$HOME/Library/Messages/Attachments" ]; then + # Count and size before + old_videos=$(find "$HOME/Library/Messages/Attachments" -name "*.MOV" -mtime +90 2>/dev/null) + if [ -n "$old_videos" ]; then + count=$(echo "$old_videos" | wc -l | tr -d ' ') + echo "Found $count old videos" + + # Calculate size + size=0 + while IFS= read -r file; do + fsize=$(stat -f%z "$file" 2>/dev/null || echo 0) + size=$((size + fsize)) + done <<< "$old_videos" + + echo "Total size: $(format_bytes $size)" + read -p "Delete these old videos? (y/n): " confirm + if [ "$confirm" = "y" ]; then + find "$HOME/Library/Messages/Attachments" -name "*.MOV" -mtime +90 -delete 2>/dev/null + total_freed=$((total_freed + size)) + echo "Freed: $(format_bytes $size)" + else + echo "Skipped" + fi + else + echo "No old videos found" + fi +else + echo "Messages attachments not found - skipping" +fi +echo "" + +# 5. Chittychat overlay state +echo "==============================================" +echo "5. Cleaning chittychat overlay state..." +echo "==============================================" +if [ -f "$HOME/.chittychat/native-overlay-state.json" ]; then + size=$(stat -f%z "$HOME/.chittychat/native-overlay-state.json" 2>/dev/null || echo 0) + echo "Size: $(format_bytes $size)" + rm -f "$HOME/.chittychat/native-overlay-state.json" 2>/dev/null + total_freed=$((total_freed + size)) + echo "Freed: $(format_bytes $size)" +else + echo "Chittychat state not found - skipping" +fi +echo "" + +# 6. Browser Caches +echo "==============================================" +echo "6. Cleaning browser caches..." +echo "==============================================" +# Chrome +if [ -d "$HOME/Library/Caches/Google/Chrome" ]; then + before=$(get_size "$HOME/Library/Caches/Google/Chrome") + echo "Chrome cache before: $(format_bytes $before)" + rm -rf "$HOME/Library/Caches/Google/Chrome"/* 2>/dev/null + after=$(get_size "$HOME/Library/Caches/Google/Chrome") + freed=$((before - after)) + total_freed=$((total_freed + freed)) + echo "Chrome freed: $(format_bytes $freed)" +fi + +# Safari +if [ -d "$HOME/Library/Caches/com.apple.Safari" ]; then + before=$(get_size "$HOME/Library/Caches/com.apple.Safari") + echo "Safari cache before: $(format_bytes $before)" + rm -rf "$HOME/Library/Caches/com.apple.Safari"/* 2>/dev/null + after=$(get_size "$HOME/Library/Caches/com.apple.Safari") + freed=$((before - after)) + total_freed=$((total_freed + freed)) + echo "Safari freed: $(format_bytes $freed)" +fi +echo "" + +# 7. System Caches +echo "==============================================" +echo "7. Cleaning user caches..." +echo "==============================================" +if [ -d "$HOME/Library/Caches" ]; then + # Clean specific safe caches + for cache_dir in "com.apple.bird" "CloudKit" "SiriTTS"; do + if [ -d "$HOME/Library/Caches/$cache_dir" ]; then + before=$(get_size "$HOME/Library/Caches/$cache_dir") + rm -rf "$HOME/Library/Caches/$cache_dir"/* 2>/dev/null + after=$(get_size "$HOME/Library/Caches/$cache_dir") + freed=$((before - after)) + if [ $freed -gt 0 ]; then + total_freed=$((total_freed + freed)) + echo "$cache_dir freed: $(format_bytes $freed)" + fi + fi + done +fi +echo "" + +# 8. Old downloads +echo "==============================================" +echo "8. Finding old downloads (>90 days)..." +echo "==============================================" +if [ -d "$HOME/Downloads" ]; then + old_files=$(find "$HOME/Downloads" -type f -mtime +90 2>/dev/null | head -20) + if [ -n "$old_files" ]; then + echo "Found old downloads (showing first 20):" + echo "$old_files" + echo "" + read -p "Review and delete manually? (y/n): " confirm + if [ "$confirm" = "y" ]; then + open "$HOME/Downloads" + fi + else + echo "No old downloads found" + fi +fi +echo "" + +# Summary +echo "==============================================" +echo " CLEANUP SUMMARY" +echo "==============================================" +echo "Total space freed: $(format_bytes $total_freed)" +echo "" +echo "Disk usage after cleanup:" +df -h ~ | tail -1 +echo "" +echo "==============================================" +echo "Cleanup complete!" +echo "==============================================" diff --git a/src/cleanup.js b/src/cleanup.js new file mode 100644 index 0000000..dc9c46c --- /dev/null +++ b/src/cleanup.js @@ -0,0 +1,311 @@ +#!/usr/bin/env node + +/** + * Storage Cleanup Script + * Runs cleanup operations to free disk space + */ + +const path = require('path'); +const fs = require('fs').promises; +const { exec } = require('child_process'); +const { promisify } = require('util'); +const execAsync = promisify(exec); + +class CleanupTool { + constructor() { + this.totalFreed = 0; + } + + formatBytes(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; + } + + async getDiskUsage() { + try { + const result = await execAsync('df -h / | tail -1'); + const parts = result.stdout.trim().split(/\s+/); + + return { + total: parts[1], + used: parts[2], + available: parts[3], + usePercent: parts[4], + mount: parts[5] || '/' + }; + } catch (err) { + console.error('Error getting disk usage:', err.message); + return null; + } + } + + async getDirectorySize(dirPath) { + try { + const result = await execAsync(`du -sb "${dirPath}" 2>/dev/null | cut -f1`); + return parseInt(result.stdout.trim()) || 0; + } catch (err) { + return 0; + } + } + + async cleanCaches() { + console.log('\nCleaning cache directories...'); + + const cacheDirectories = [ + { path: '~/.cache', name: 'User cache' }, + { path: '~/.npm/_cacache', name: 'NPM cache' }, + { path: '~/Library/Caches', name: 'Library caches' }, + { path: '~/.yarn/cache', name: 'Yarn cache' }, + { path: '~/Library/Application Support/Google/DriveFS/*/content_cache', name: 'Google Drive cache' } + ]; + + let totalCacheFreed = 0; + + for (const cache of cacheDirectories) { + const expandedPath = cache.path.replace('~', process.env.HOME); + try { + // Check if directory exists + const exists = await fs.access(expandedPath).then(() => true).catch(() => false); + if (!exists) { + console.log(` ${cache.name}: Not found, skipping`); + continue; + } + + const before = await this.getDirectorySize(expandedPath); + if (before > 0) { + console.log(` ${cache.name}: ${this.formatBytes(before)}`); + + // Clean the cache + if (cache.path.includes('*')) { + await execAsync(`bash -c 'rm -rf ${expandedPath}/* 2>/dev/null || true'`); + } else { + await execAsync(`rm -rf ${expandedPath}/* 2>/dev/null || true`); + } + + const after = await this.getDirectorySize(expandedPath); + const freed = before - after; + if (freed > 0) { + console.log(` Freed: ${this.formatBytes(freed)}`); + totalCacheFreed += freed; + } + } else { + console.log(` ${cache.name}: Already clean`); + } + } catch (err) { + console.log(` ${cache.name}: Error - ${err.message}`); + } + } + + this.totalFreed += totalCacheFreed; + return totalCacheFreed; + } + + async cleanOldLogs() { + console.log('\nCleaning old log files (>30 days)...'); + + try { + const result = await execAsync('find ~ -name "*.log" -mtime +30 -type f -exec du -sb {} \\; 2>/dev/null | awk \'{sum+=$1} END {print sum}\''); + const logSize = parseInt(result.stdout.trim()) || 0; + + if (logSize > 0) { + console.log(` Found ${this.formatBytes(logSize)} of old logs`); + await execAsync('find ~ -name "*.log" -mtime +30 -type f -delete 2>/dev/null || true'); + console.log(` Freed: ${this.formatBytes(logSize)}`); + this.totalFreed += logSize; + return logSize; + } else { + console.log(' No old logs found'); + return 0; + } + } catch (err) { + console.log(` Error: ${err.message}`); + return 0; + } + } + + async cleanOldDownloads() { + console.log('\nCleaning old downloads (>30 days)...'); + + const downloadsPath = path.join(process.env.HOME, 'Downloads'); + const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000); + + let downloadsFreed = 0; + let fileCount = 0; + + try { + const files = await fs.readdir(downloadsPath); + + for (const file of files) { + const filePath = path.join(downloadsPath, file); + try { + const stats = await fs.stat(filePath); + + if (stats.isFile() && stats.mtime.getTime() < thirtyDaysAgo) { + downloadsFreed += stats.size; + fileCount++; + await fs.unlink(filePath); + } + } catch (err) { + // Skip files we can't access + } + } + + if (fileCount > 0) { + console.log(` Deleted ${fileCount} old files`); + console.log(` Freed: ${this.formatBytes(downloadsFreed)}`); + this.totalFreed += downloadsFreed; + } else { + console.log(' No old files found'); + } + + return downloadsFreed; + } catch (err) { + console.log(` Error: ${err.message}`); + return 0; + } + } + + async cleanTempFiles() { + console.log('\nCleaning temporary files...'); + + const tempDirs = [ + { path: '/tmp', name: 'System temp' }, + { path: '~/.tmp', name: 'User temp' } + ]; + + let tempFreed = 0; + + for (const temp of tempDirs) { + const expandedPath = temp.path.replace('~', process.env.HOME); + try { + const before = await this.getDirectorySize(expandedPath); + if (before > 0) { + console.log(` ${temp.name}: ${this.formatBytes(before)}`); + await execAsync(`find ${expandedPath} -type f -mtime +7 -delete 2>/dev/null || true`); + const after = await this.getDirectorySize(expandedPath); + const freed = before - after; + if (freed > 0) { + console.log(` Freed: ${this.formatBytes(freed)}`); + tempFreed += freed; + } + } + } catch (err) { + console.log(` ${temp.name}: Error - ${err.message}`); + } + } + + this.totalFreed += tempFreed; + return tempFreed; + } + + async cleanEmptyDirectories() { + console.log('\nCleaning empty directories...'); + + try { + const result = await execAsync('find ~ -type d -empty 2>/dev/null | wc -l'); + const emptyCount = parseInt(result.stdout.trim()) || 0; + + if (emptyCount > 0) { + console.log(` Found ${emptyCount} empty directories`); + await execAsync('find ~ -type d -empty -delete 2>/dev/null || true'); + console.log(` Removed ${emptyCount} empty directories`); + } else { + console.log(' No empty directories found'); + } + } catch (err) { + console.log(` Error: ${err.message}`); + } + } + + async findLargeFiles() { + console.log('\nFinding large files (>100MB)...'); + + try { + const result = await execAsync('find ~ -type f -size +100M 2>/dev/null | head -20'); + const files = result.stdout.trim().split('\n').filter(f => f); + + if (files.length > 0) { + console.log(` Found ${files.length} large files (showing first 20):\n`); + + for (const file of files) { + try { + const stats = await fs.stat(file); + const size = this.formatBytes(stats.size); + const fileName = path.basename(file); + console.log(` ${size.padEnd(12)} ${fileName}`); + } catch (err) { + // Skip files we can't access + } + } + + console.log('\n Review these files manually to see if they can be deleted.'); + } else { + console.log(' No large files found'); + } + } catch (err) { + console.log(` Error: ${err.message}`); + } + } + + async run() { + console.log('================================================='); + console.log(' STORAGE CLEANUP TOOL'); + console.log('================================================='); + + // Show initial disk usage + const initialUsage = await this.getDiskUsage(); + if (initialUsage) { + console.log('\nInitial Disk Usage:'); + console.log(` Total: ${initialUsage.total}`); + console.log(` Used: ${initialUsage.used} (${initialUsage.usePercent})`); + console.log(` Available: ${initialUsage.available}`); + } + + console.log('\n================================================='); + console.log('Starting cleanup operations...'); + console.log('================================================='); + + // Run cleanup operations + await this.cleanCaches(); + await this.cleanOldLogs(); + await this.cleanOldDownloads(); + await this.cleanTempFiles(); + await this.cleanEmptyDirectories(); + + // Show final results + console.log('\n================================================='); + console.log('CLEANUP SUMMARY'); + console.log('================================================='); + console.log(`Total space freed: ${this.formatBytes(this.totalFreed)}`); + + // Show final disk usage + const finalUsage = await this.getDiskUsage(); + if (finalUsage) { + console.log('\nFinal Disk Usage:'); + console.log(` Total: ${finalUsage.total}`); + console.log(` Used: ${finalUsage.used} (${finalUsage.usePercent})`); + console.log(` Available: ${finalUsage.available}`); + } + + // Show large files + await this.findLargeFiles(); + + console.log('\n================================================='); + console.log('Cleanup complete!'); + console.log('=================================================\n'); + } +} + +// Run cleanup if executed directly +if (require.main === module) { + const cleanup = new CleanupTool(); + cleanup.run().catch(err => { + console.error('Cleanup failed:', err); + process.exit(1); + }); +} + +module.exports = CleanupTool;