feat: Enhance MySQL backup script with

customizable retention and logging

    Refactor backup method to dump each database individually
    Update delete_old_backups method to remove local backups based on user-defined retention days
    Add Loggman class for logging program actions to a logfile and implement log deletion for logs older than a week
    Modify MysqlDatabaseConfig class to ask the user for local and B2 backup retention days
    Update MysqlDatabaseBackup class to use user-defined retention days for local backups
    Refactor upload_to_b2 method to delete B2 backups based on user-defined retention days
    Implement various code improvements and refactoring for better readability and maintainability
This commit is contained in:
connorc@orbitnode.net 2023-03-19 16:49:18 -06:00
parent 5b9ce0e341
commit 3342da13c6
4 changed files with 218 additions and 149 deletions

44
loggman.rb Normal file
View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'time'
require 'fileutils'
# Loggman class
class Loggman
def initialize(logfile)
@logfile = logfile
end
def log(message, level = :info)
delete_old_logs
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
File.open(@logfile, 'a') do |file|
file.puts "[#{timestamp}] [#{level.to_s.upcase}] #{message}"
end
end
def info(message)
log(message, :info)
end
def warn(message)
log(message, :warn)
end
def error(message)
log(message, :error)
end
def debug(message)
log(message, :debug)
end
def delete_old_logs
max_age_days = 7
max_age_seconds = max_age_days * 24 * 60 * 60
if File.exist?(@logfile) && Time.now - File.mtime(@logfile) > max_age_seconds
FileUtils.rm(@logfile)
end
end
end

View File

@ -14,29 +14,40 @@ class MysqlDatabaseBackup
@b2_key_id = config['b2']&.dig('key_id') @b2_key_id = config['b2']&.dig('key_id')
@b2_application_key = config['b2']&.dig('application_key') @b2_application_key = config['b2']&.dig('application_key')
@b2_bucket_name = config['b2']&.dig('bucket_name') @b2_bucket_name = config['b2']&.dig('bucket_name')
@local_retention_days = config['local_retention_days'] || 30
@b2_retention_days = config['b2']&.dig('retention_days') || 30
end end
def backup def backup
puts 'Backing up sql' puts 'Backing up sql'
timestamp = Time.now.strftime('%Y-%m-%d_%H-%M-%S') timestamp = Time.now.strftime('%Y-%m-%d_%H-%M-%S')
puts "Timestamp = #{timestamp}" puts "Timestamp = #{timestamp}"
backup_file = File.join(@backup_dir, "database-backup_#{timestamp}.sql")
databases = get_databases
databases.each do |database_name|
backup_file = File.join(@backup_dir, "#{database_name}_#{timestamp}.sql")
puts "backup_file = #{backup_file}" puts "backup_file = #{backup_file}"
puts "MySQL Info = #{@host} #{@username} #{@password} #{backup_file}" puts "MySQL Info = #{@host} #{@username} #{@password} #{backup_file}"
`mysqldump --host=#{@host} --user=#{@username} --password='#{@password}' --all-databases > #{backup_file}` `mysqldump --host=#{@host} --user=#{@username} --password='#{@password}' --databases #{database_name} > #{backup_file}`
delete_old_backups delete_old_backups
return unless @b2_enabled upload_to_b2(backup_file) if @b2_enabled
end
end
upload_to_b2(backup_file) def get_databases
databases_output = `mysql --host=#{@host} --user=#{@username} --password='#{@password}' --execute='SHOW DATABASES;'`
databases = databases_output.split("\n")[1..] # Ignore the first line (header)
databases.reject { |db| %w[information_schema performance_schema mysql sys].include?(db) }
end end
def delete_old_backups def delete_old_backups
max_age_hours = 48 max_age_days = @local_retention_days
max_age_seconds = max_age_hours * 60 * 60 max_age_seconds = max_age_days * 24 * 60 * 60
backups = Dir[File.join(@backup_dir, 'database-backup_*.sql')] backups = Dir[File.join(@backup_dir, '*_*.sql')]
return if backups.empty? return if backups.empty?
@ -53,23 +64,33 @@ class MysqlDatabaseBackup
def upload_to_b2(backup_file) def upload_to_b2(backup_file)
b2_file_name = File.basename(backup_file) b2_file_name = File.basename(backup_file)
b2_file_url = "b2://#{@b2_bucket_name}/#{b2_file_name}" 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_files = `b2 ls #{@b2_bucket_name}`
unless existing_files.empty?
existing_files.each_line do |line|
file_name = line.split(' ').last.strip
next unless file_name != b2_file_name
file_id = line.match(/"fileId": "([^"]+)"/)[1]
`b2 delete-file-version #{@b2_bucket_name} #{file_name} #{file_id}`
puts "Deleted existing backup file from B2 bucket: #{file_name}"
end
end
# Upload the backup file to the B2 bucket # Upload the backup file to the B2 bucket
`b2 upload-file #{@b2_bucket_name} #{backup_file} #{b2_file_name}` `b2 upload-file #{@b2_bucket_name} #{backup_file} #{b2_file_name}`
puts "Uploaded backup file to B2 bucket: #{b2_file_url}" puts "Uploaded backup file to B2 bucket: #{b2_file_url}"
end
# Calculate the cutoff date based on b2_retention_days
max_age_days = @b2_retention_days
cutoff_date = Time.now - (max_age_days * 24 * 60 * 60)
existing_files = `b2 ls #{@b2_bucket_name}`
return if existing_files.empty?
existing_files.each_line do |line|
file_name = line.split(' ').last.strip
file_timestamp_str = file_name.match(/_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.sql/)&.captures&.first
# Skip files that don't match the expected filename format
next if file_timestamp_str.nil?
file_timestamp = Time.strptime(file_timestamp_str, '%Y-%m-%d_%H-%M-%S')
next unless file_timestamp < cutoff_date
file_id = line.match(/"fileId": "([^"]+)"/)[1]
`b2 delete-file-version #{@b2_bucket_name} #{file_name} #{file_id}`
puts "Deleted old backup file from B2 bucket: #{file_name}"
end
end
end end

