A flexible Python-based backup solution for MySQL databases, PostgreSQL databases, and file systems. Create automated backups with configurable settings through a simple YAML configuration file.
- MySQL Database Backups: Automated mysqldump with optional compression
- PostgreSQL Database Backups: Automated pg_dump with optional compression
- File/Directory Backups: Create gzipped tarballs of specified paths
- YAML Configuration: Centralized configuration management
- Dry Run by Default: Preview mode is the default; pass
--executeto actually write backups - Organized Storage: Automatic organization by year
- Command-line Interface: Simple CLI with flexible options
- Python 3.7+
- Poetry (for dependency management)
- MySQL client tools (
mysqldump, for MySQL backups) - PostgreSQL client tools (
pg_dump, for PostgreSQL backups) - gzip (for compression)
- Clone the repository:
git clone <repository-url>
cd yet-another-python-backup-script- Install dependencies using Poetry:
poetry installAlternatively, install dependencies manually:
pip install pyyamlCreate or edit config.yaml in the project root with your backup settings:
# MySQL Backup Configuration
mysql:
host: localhost
port: 3306
username: backup_user
# password is read from $YAP_MYSQL_PASSWORD (see .envrc.example) unless set here
compress: true
databases:
- database1
- database2
- database3
# PostgreSQL Backup Configuration (optional section)
postgresql:
host: localhost
port: 5432
username: backup_user
# password is read from $YAP_POSTGRES_PASSWORD (see .envrc.example) unless set here
compress: true
databases:
- database1
# Backup Root Path Configuration
backup:
root_path: /path/to/backup/destination
# Backups will be organized by year in subdirectories
# File/Directory Backup Configuration
file_backups:
sources:
- /home/user/documents
- /home/user/projects
- /etc/important-configs
- /opt/application-data
# Logging Configuration (optional section)
logging:
level: INFO
file: logs/yap-backs.log
# CLI --log-level/--log-file override these if given.host: MySQL server hostname (default: localhost)port: MySQL server port (default: 3306)username: MySQL username for authenticationpassword: MySQL password (optional — preferYAP_MYSQL_PASSWORDin.envrc)compress: Enable gzip compression for dump files (default: true)databases: List of database names to backup
host: PostgreSQL server hostname (default: localhost)port: PostgreSQL server port (default: 5432)username: PostgreSQL username for authenticationpassword: PostgreSQL password (optional — preferYAP_POSTGRES_PASSWORDin.envrc)compress: Enable gzip compression for dump files (default: true)databases: List of database names to backup- Omit this whole section if you have no PostgreSQL databases to back up
root_path: Root directory where backups will be stored- Backups are automatically organized into year subdirectories
sources: List of file or directory paths to backup- Each path will be archived into a separate gzipped tarball
level: Logging level —DEBUG,INFO,WARNING, orERROR(default:INFO)file: Log file path (default:logs/yap-backs.log)- CLI flags
--log-level/--log-filetake priority over these if given
Run in dry-run mode (default; preview without creating files):
poetry run python bin/yap-backs.pyActually write backups:
poetry run python bin/yap-backs.py --executeUse a custom configuration file:
poetry run python bin/yap-backs.py --config /path/to/custom-config.yaml --executeCombine options:
poetry run python bin/yap-backs.py --config prod-config.yaml --execute--config,-c: Path to configuration file (default:config.yaml)--execute,-x: Actually perform the backup (default is a dry run; no files are written without this flag)--log-level,-l: Logging level,DEBUG/INFO/WARNING/ERROR(default: fromconfig.yaml'slogging.level, elseINFO)--log-file: Log file path (default: fromconfig.yaml'slogging.file, elselogs/yap-backs.log)--help,-h: Show help message and exit
Backups are organized with the following structure:
/backup/root/path/
└── 2025/
├── mysql_backups/
│ ├── database1_20250131_1430.sql.gz
│ ├── database2_20250131_1430.sql.gz
│ └── database3_20250131_1430.sql.gz
├── postgresql_backups/
│ └── database1_20250131_1430.sql.gz
└── backup-home-user-documents_2025-01-31_1430.tar.gz
└── backup-home-user-projects_2025-01-31_1430.tar.gz
└── backup-etc-important-configs_2025-01-31_1430.tar.gz
Create a cron job for daily backups at 2 AM:
0 2 * * * cd /path/to/yet-another-python-backup-script && /usr/local/bin/poetry run python bin/yap-backs.py --executeNote: --execute is required — without it the script only runs a dry-run preview and writes nothing.
Maintain separate configurations for different environments:
# Production backups
poetry run python bin/yap-backs.py --config config-prod.yaml
# Development backups
poetry run python bin/yap-backs.py --config config-dev.yamlAlways test new configurations with dry-run first (the default with no flags):
poetry run python bin/yap-backs.py --config new-config.yaml-
Credentials via .envrc: Database passwords are not stored in
config.yaml. SetYAP_MYSQL_PASSWORDandYAP_POSTGRES_PASSWORDin.envrc(loaded via direnv; see.envrc.example). Ensure.envrchas restricted permissions:chmod 600 .envrc
-
Use Dedicated Backup User (MySQL): Create a MySQL user with minimal required permissions:
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'secure_password'; GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup_user'@'localhost';
-
Use a Read-Only Role (PostgreSQL): Rather than a superuser, grant the backup user the built-in read-only role (PostgreSQL 14+) — this covers every current and future database on the cluster without per-database grants:
GRANT pg_read_all_data TO backup_user;Avoid using the
postgressuperuser account for backups — it bypasses all permission checks (read, write, DDL) across the whole cluster, which is a much larger blast radius than a backup tool needs. -
Secure Backup Storage: Ensure backup destination has appropriate access controls
-
Don't Commit Secrets:
config.yamland.envrcare already in.gitignore; keep it that way
If you encounter MySQL connection errors, verify:
- MySQL credentials are correct
- MySQL server is running and accessible
- User has appropriate permissions
- Host and port settings are correct
pg_dump fails with permission denied for table ... when the configured
user can connect but lacks SELECT on that database's tables — common when
a database was provisioned with its own app-specific owning role. Fix by
granting the backup user the read-only role (see Security Considerations
above): GRANT pg_read_all_data TO backup_user;. This is a one-time,
cluster-wide grant — no need to repeat it per database.
Also verify:
- PostgreSQL server is running and accessible on the configured host/port
- Credentials are correct (
YAP_POSTGRES_PASSWORDin.envrc, orpasswordin config.yaml)
If you encounter permission errors:
- Ensure the script has read access to source directories
- Ensure the script has write access to backup destination
- Check that the MySQL/PostgreSQL user has necessary database privileges
- The script exits non-zero on any dump/tarball failure — check the log
file (default
logs/yap-backs.log) forERRORlines rather than assuming a completed run means everything succeeded
If you get import errors:
poetry install
# or
pip install pyyaml