This repository was archived by the owner on Jan 29, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 71
Add automated backup system for database #86
Open
Copilot
wants to merge
5
commits into
main
Choose a base branch
from
copilot/implement-backup-system
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
69c6343
Initial plan
Copilot 3b73d2f
Implement automated backup system for database files
Copilot 31c6bea
Changes before error encountered
Copilot d8d3767
Address code review feedback
Copilot 8fee9eb
Merge main branch and resolve conflicts
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| # Database Backup System | ||
|
|
||
| ## Overview | ||
|
|
||
| The automated backup system provides comprehensive protection for all database files in the `.data/` directory. It creates compressed, timestamped backups automatically at configurable intervals. | ||
|
|
||
| ## Features | ||
|
|
||
| - β **Automated Backups**: Runs every 24 hours by default (configurable) | ||
| - β **Startup Backup**: Creates backup on server startup | ||
| - β **Shutdown Backup**: Creates final backup on graceful shutdown | ||
| - β **Compression**: Uses gzip compression to save disk space | ||
| - β **Rotation**: Automatically keeps only the last 30 backups (configurable) | ||
| - β **Metadata**: Each backup includes timestamp and file list | ||
| - β **API Endpoints**: Manual backup/restore via REST API | ||
|
|
||
| ## Backed Up Files | ||
|
|
||
| The system backs up the following files from `.data/`: | ||
| - `workflows.json` - User-created workflow definitions | ||
| - `store-state.json` - Current UI state | ||
| - `sessions.json` - Active session data | ||
|
|
||
| ## Configuration | ||
|
|
||
| Set these environment variables in your `.env` file: | ||
|
|
||
| ```bash | ||
| # Backup configuration | ||
| BACKUP_INTERVAL_HOURS=24 # Backup every 24 hours | ||
| MAX_BACKUPS=30 # Keep last 30 backups | ||
| API_KEY=your-secure-api-key-for-admin-endpoints | ||
| ``` | ||
|
|
||
| ## Directory Structure | ||
|
|
||
| ``` | ||
| .data/ | ||
| βββ workflows.json | ||
| βββ store-state.json | ||
| βββ sessions.json | ||
| βββ backups/ | ||
| βββ backup-2025-10-27T23-51-02-293Z/ | ||
| β βββ workflows.json.gz | ||
| β βββ store-state.json.gz | ||
| β βββ sessions.json.gz | ||
| β βββ metadata.json | ||
| βββ backup-2025-10-27T23-52-19-819Z/ | ||
| βββ ... | ||
| ``` | ||
|
|
||
| ## API Endpoints | ||
|
|
||
| ### List Backups | ||
| ```bash | ||
| curl http://localhost:3001/api/admin/backups \ | ||
| -H "X-API-Key: $API_KEY" | ||
| ``` | ||
|
|
||
| Response: | ||
| ```json | ||
| { | ||
| "success": true, | ||
| "data": { | ||
| "backups": [ | ||
| { | ||
| "name": "backup-2025-10-27T23-51-02-293Z", | ||
| "timestamp": "2025-10-27T23:51:02.303Z", | ||
| "files": ["workflows.json", "store-state.json", "sessions.json"], | ||
| "size": 4096 | ||
| } | ||
| ], | ||
| "stats": { | ||
| "count": 5, | ||
| "totalSize": 20480, | ||
| "oldest": "2025-10-27T23:51:02.303Z", | ||
| "newest": "2025-10-27T23:52:19.823Z" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Create Manual Backup | ||
| ```bash | ||
| curl -X POST http://localhost:3001/api/admin/backups \ | ||
| -H "X-API-Key: $API_KEY" | ||
| ``` | ||
|
|
||
| Response: | ||
| ```json | ||
| { | ||
| "success": true, | ||
| "data": { | ||
| "backupPath": "/path/to/.data/backups/backup-2025-10-27T23-52-19-819Z" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Restore from Backup | ||
| ```bash | ||
| curl -X POST http://localhost:3001/api/admin/backups/backup-2025-10-27T23-51-02-293Z/restore \ | ||
| -H "X-API-Key: $API_KEY" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{"confirm": "RESTORE"}' | ||
| ``` | ||
|
|
||
| Response: | ||
| ```json | ||
| { | ||
| "success": true, | ||
| "message": "Database restored. Restart recommended." | ||
| } | ||
| ``` | ||
|
|
||
| β οΈ **Warning**: Restoring a backup will overwrite current database files. Consider creating a manual backup first. | ||
|
|
||
| ## Programmatic Usage | ||
|
|
||
| ### Create Backup | ||
| ```javascript | ||
| import { createBackup } from './backend/src/db/backup.js'; | ||
|
|
||
| const backupPath = await createBackup(); | ||
| console.log('Backup created:', backupPath); | ||
| ``` | ||
|
|
||
| ### List Backups | ||
| ```javascript | ||
| import { listBackups, getBackupStats } from './backend/src/db/backup.js'; | ||
|
|
||
| const backups = await listBackups(); | ||
| const stats = await getBackupStats(); | ||
|
|
||
| console.log('Available backups:', backups.length); | ||
| console.log('Total size:', stats.totalSize, 'bytes'); | ||
| ``` | ||
|
|
||
| ### Restore Backup | ||
| ```javascript | ||
| import { restoreBackup } from './backend/src/db/backup.js'; | ||
|
|
||
| await restoreBackup('backup-2025-10-27T23-51-02-293Z'); | ||
| console.log('Database restored'); | ||
| ``` | ||
|
|
||
| ## Security | ||
|
|
||
| - **Authentication**: All admin endpoints require `X-API-Key` header (when `API_KEY` env var is set) | ||
| - **Confirmation**: Restore operations require explicit `{"confirm": "RESTORE"}` in request body | ||
| - **Read-only by Default**: Backups are created but never automatically deleted except during rotation | ||
|
|
||
| ## Monitoring | ||
|
|
||
| The backup system logs all operations: | ||
| - Backup creation success/failure | ||
| - Backup restoration success/failure | ||
| - Old backup deletion | ||
| - Scheduler start/stop | ||
|
|
||
| Example log output: | ||
| ``` | ||
| [2025-10-27T23:51:02.303Z] INFO: Database backup created {"backupName":"backup-2025-10-27T23-51-02-293Z","files":["workflows.json","store-state.json","sessions.json"]} | ||
| [2025-10-27T23:51:56.350Z] INFO: Backup scheduler started {"intervalHours":24} | ||
| [2025-10-27T23:52:32.682Z] INFO: Final backup completed | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Backup Not Created | ||
| - Check that `.data/` directory exists | ||
| - Verify database files exist in `.data/` | ||
| - Check server logs for error messages | ||
|
|
||
| ### Restore Failed | ||
| - Verify backup exists in `.data/backups/` | ||
| - Check backup metadata.json is valid | ||
| - Ensure sufficient disk space | ||
|
|
||
| ### Too Many Backups | ||
| - Reduce `MAX_BACKUPS` environment variable | ||
| - Manually delete old backups from `.data/backups/` | ||
|
|
||
| ## Future Enhancements | ||
|
|
||
| - **Cloud Backup**: Integration with cloud storage (S3, Google Cloud Storage) | ||
| - **Encryption**: Encrypt backups at rest | ||
| - **Incremental Backups**: Only backup changed files | ||
| - **Backup Verification**: Automatically verify backup integrity | ||
| - **Email Notifications**: Alert on backup success/failure | ||
|
|
||
| ## Related Issues | ||
|
|
||
| - Issue #73: Automated Backup System (this implementation) | ||
| - Issue #68: Atomic Database Operations (dependency) | ||
| - Pull Request #66: Original review discussion |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /** | ||
| * Authentication Middleware | ||
| * | ||
| * Provides simple API key authentication for admin endpoints. | ||
| * Uses X-API-Key header or API_KEY environment variable. | ||
| */ | ||
|
|
||
| import { logger } from '../../utils/logger.js'; | ||
|
|
||
| const API_KEY = process.env.API_KEY || ''; | ||
|
|
||
| /** | ||
| * Authentication middleware | ||
| * @param {Object} options - Authentication options | ||
| * @param {boolean} options.required - Whether authentication is required | ||
| */ | ||
| export function authenticate(options = { required: true }) { | ||
| return (req, res, next) => { | ||
| if (!options.required) { | ||
| return next(); | ||
| } | ||
|
|
||
| const providedKey = req.headers['x-api-key']; | ||
|
|
||
| if (!API_KEY) { | ||
| logger.warn('API_KEY not configured, authentication disabled'); | ||
| return next(); | ||
| } | ||
|
clduab11 marked this conversation as resolved.
|
||
|
|
||
| if (!providedKey || providedKey !== API_KEY) { | ||
| logger.warn({ ip: req.ip }, 'Unauthorized access attempt'); | ||
| return res.status(401).json({ | ||
| success: false, | ||
| error: { message: 'Unauthorized. Provide valid X-API-Key header.' } | ||
| }); | ||
| } | ||
|
clduab11 marked this conversation as resolved.
|
||
|
|
||
| next(); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Async handler wrapper for route handlers | ||
| */ | ||
| export function asyncHandler(fn) { | ||
| return (req, res, next) => { | ||
| Promise.resolve(fn(req, res, next)).catch(next); | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| /** | ||
| * Admin API Routes | ||
| * | ||
| * Provides endpoints for manual backup and restore operations. | ||
| * All endpoints require authentication via X-API-Key header. | ||
| */ | ||
|
|
||
| import express from 'express'; | ||
| import { authenticate, asyncHandler } from '../middleware/auth.js'; | ||
| import * as backup from '../../db/backup.js'; | ||
|
|
||
| const router = express.Router(); | ||
|
|
||
| /** | ||
| * GET /api/admin/backups | ||
| * List all available backups with statistics | ||
| */ | ||
| router.get('/backups', | ||
| authenticate({ required: true }), | ||
| asyncHandler(async (req, res) => { | ||
| const backups = await backup.listBackups(); | ||
| const stats = await backup.getBackupStats(); | ||
| res.json({ success: true, data: { backups, stats } }); | ||
| }) | ||
| ); | ||
|
|
||
| /** | ||
| * POST /api/admin/backups | ||
| * Create a new backup manually | ||
| */ | ||
| router.post('/backups', | ||
| authenticate({ required: true }), | ||
| asyncHandler(async (req, res) => { | ||
| const backupPath = await backup.createBackup(); | ||
| res.json({ success: true, data: { backupPath } }); | ||
| }) | ||
| ); | ||
|
|
||
| /** | ||
| * POST /api/admin/backups/:name/restore | ||
| * Restore database from a backup | ||
| * Requires confirmation in request body: {"confirm": "RESTORE"} | ||
| */ | ||
| router.post('/backups/:name/restore', | ||
| authenticate({ required: true }), | ||
| asyncHandler(async (req, res) => { | ||
| if (req.body.confirm !== 'RESTORE') { | ||
| return res.status(400).json({ | ||
| success: false, | ||
| error: { message: 'Confirmation required: send {"confirm": "RESTORE"}' } | ||
| }); | ||
| } | ||
| await backup.restoreBackup(req.params.name); | ||
| res.json({ | ||
| success: true, | ||
| message: 'Database restored. Restart recommended.' | ||
| }); | ||
| }) | ||
| ); | ||
|
|
||
| export default router; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.