View File

@ -19,25 +19,29 @@ class MysqlDatabaseConfig
backup_dir = prompt('Backup Directory', default: '.') backup_dir = prompt('Backup Directory', default: '.')
local_retention_days = prompt('Local backup retention in days (1 day minimum)', default: '1').to_i
@config = { @config = {
'mysql' => { 'mysql' => {
'host' => mysql_host, 'host' => mysql_host,
'username' => mysql_username, 'username' => mysql_username,
'password' => mysql_password 'password' => mysql_password
}, },
'backup_dir' => backup_dir 'backup_dir' => backup_dir,
'local_retention_days' => local_retention_days
} }
b2_enabled = prompt_bool('Enable Backblaze B2?', default: false) b2_enabled = prompt_bool('Enable Backblaze B2?', default: false)
@config['b2_enabled'] = b2_enabled @config['b2_enabled'] = b2_enabled
if b2_enabled if b2_enabled
@b2_key_id = prompt('B2 Key ID') @b2_key_id = prompt('B2 Key ID')
@b2_application_key = prompt('B2 Application Key') @b2_application_key = prompt('B2 Application Key')
@b2_bucket_name = prompt('B2 Bucket Name') @b2_bucket_name = prompt('B2 Bucket Name')
b2_retention_days = prompt('B2 backup retention in days (1 day minimum)', default: '1').to_i
@config['b2'] = { @config['b2'] = {
'key_id' => @b2_key_id, 'key_id' => @b2_key_id,
'application_key' => @b2_application_key, 'application_key' => @b2_application_key,
'bucket_name' => @b2_bucket_name 'bucket_name' => @b2_bucket_name,
'retention_days' => b2_retention_days
} }
end end