/** * S3 Backup Script for Weight Tracker * * This script creates automated backups of the Weight Tracker data * and uploads them to an S3-compatible storage (like Minio) */ const fs = require('fs'); const path = require('path'); const { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand } = require('@aws-sdk/client-s3'); const cron = require('node-cron'); // Configuration (can be overridden by environment variables) const config = { // S3 Configuration s3: { endpoint: process.env.S3_ENDPOINT || 'https://your-minio-server.example.com', region: process.env.S3_REGION || 'us-east-1', // Default region, can be any value for Minio bucket: process.env.S3_BUCKET || 'weight-tracker-backups', accessKey: process.env.S3_ACCESS_KEY || 'your-access-key', secretKey: process.env.S3_SECRET_KEY || 'your-secret-key', useSSL: process.env.S3_USE_SSL !== 'false' // Default to true unless explicitly set to 'false' }, // Backup Configuration backup: { dataPath: process.env.DATA_PATH || '/data/weight-tracker-data.json', schedule: process.env.BACKUP_SCHEDULE || '0 0 * * *', // Default: daily at midnight retention: parseInt(process.env.BACKUP_RETENTION || '7') // Default: keep 7 backups } }; // Initialize S3 client const s3Client = new S3Client({ endpoint: config.s3.endpoint, region: config.s3.region, credentials: { accessKeyId: config.s3.accessKey, secretAccessKey: config.s3.secretKey }, forcePathStyle: true, // Required for Minio tls: config.s3.useSSL }); /** * Create a backup and upload to S3 */ async function createBackup() { try { // Check if data file exists if (!fs.existsSync(config.backup.dataPath)) { console.error(`Data file not found: ${config.backup.dataPath}`); return; } // Read data file const data = fs.readFileSync(config.backup.dataPath, 'utf8'); // Generate backup filename with timestamp const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupFilename = `weight-tracker-backup-${timestamp}.json`; // Upload to S3 const uploadParams = { Bucket: config.s3.bucket, Key: backupFilename, Body: data, ContentType: 'application/json' }; const uploadCommand = new PutObjectCommand(uploadParams); await s3Client.send(uploadCommand); console.log(`Backup created successfully: ${backupFilename}`); // Cleanup old backups await cleanupOldBackups(); } catch (error) { console.error('Error creating backup:', error); } } /** * Clean up old backups based on retention policy */ async function cleanupOldBackups() { try { // List all backups const listParams = { Bucket: config.s3.bucket, Prefix: 'weight-tracker-backup-' }; const listCommand = new ListObjectsV2Command(listParams); const response = await s3Client.send(listCommand); if (!response.Contents || response.Contents.length <= config.backup.retention) { return; // No cleanup needed } // Sort by date (oldest first) const backups = response.Contents.sort((a, b) => new Date(a.LastModified) - new Date(b.LastModified) ); // Delete oldest backups that exceed retention count const backupsToDelete = backups.slice(0, backups.length - config.backup.retention); for (const backup of backupsToDelete) { const deleteParams = { Bucket: config.s3.bucket, Key: backup.Key }; const deleteCommand = new DeleteObjectCommand(deleteParams); await s3Client.send(deleteCommand); console.log(`Deleted old backup: ${backup.Key}`); } } catch (error) { console.error('Error cleaning up old backups:', error); } } /** * Initialize the backup system */ function init() { console.log('Starting Weight Tracker S3 backup system'); console.log(`Backup schedule: ${config.backup.schedule}`); console.log(`Backup retention: ${config.backup.retention} backups`); console.log(`S3 endpoint: ${config.s3.endpoint}`); console.log(`S3 bucket: ${config.s3.bucket}`); // Schedule regular backups cron.schedule(config.backup.schedule, () => { console.log('Running scheduled backup...'); createBackup(); }); // Create initial backup console.log('Creating initial backup...'); createBackup(); } // Start the backup system init();