166 lines
3.6 KiB
Markdown
166 lines
3.6 KiB
Markdown
|
|
# Rotation Logic
|
||
|
|
|
||
|
|
## How the Grandfather-Father-Son Scheme Works
|
||
|
|
|
||
|
|
### The Naming Convention
|
||
|
|
|
||
|
|
Backups follow this pattern:
|
||
|
|
```
|
||
|
|
solaria-deep-backup_YYYYMMDD_HHMMSS.tar.gz
|
||
|
|
```
|
||
|
|
|
||
|
|
Examples:
|
||
|
|
- `solaria-deep-backup_20260215_020001.tar.gz` (Feb 15, 2026, 2:00 AM)
|
||
|
|
- `solaria-deep-backup_20260208_020001.tar.gz` (Feb 8, 2026, 2:00 AM)
|
||
|
|
- `solaria-deep-backup_20260101_000001.tar.gz` (Jan 1, 2026, 12:00 AM)
|
||
|
|
|
||
|
|
### Extraction
|
||
|
|
|
||
|
|
From the filename, we extract:
|
||
|
|
- **Date**: `YYYYMMDD` (first 8 digits)
|
||
|
|
- **Week**: `YYYYW##` (ISO week number)
|
||
|
|
- **Month**: `YYYYMM` (first 6 digits)
|
||
|
|
- **Year**: `YYYY` (first 4 digits)
|
||
|
|
|
||
|
|
### The Algorithm
|
||
|
|
|
||
|
|
```
|
||
|
|
1. SCAN
|
||
|
|
Find all backup files in the directory
|
||
|
|
Extract date components for each
|
||
|
|
|
||
|
|
2. CATEGORIZE
|
||
|
|
For each backup, mark its:
|
||
|
|
- Daily bucket (exact date)
|
||
|
|
- Weekly bucket (ISO week)
|
||
|
|
- Monthly bucket (year-month)
|
||
|
|
- Yearly bucket (year)
|
||
|
|
|
||
|
|
3. ROTATE
|
||
|
|
For each bucket type:
|
||
|
|
a. Identify all items
|
||
|
|
b. Sort by date (newest first)
|
||
|
|
c. Keep N newest
|
||
|
|
d. Mark others for deletion
|
||
|
|
|
||
|
|
4. VERIFY
|
||
|
|
Dry-run first (report only)
|
||
|
|
--apply flag executes deletions
|
||
|
|
```
|
||
|
|
|
||
|
|
### Code Structure
|
||
|
|
|
||
|
|
```bash
|
||
|
|
#!/bin/bash
|
||
|
|
# solaria-backup-rotate.sh
|
||
|
|
|
||
|
|
# Configuration
|
||
|
|
MAX_DAILIES=7
|
||
|
|
MAX_WEEKLIES=4
|
||
|
|
MAX_MONTHLIES=12
|
||
|
|
MAX_YEARLIES=5
|
||
|
|
|
||
|
|
# Phase 1: Yearly
|
||
|
|
# Keep newest backup of each year
|
||
|
|
# Delete older year backups beyond MAX_YEARLIES
|
||
|
|
|
||
|
|
# Phase 2: Monthly
|
||
|
|
# For each month, keep only newest
|
||
|
|
# Delete older month backups
|
||
|
|
|
||
|
|
# Phase 3: Weekly
|
||
|
|
# For each week, keep only newest
|
||
|
|
# Delete older week backups
|
||
|
|
|
||
|
|
# Phase 4: Daily
|
||
|
|
# Keep last 7 days
|
||
|
|
# (Already handled by daily rotation logic)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Safety Features
|
||
|
|
|
||
|
|
1. **Dry-Run Default**: Running without `--apply` shows what would happen
|
||
|
|
2. **Chunk Awareness**: Handles split archives (`.part_XXX`)
|
||
|
|
3. **Error Handling**: Fails safely on invalid directories
|
||
|
|
4. **Logging**: All operations logged with timestamps
|
||
|
|
|
||
|
|
### Example Output
|
||
|
|
|
||
|
|
```
|
||
|
|
[2026-02-15 06:57:47] ROTATE: Starting rotation in: /path/to/backups
|
||
|
|
[2026-02-15 06:57:47] ROTATE: Found: solaria-deep-backup_20260215_060000.tar.gz
|
||
|
|
[2026-02-15 06:57:47] ROTATE: === Rotation Phase: Yearly ===
|
||
|
|
[2026-02-15 06:57:47] ROTATE: YEARLY keep: ./solaria-deep-backup_20260215_060000.tar.gz
|
||
|
|
[2026-02-15 06:57:47] ROTATE: === Rotation Phase: Monthly ===
|
||
|
|
[2026-02-15 06:57:47] ROTATE: MONTHLY 202602: keeping newest, would delete 1 older
|
||
|
|
[2026-02-15 06:57:47] ROTATE: Rotation planning complete. Run with --apply to execute.
|
||
|
|
```
|
||
|
|
|
||
|
|
### What Gets Deleted
|
||
|
|
|
||
|
|
Suppose we have:
|
||
|
|
```
|
||
|
|
2026-02-15 (newest daily)
|
||
|
|
2026-02-14
|
||
|
|
2026-02-13
|
||
|
|
2026-02-12
|
||
|
|
2026-02-11
|
||
|
|
2026-02-10
|
||
|
|
2026-02-09
|
||
|
|
2026-02-08 (DELETE - beyond 7 days)
|
||
|
|
2026-02-02 (DELETE - older week, not newest)
|
||
|
|
2026-02-01 (DELETE - older month, not newest)
|
||
|
|
2025-01-15 (DELETE - older year, not newest)
|
||
|
|
```
|
||
|
|
|
||
|
|
After rotation:
|
||
|
|
```
|
||
|
|
2026-02-15 (kept - newest daily)
|
||
|
|
2026-02-14 (kept - within 7 days)
|
||
|
|
2026-02-13 (kept - within 7 days)
|
||
|
|
2026-02-12 (kept - within 7 days)
|
||
|
|
2026-02-11 (kept - within 7 days)
|
||
|
|
2026-02-10 (kept - within 7 days)
|
||
|
|
2026-02-09 (kept - within 7 days)
|
||
|
|
2026-02-08 (DELETED)
|
||
|
|
2026-02-02 (DELETED)
|
||
|
|
2026-02-01 (DELETED)
|
||
|
|
2025-01-15 (DELETED)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
The test suite covers:
|
||
|
|
- Empty directories
|
||
|
|
- Single backups
|
||
|
|
- Multiple backups same day
|
||
|
|
- Mixed ages (daily rotation)
|
||
|
|
- Weekly promotion logic
|
||
|
|
- Monthly promotion logic
|
||
|
|
- Yearly promotion logic
|
||
|
|
- Month/year boundary cases
|
||
|
|
- Chunk file handling
|
||
|
|
- Error conditions
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd tests
|
||
|
|
./test-backup-rotate.sh
|
||
|
|
# Expected: 13 passed, 0 failed
|
||
|
|
```
|
||
|
|
|
||
|
|
## Integration
|
||
|
|
|
||
|
|
### With solaria-deep-backup.sh
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# At end of backup script
|
||
|
|
/path/to/solaria-backup-rotate.sh /path/to/backups --apply
|
||
|
|
```
|
||
|
|
|
||
|
|
### With Cron
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Daily at 2 AM
|
||
|
|
0 2 * * * /path/to/solaria-backup-rotate.sh /path/to/backups --apply >> /var/log/backup.log 2>&1
|
||
|
|
```
|