100 lines
3.0 KiB
Ruby
100 lines
3.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'English'
|
|
require 'tty-prompt'
|
|
require 'tty-progressbar'
|
|
require 'sequel'
|
|
require 'open3'
|
|
|
|
config = JSON.parse(File.read('config.json'))
|
|
host = config['mysql']['host']
|
|
username = config['mysql']['username']
|
|
password = config['mysql']['password']
|
|
b2_bucket_name = config['b2']&.dig('bucket_name')
|
|
|
|
prompt = TTY::Prompt.new
|
|
|
|
# Connect to the SQLite database
|
|
db = Sequel.sqlite('backups.db')
|
|
|
|
# Get a reference to the backups table
|
|
backups_table = db[:backups]
|
|
|
|
# Query the database for remote and local backups
|
|
remote_backups = backups_table.where(backup_type: 'remote').map(:backup_name)
|
|
local_backups = backups_table.where(backup_type: 'local').map(:backup_name)
|
|
|
|
# Define the categories with the queried lists
|
|
choices = [
|
|
{
|
|
name: 'Remote Backups',
|
|
choices: remote_backups
|
|
},
|
|
{
|
|
name: 'Local Backups',
|
|
choices: local_backups
|
|
}
|
|
]
|
|
|
|
user_choice = prompt.select('Choose a backup to restore:', choices)
|
|
|
|
# Determine if the chosen backup is local or remote
|
|
backup_type = backups_table.where(backup_name: user_choice).get(:backup_type)
|
|
|
|
if backup_type == 'remote'
|
|
# Download the remote backup from B2
|
|
`./b2 download-file-by-name #{b2_bucket_name} #{user_choice} ./#{user_choice}`
|
|
end
|
|
|
|
# Perform a "dry run" of the SQL script
|
|
output = `mysql --host=#{host} --user=#{username} --password=#{password} #{user_choice}
|
|
--execute="START TRANSACTION; SOURCE #{user_choice}; ROLLBACK;"`
|
|
if $CHILD_STATUS.success?
|
|
prompt.say('SQL file passed the dry run.')
|
|
else
|
|
prompt.say("Error in SQL file: #{output}")
|
|
exit 1
|
|
end
|
|
|
|
# Ask the user for confirmation before dropping the database
|
|
if prompt.yes?("Are you sure you want to drop the database and restore it from the backup #{user_choice}?")
|
|
`mysql --host=#{host} --user=#{username} --password=#{password} --execute="DROP DATABASE IF EXISTS #{user_choice};
|
|
CREATE DATABASE #{user_choice};"`
|
|
else
|
|
prompt.say('Database restoration cancelled.')
|
|
exit 0
|
|
end
|
|
|
|
# Create a progress bar
|
|
total_size = File.size(user_choice)
|
|
bar = TTY::ProgressBar.new('Restoring [:bar] :percent', total: total_size)
|
|
|
|
# Open the SQL file and the MySQL process
|
|
File.open(user_choice) do |file|
|
|
sql_chunk = ''
|
|
file.each_line do |line|
|
|
sql_chunk += line
|
|
# If the line ends with a semicolon and the chunk size is over 1024 bytes, execute the chunk
|
|
next unless line.strip.end_with?(';') && sql_chunk.bytesize >= 1024
|
|
|
|
# Write the chunk to the MySQL process
|
|
`mysql --host=#{host} --user=#{username} --password=#{password} #{user_choice} --execute="#{sql_chunk}"`
|
|
# Update the progress bar
|
|
bar.advance(sql_chunk.bytesize)
|
|
sql_chunk = ''
|
|
end
|
|
# Write the remaining SQL commands to the MySQL process
|
|
unless sql_chunk.empty?
|
|
`mysql --host=#{host} --user=#{username} --password=#{password} #{user_choice} --execute="#{sql_chunk}"`
|
|
bar.advance(sql_chunk.bytesize)
|
|
end
|
|
end
|
|
|
|
if $CHILD_STATUS.success?
|
|
prompt.say('Import completed successfully.')
|
|
else
|
|
prompt.say("Error during import: #{output}")
|
|
end
|
|
|
|
prompt.say("Backup #{user_choice} restored successfully.")
|