diff --git a/gemfile b/gemfile new file mode 100644 index 0000000..172fa14 --- /dev/null +++ b/gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'rubocop', '1.46.0' +gem 'json', '2.6.3' diff --git a/gemfile.lock b/gemfile.lock new file mode 100644 index 0000000..11291f2 --- /dev/null +++ b/gemfile.lock @@ -0,0 +1,35 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + json (2.6.3) + parallel (1.22.1) + parser (3.2.1.0) + ast (~> 2.4.1) + rainbow (3.1.1) + regexp_parser (2.7.0) + rexml (3.2.5) + rubocop (1.46.0) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.2.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.26.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.26.0) + parser (>= 3.2.1.0) + ruby-progressbar (1.11.0) + unicode-display_width (2.4.2) + +PLATFORMS + x64-mingw-ucrt + +DEPENDENCIES + json (= 2.6.3) + rubocop (= 1.46.0) + +BUNDLED WITH + 2.4.6 diff --git a/mysql_database_backup.rb b/mysql_database_backup.rb new file mode 100644 index 0000000..fcd4fea --- /dev/null +++ b/mysql_database_backup.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'json' + +# class for creating, managing and deleting backups both locally and in B2 +class MysqlDatabaseBackup + def initialize(config_file) + config = JSON.parse(File.read(config_file)) + @host = config['mysql']['host'] + @username = config['mysql']['username'] + @password = config['mysql']['password'] + @backup_dir = config['backup_dir'] || '.' + @b2_enabled = config['b2_enabled'] || false + @b2_key_id = config['b2']['key_id'] + @b2_application_key = config['b2']['application_key'] + @b2_bucket_name = config['b2']['bucket_name'] + end + + def backup + timestamp = Time.now.strftime('%Y-%m-%d_%H-%M-%S') + backup_file = File.join(@backup_dir, "database-backup_#{timestamp}.sql") + + `mysqldump --host=#{@host} --user=#{@username} --password=#{@password} --all-databases > #{backup_file}` + + delete_old_backups + + return unless @b2_enabled + + upload_to_b2(backup_file) + + end + + def delete_old_backups + max_age_hours = 48 + max_age_seconds = max_age_hours * 60 * 60 + backups = Dir[File.join(@backup_dir, 'database-backup_*.sql')] + + return if backups.empty? + + backups.each do |backup| + age_seconds = Time.now - File.mtime(backup) + + if age_seconds > max_age_seconds + puts "Deleted old backup: #{backup}" + File.delete(backup) + end + end + end + + def upload_to_b2(backup_file) + b2_file_name = File.basename(backup_file) + b2_file_url = "b2://#{@b2_bucket_name}/#{b2_file_name}" + + # Check if a backup file with the same name already exists in the B2 bucket + existing_file = `b2 list-file-names #{@b2_bucket_name} #{b2_file_name}` + if existing_file.include?(b2_file_name) + # Delete the existing backup file from the B2 bucket + `b2 delete-file-version #{b2_file_url}` + puts "Deleted existing backup file from B2 bucket: #{b2_file_url}" + end + + # Upload the backup file to the B2 bucket + `b2 upload-file #{@b2_bucket_name} #{backup_file} #{b2_file_name}` + puts "Uploaded backup file to B2 bucket: #{b2_file_url}" + end +end diff --git a/mysql_database_config.rb b/mysql_database_config.rb new file mode 100644 index 0000000..fa2e6c6 --- /dev/null +++ b/mysql_database_config.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# class for generating the mysql config if it doesn't exist +require 'json' + +# class for generating our config +class MysqlDatabaseConfig + def initialize(config_file) + @config_file = config_file + end + + def generate + if File.exist?(@config_file) + puts 'Config file already exists, skipping generation.' + else + mysql_host = prompt('MySQL Host') + mysql_username = prompt('MySQL Username') + mysql_password = prompt('MySQL Password') + + backup_dir = prompt('Backup Directory', default: '.') + + b2_enabled = prompt_bool('Enable Backblaze B2?', default: false) + if b2_enabled + @b2_key_id = prompt('Backblaze B2 Key ID') + @b2_application_key = prompt('Backblaze B2 Application Key') + @b2_bucket_name = prompt('Backblaze B2 Bucket Name') + end + + config = { + 'mysql' => { + 'host' => mysql_host, + 'username' => mysql_username, + 'password' => mysql_password + }, + 'backup_dir' => backup_dir + } + + if b2_enabled + config['b2'] = { + 'key_id' => @b2_key_id, + 'application_key' => @b2_application_key, + 'bucket_name' => @b2_bucket_name + } + end + + File.write(@config_file, JSON.pretty_generate(config)) + puts "Config file generated: #{@config_file}" + end + end + + private + + def prompt(message, default: nil) + print message.to_s + print " [#{default}]" if default + print ': ' + value = gets.chomp + value.empty? ? default : value + end + + def prompt_bool(message, default: false) + prompt("#{message} (y/n)", default: default) =~ /y|yes/i + end +end diff --git a/starter.rb b/starter.rb new file mode 100644 index 0000000..ce3d691 --- /dev/null +++ b/starter.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_relative 'mysql_database_config' +require_relative 'mysql_database_backup' + +config_file = 'config.json' + +config_generator = MysqlDatabaseConfig.new(config_file) +config_generator.generate + +backup = MysqlDatabaseBackup.new(config_file) +backup.backup