Automated backup file rotation system with Telegram notifications using Grandfather-Father-Son (GFS) retention strategy.
- Configurable GFS Rotation Strategy: Per-project retention policies (daily, weekly, monthly backups)
- Multiple Datetime Format Support: Flexible parsing of backup filenames with configurable patterns
- Telegram Notifications:
- Low disk space alerts (with daily reminders until resolved)
- Missing backup file alerts
- Undersized file warnings
- Deletion failure alerts
- Weekly summary reports
- Disk Space Monitoring: Monitors
/backupsdirectory with hysteresis to prevent alert flapping - State Persistence: Tracks deletions, alerts, and reports to avoid duplicates
- Monthly Rotating Logs: Automatically rotates logs monthly, keeping 12 months
- Dry-Run Mode: Safe default mode that simulates deletions without actually removing files
- Built-in Scheduler: Automated weekly reports using configurable schedule
Install uv (fast Python package installer):
curl -LsSf https://astral.sh/uv/install.sh | sh- Clone the repository:
git clone https://github.com/dmytro-samoylenko/backup-rotator.git
cd backup-rotator- Create virtual environment and install dependencies:
uv venv
source .venv/bin/activate # On Linux/Mac
# or
.venv\Scripts\activate # On Windows
uv pip install -e .- Copy and edit
config.yaml:
cp config.yaml config.yaml.local
nano config.yaml.local-
Configure Telegram bot:
- Create a bot via @BotFather
- Get your chat ID via @userinfobot
- Update
telegram.bot_tokenandtelegram.chat_idin config
-
Configure projects:
- Set project IDs matching your
/backups/{project_id}directory structure - Define filename patterns using regex
- Set retention policies (or use defaults)
- Configure expected backup intervals and minimum file sizes
- Set project IDs matching your
Test the rotation logic without deleting files:
python run.py --config config.yaml.localActually delete backup files according to retention policy:
python run.py --config config.yaml.local --executeRun rotation once and exit (no scheduler):
python run.py --config config.yaml.local --onceImmediately send a weekly summary:
python run.py --config config.yaml.local --send-weekly-now--config PATH: Path to configuration file (default:config.yaml)--execute: Enable actual file deletion (default is dry-run)--once: Run once without starting scheduler--send-weekly-now: Send weekly summary immediately and exit
See config.yaml for a complete example with comments.
telegram: Bot token and chat IDbackups.base_path: Base directory for backups (e.g.,/backups)disk.threshold_percent: Alert when free space drops below this percentagedisk.margin_percent: Clear alert when space rises above threshold + marginlogging.directory: Log file directorylogging.level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)rotation_schedule.frequency: How often to run rotation (hourly,daily,weekly, orHH:MMfor custom time)rotation_schedule.time: Time for daily/custom rotations (HH:MM format)weekly_report.day: Day for weekly report (monday-sunday)weekly_report.time: Time for weekly report (HH:MM format)datetime_formats: List of datetime format patterns (tried in order)default_retention: Default GFS retention policy
id: Project identifier (matches directory name in/backups)name: Human-readable project namefilename_pattern: Regex pattern for backup filesexpected_interval_hours: Expected time between backupsmin_file_size_mb: Minimum file size (files below this are flagged)retention: Optional override for retention policy
The Grandfather-Father-Son strategy keeps:
- Daily backups: Last N days (default: 7)
- Weekly backups: Last N weeks (default: 4) - one per calendar week, preferring Monday
- Monthly backups: Last N months (default: 12) - one per calendar month, preferring first of month
Backups are categorized by calendar boundaries:
- Weeks start on Monday (ISO calendar)
- Months are calendar months
The system maintains a state.json file to track:
- Last deletion timestamp per project
- Deleted files count/size (for weekly summaries)
- Last weekly report timestamp
- Disk alert status
This prevents duplicate notifications and provides accurate weekly summaries.
Logs are written to logs/backup-rotator.log with:
- Monthly rotation
- 12 months retention
- Both file and console output
- Configurable log level
- Install the application:
# Clone/copy to /opt
sudo mkdir -p /opt/backup-rotator
sudo cp -r /path/to/backup-rotator/* /opt/backup-rotator/
# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment and install dependencies
cd /opt/backup-rotator
uv venv
uv pip install -e .
# Configure
sudo cp config.yaml config.yaml.local
sudo nano config.yaml.local
# Update Telegram credentials and project settings- Install systemd service:
sudo cp backup-rotator.service /etc/systemd/system/
sudo systemctl daemon-reload- Enable and start:
sudo systemctl enable backup-rotator
sudo systemctl start backup-rotator- Check status:
sudo systemctl status backup-rotator
sudo journalctl -u backup-rotator -fNote: The service runs as root by default since it needs access to /backups. Adjust User and Group in the service file if your backup directory has different permissions.
Add to crontab:
# Run backup rotation daily at 3 AM
0 3 * * * cd /opt/backup-rotator && source .venv/bin/activate && python run.py --config /etc/backup-rotator/config.yaml --execute --oncetail -f logs/backup-rotator.logpython run.py --config config.yaml.local --onceUse Python to test your filename patterns:
import re
pattern = r'^\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2}\.sql\.gz\.gpg$'
filename = '2025-11-25_20:00:01.sql.gz.gpg'
print(bool(re.match(pattern, filename)))MIT