From 6095bd2d16dae963dffe1c1efc8e804b157df40d Mon Sep 17 00:00:00 2001 From: Marvin Sanders Date: Fri, 3 Nov 2023 11:13:48 +0100 Subject: [PATCH] main --- README.md | 44 +++++++++++++++ backup_config.json | 37 +++++++++++++ backup_script.py | 132 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 README.md create mode 100644 backup_config.json create mode 100644 backup_script.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..71f99d5 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Hypernode Backup Script + +## Overview +This backup script is designed specifically for use on the Hypernode platform. It facilitates secure backup of files and MySQL databases and can be configured to store backups locally and/or send to a remote server via SSH. + +## Features +- Automated backup of files and databases. +- Support for daily, weekly, and monthly backup schedules. +- Cleanup functionality to remove old backups based on retention policy. +- Option to disable local backup storage and SSH/FTP transfer. +- Detailed logging of all backup activities. + +## Configuration +Customize the `backup_config.json` file to suit your backup needs. This is where you can set: +- SSH and MySQL connection details. +- Backup paths and schedules. +- Retention policy for backups. + +## Installation +1. Clone the repository to your Hypernode server. +2. Install any required external commands (`tar`, `rsync`, `mysqldump`). +3. Configure your backup preferences in `backup_config.json`. +4. Set up a cron job to run the script according to your schedule. + +## Usage +The script is intended to run as a cron job but can be started manually with: + +```bash +python3 backup_script.py +``` + +## Cron Job Setup +To schedule the backup script to run automatically, add the following line to your crontab: +Make sure to adjust the paths to match the location of your script and working directory. + +```cron +0 * * * * sleep 7; cd /data/web/backup/ && /usr/bin/python3 /data/web/backup/backup_script.py # noflock +``` + +## Disclaimer +This script is not officially supported by the Hypernode support team. Users should implement this script at their own risk and are responsible for properly configuring and securing their backup processes. + +## License +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/backup_config.json b/backup_config.json new file mode 100644 index 0000000..50384de --- /dev/null +++ b/backup_config.json @@ -0,0 +1,37 @@ +{ + "ssh": { + "host": "example.com", + "port": 22, + "user": "username", + "location": "/path/on/remote/server" + }, + "local": { + "source_folder": "/path/to/source/folder", + "backup_folder": "/path/to/store/backups" + }, + "mysql": { + "host": "db.example.com", + "user": "dbuser", + "password": "dbpassword", + "database": "dbname" + }, + "schedule": { + "daily": [3, 8, 13, 22], + "weekly": { + "weekday": 4, + "hour": 17 + }, + "monthly": { + "weekday": 0, + "day_range": [1, 7], + "hour": 17 + } + }, + "retention": { + "daily": 4, + "weekly": 30, + "monthly": 3 + }, + "disable_local_save": false, + "disable_ssh_ftp": false +} diff --git a/backup_script.py b/backup_script.py new file mode 100644 index 0000000..2ee7a25 --- /dev/null +++ b/backup_script.py @@ -0,0 +1,132 @@ +import os +import subprocess +import datetime +import json +import logging +import glob + +# Load configuration from file +with open('backup_config.json', 'r') as config_file: + config = json.load(config_file) + +# Ensure backup folder exists +if not os.path.exists(config["local"]["backup_folder"]): + os.makedirs(config["local"]["backup_folder"]) + +# Setup logging +log_file = os.path.join(config["local"]["backup_folder"], "backup_log.txt") +logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +def backup_files(backup_type): + if config.get('disable_local_save'): + logging.info("Local backup saving is disabled.") + return None + + try: + backup_name = f"{backup_type}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.tar.gz" + backup_path = os.path.join(config["local"]["backup_folder"], backup_name) + + subprocess.run(['tar', '-czf', backup_path, config["local"]["source_folder"]]) + + if not config.get('disable_ssh_ftp'): + rsync_command = [ + 'rsync', + '-avz', + '-e', f'ssh -p {config["ssh"]["port"]}', + backup_path, + f'{config["ssh"]["user"]}@{config["ssh"]["host"]}:{config["ssh"]["location"]}' + ] + subprocess.run(rsync_command) + + logging.info(f"{backup_type} file backup-up successful.") + return backup_name + + except Exception as e: + logging.error(f"Error during {backup_type} file backup: {str(e)}") + return None + +def backup_mysql(backup_type): + if config.get('disable_local_save'): + logging.info("Local backup saving is disabled.") + return None + + try: + backup_name = f"{backup_type}_db_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.sql.gz" + backup_path = os.path.join(config["local"]["backup_folder"], backup_name) + + dump_command = [ + 'mysqldump', + '--single-transaction', + '-h', config["mysql"]["host"], + '-u', config["mysql"]["user"], + f'--password={config["mysql"]["password"]}', + config["mysql"]["database"], + '|', 'gzip', '>', backup_path + ] + subprocess.run(' '.join(dump_command), shell=True) + + if not config.get('disable_ssh_ftp'): + rsync_command = [ + 'rsync', + '-avz', + '-e', f'ssh -p {config["ssh"]["port"]}', + backup_path, + f'{config["ssh"]["user"]}@{config["ssh"]["host"]}:{config["ssh"]["location"]}' + ] + subprocess.run(rsync_command) + + logging.info(f"{backup_type} database backup-up successful.") + return backup_name + + except Exception as e: + logging.error(f"Error during {backup_type} database backup: {str(e)}") + return None + +def cleanup_backups(config): + try: + for backup_type, retention_period in config["retention"].items(): + file_backup_pattern = os.path.join(config["local"]["backup_folder"], f"{backup_type}_*.tar.gz") + db_backup_pattern = os.path.join(config["local"]["backup_folder"], f"{backup_type}_db_*.sql.gz") + + file_backups = sorted(glob.glob(file_backup_pattern)) + db_backups = sorted(glob.glob(db_backup_pattern)) + + delete_file_count = max(0, len(file_backups) - retention_period) + delete_db_count = max(0, len(db_backups) - retention_period) + + for i in range(delete_file_count): + os.remove(file_backups[i]) + logging.info(f"Cleaned up old file backup: {file_backups[i]}") + + for i in range(delete_db_count): + os.remove(db_backups[i]) + logging.info(f"Cleaned up old database backup: {db_backups[i]}") + + except Exception as e: + logging.error(f"Error during cleanup: {str(e)}") + +def main(): + now = datetime.datetime.now() + + if now.hour in config["schedule"]["daily"]: + backup_type = "daily" + elif now.weekday() == config["schedule"]["weekly"]["weekday"] and now.hour == config["schedule"]["weekly"]["hour"]: + backup_type = "weekly" + elif (config["schedule"]["monthly"]["day_range"][0] <= now.day <= config["schedule"]["monthly"]["day_range"][1]) and now.weekday() == config["schedule"]["monthly"]["weekday"] and now.hour == config["schedule"]["monthly"]["hour"]: + backup_type = "monthly" + else: + logging.info("No Backups for this time frame.") + return + + logging.info(f"Starting {backup_type} file back-up...") + backup_files(backup_type) + + logging.info(f"Starting {backup_type} database back-up...") + backup_mysql(backup_type) + + logging.info("Starting cleanup old back-ups...") + cleanup_backups(config) + logging.info("Cleanup successful.") + +if __name__ == "__main__": + main()