backup_nexus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +82 -0
- data/Rakefile +10 -0
- data/app/controllers/backup_nexus/application_controller.rb +31 -0
- data/app/controllers/backup_nexus/backup_controller.rb +119 -0
- data/app/helpers/backup_nexus/application_helper.rb +51 -0
- data/app/jobs/backup_nexus/backup_job.rb +18 -0
- data/app/models/backup_nexus/backup.rb +107 -0
- data/app/models/backup_nexus/backup_config.rb +189 -0
- data/app/services/backup_nexus/backup_runner.rb +62 -0
- data/app/services/backup_nexus/backup_service.rb +817 -0
- data/app/views/backup_nexus/backup/_form.html.erb +334 -0
- data/app/views/backup_nexus/backup/config_history.html.erb +67 -0
- data/app/views/backup_nexus/backup/edit.html.erb +15 -0
- data/app/views/backup_nexus/backup/history.html.erb +75 -0
- data/app/views/backup_nexus/backup/index.html.erb +112 -0
- data/app/views/backup_nexus/backup/new.html.erb +15 -0
- data/backup_nexus.gemspec +30 -0
- data/config/routes.rb +15 -0
- data/db/seeds/backup_configs_safe.rb +61 -0
- data/lib/backup_nexus/base_record.rb +7 -0
- data/lib/backup_nexus/configuration.rb +19 -0
- data/lib/backup_nexus/engine.rb +51 -0
- data/lib/backup_nexus/version.rb +3 -0
- data/lib/backup_nexus.rb +26 -0
- data/lib/generators/backup_nexus/backup_generator.rb +70 -0
- data/lib/generators/backup_nexus/install_generator.rb +27 -0
- data/lib/generators/backup_nexus/templates/backup/backup/backup_helper.sh +152 -0
- data/lib/generators/backup_nexus/templates/backup/backup/config.rb +27 -0
- data/lib/generators/backup_nexus/templates/backup/backup/models/daily_backup.rb +24 -0
- data/lib/generators/backup_nexus/templates/backup/backup/models/full_backup.rb +35 -0
- data/lib/generators/backup_nexus/templates/backup/backup/models/sync_backup.rb +43 -0
- data/lib/generators/backup_nexus/templates/backup/backup/mysql-config/db_config.cnf +15 -0
- data/lib/generators/backup_nexus/templates/backup/backup/schedule.rb +28 -0
- data/lib/generators/backup_nexus/templates/migration.rb +105 -0
- data/lib/tasks/backup_nexus.rake +81 -0
- metadata +138 -0
data/config/routes.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
BackupNexus::Engine.routes.draw do
|
|
4
|
+
get "backup", to: "backup#index", as: :backup
|
|
5
|
+
get "backup/new", to: "backup#new", as: :new_backup
|
|
6
|
+
post "backup", to: "backup#create"
|
|
7
|
+
get "backup/history", to: "backup#all_history", as: :backup_history
|
|
8
|
+
get "backup/:id/edit", to: "backup#edit", as: :edit_backup
|
|
9
|
+
patch "backup/:id", to: "backup#update"
|
|
10
|
+
delete "backup/:id", to: "backup#destroy", as: :backup_destroy
|
|
11
|
+
post "backup/:id/trigger", to: "backup#trigger", as: :backup_trigger
|
|
12
|
+
get "backup/:id/history", to: "backup#history", as: :backup_config_history
|
|
13
|
+
|
|
14
|
+
root to: "backup#index"
|
|
15
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Safe example backup configuration.
|
|
4
|
+
#
|
|
5
|
+
# This seed intentionally contains no real hosts, usernames, paths, or
|
|
6
|
+
# passwords. Keep application-specific values in environment variables or
|
|
7
|
+
# Rails encrypted credentials, never in a committed seed file.
|
|
8
|
+
#
|
|
9
|
+
# Run with:
|
|
10
|
+
# rails runner db/seeds/backup_configs_safe.rb
|
|
11
|
+
#
|
|
12
|
+
# Or from the engine:
|
|
13
|
+
# backup_nexus:seed_backups
|
|
14
|
+
|
|
15
|
+
configs = [
|
|
16
|
+
{
|
|
17
|
+
name: "local_backup_example",
|
|
18
|
+
description: "Disabled local backup example; configure before enabling",
|
|
19
|
+
adapter: "mysql",
|
|
20
|
+
database_name: "app_development",
|
|
21
|
+
host: "localhost",
|
|
22
|
+
port: 3306,
|
|
23
|
+
username: "ENV[DATABASE_USERNAME]",
|
|
24
|
+
password: "ENV[DATABASE_PASSWORD]",
|
|
25
|
+
skip_tables: [],
|
|
26
|
+
|
|
27
|
+
storage_path: Rails.root.join("tmp", "backup_nexus_backups").to_s,
|
|
28
|
+
keep_count: 7,
|
|
29
|
+
|
|
30
|
+
compress: true,
|
|
31
|
+
bzip2_compress: false,
|
|
32
|
+
encrypted: false,
|
|
33
|
+
gpg_enabled: false,
|
|
34
|
+
|
|
35
|
+
rsync_enabled: false,
|
|
36
|
+
s3_enabled: false,
|
|
37
|
+
archive_enabled: false,
|
|
38
|
+
|
|
39
|
+
notify_on_success: false,
|
|
40
|
+
notify_on_failure: false,
|
|
41
|
+
email_notify: false,
|
|
42
|
+
|
|
43
|
+
schedule_cron: "",
|
|
44
|
+
enabled: false
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
puts "Seeding #{configs.size} safe backup config..."
|
|
49
|
+
|
|
50
|
+
configs.each do |attrs|
|
|
51
|
+
config = BackupNexus::BackupConfig.find_or_initialize_by(name: attrs[:name])
|
|
52
|
+
config.assign_attributes(attrs)
|
|
53
|
+
|
|
54
|
+
if config.save
|
|
55
|
+
puts " ✓ #{config.name} (#{config.adapter}/#{config.database_name})"
|
|
56
|
+
else
|
|
57
|
+
puts " ✗ #{config.name}: #{config.errors.full_messages.join(', ')}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
puts "Done. #{BackupNexus::BackupConfig.count} configs total."
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BackupNexus
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :enabled
|
|
6
|
+
attr_accessor :database_url
|
|
7
|
+
attr_accessor :database_name
|
|
8
|
+
attr_accessor :database_yml
|
|
9
|
+
attr_accessor :table_prefix
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@enabled = false
|
|
13
|
+
@database_url = nil
|
|
14
|
+
@database_name = nil
|
|
15
|
+
@database_yml = nil
|
|
16
|
+
@table_prefix = "backup_nexus_"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake"
|
|
4
|
+
|
|
5
|
+
module BackupNexus
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
isolate_namespace BackupNexus
|
|
8
|
+
|
|
9
|
+
engine_name "backup_nexus"
|
|
10
|
+
|
|
11
|
+
initializer "backup_nexus.database" do
|
|
12
|
+
ActiveSupport.on_load(:active_record) do
|
|
13
|
+
config = BackupNexus.configuration
|
|
14
|
+
|
|
15
|
+
if config.database_url.present?
|
|
16
|
+
uri = URI.parse(config.database_url)
|
|
17
|
+
BackupNexus::BaseRecord.establish_connection(
|
|
18
|
+
adapter: uri.scheme,
|
|
19
|
+
host: uri.host,
|
|
20
|
+
port: uri.port&.to_i,
|
|
21
|
+
username: URI.decode_www_form_component(uri.user || ""),
|
|
22
|
+
password: URI.decode_www_form_component(uri.password || ""),
|
|
23
|
+
database: uri.path&.gsub(%r{^/}, "")
|
|
24
|
+
)
|
|
25
|
+
elsif config.database_name.present?
|
|
26
|
+
yaml_path = Rails.root.join("config", "database.yml")
|
|
27
|
+
if yaml_path.exist?
|
|
28
|
+
yaml = YAML.safe_load(ERB.new(yaml_path.read).result, aliases: true) || {}
|
|
29
|
+
env_config = yaml[Rails.env.to_s] || yaml[Rails.env]
|
|
30
|
+
if env_config.is_a?(Hash)
|
|
31
|
+
db_config = env_config.symbolize_keys
|
|
32
|
+
db_config[:database] = config.database_name
|
|
33
|
+
BackupNexus::BaseRecord.establish_connection(**db_config)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
elsif config.database_yml.present?
|
|
37
|
+
yaml_path = Rails.root.join(config.database_yml)
|
|
38
|
+
if yaml_path.exist?
|
|
39
|
+
yaml = YAML.safe_load(ERB.new(yaml_path.read).result, aliases: true) || {}
|
|
40
|
+
env_config = yaml[Rails.env.to_s] || yaml[Rails.env]
|
|
41
|
+
BackupNexus::BaseRecord.establish_connection(**env_config.symbolize_keys) if env_config.is_a?(Hash)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
initializer "backup_nexus.rake_tasks" do
|
|
48
|
+
load "tasks/backup_nexus.rake" if Rake.respond_to?(:application)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/backup_nexus.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails"
|
|
4
|
+
require "rails_nexus"
|
|
5
|
+
require "pagy"
|
|
6
|
+
require "uri"
|
|
7
|
+
require "yaml"
|
|
8
|
+
require "erb"
|
|
9
|
+
require "backup_nexus/version"
|
|
10
|
+
require "backup_nexus/configuration"
|
|
11
|
+
require "backup_nexus/base_record"
|
|
12
|
+
require "backup_nexus/engine"
|
|
13
|
+
|
|
14
|
+
module BackupNexus
|
|
15
|
+
class << self
|
|
16
|
+
attr_writer :configuration
|
|
17
|
+
|
|
18
|
+
def configuration
|
|
19
|
+
@configuration ||= Configuration.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def configure
|
|
23
|
+
yield(configuration)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/erb"
|
|
5
|
+
|
|
6
|
+
module BackupNexus
|
|
7
|
+
module Generators
|
|
8
|
+
class BackupGenerator < Rails::Generators::Base
|
|
9
|
+
desc "Set up backup_nexus backup directory structure and models"
|
|
10
|
+
source_root File.expand_path("templates/backup/backup", __dir__)
|
|
11
|
+
|
|
12
|
+
def create_backup_directories
|
|
13
|
+
empty_directory "config/backup"
|
|
14
|
+
empty_directory "config/backup/models"
|
|
15
|
+
empty_directory "config/backup/mysql-config"
|
|
16
|
+
empty_directory "storage/backup_nexus/backups"
|
|
17
|
+
empty_directory "storage/backup_nexus/backups/sync"
|
|
18
|
+
empty_directory "storage/backup_nexus/backups/logs"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_config_template
|
|
22
|
+
template "config.rb", "config/backup/config.rb"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_model_templates
|
|
26
|
+
template "models/daily_backup.rb", "config/backup/models/daily_backup.rb"
|
|
27
|
+
template "models/full_backup.rb", "config/backup/models/full_backup.rb"
|
|
28
|
+
template "models/sync_backup.rb", "config/backup/models/sync_backup.rb"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_schedule_template
|
|
32
|
+
template "schedule.rb", "config/backup/schedule.rb"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create_helper_script
|
|
36
|
+
template "backup_helper.sh", "bin/backup_nexus-backup"
|
|
37
|
+
chmod "bin/backup_nexus-backup", 0o755
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_mysql_config
|
|
41
|
+
template "mysql-config/db_config.cnf", "config/backup/mysql-config/db_config.cnf"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_to_gitignore
|
|
45
|
+
append_to_file ".gitignore", <<~GITIGNORE unless File.exist?(".gitignore") && File.read(".gitignore").include?("backup_nexus/backups")
|
|
46
|
+
\n# BackupNexus backups
|
|
47
|
+
storage/backup_nexus/backups/
|
|
48
|
+
config/backup/mysql-config/db_config.cnf
|
|
49
|
+
GITIGNORE
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def show_readme
|
|
53
|
+
say ""
|
|
54
|
+
say "✅ Backup directory structure created!", :green
|
|
55
|
+
say ""
|
|
56
|
+
say "Next steps:"
|
|
57
|
+
say " 1. Edit config/backup/config.rb with your database credentials"
|
|
58
|
+
say " 2. Customize models in config/backup/models/"
|
|
59
|
+
say " 3. Install the schedule: whenever --write-crontab"
|
|
60
|
+
say " 4. Test: bin/backup_nexus-backup status"
|
|
61
|
+
say ""
|
|
62
|
+
say "Backup models created:"
|
|
63
|
+
say " - daily_backup: Incremental (skips large tables), local + remote"
|
|
64
|
+
say " - full_backup: All tables, local only"
|
|
65
|
+
say " - sync_backup: Remote sync to backup server"
|
|
66
|
+
say ""
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module BackupNexus
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
def create_migration
|
|
11
|
+
migration_template "migration.rb", "db/migrate/create_backup_nexus_tables.rb"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def mount_engine
|
|
15
|
+
route 'mount BackupNexus::Engine => "/backup_nexus", as: "backup_nexus"'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_backup_files
|
|
19
|
+
generate "backup_nexus:backup"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def show_readme
|
|
23
|
+
say "BackupNexus installed at /backup_nexus. Run bin/rails db:migrate next.", :green
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# BackupNexus Backup Helper Script
|
|
3
|
+
# Generated by: rails generate backup_nexus:backup
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ./backup_helper.sh run <model> Run a backup model
|
|
7
|
+
# ./backup_helper.sh list List backup files
|
|
8
|
+
# ./backup_helper.sh status Show backup health status
|
|
9
|
+
# ./backup_helper.sh clean <days> Remove backups older than N days
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
# Configuration
|
|
14
|
+
BACKUP_DIR="${BACKUP_DUMP_PATH:-storage/backup_nexus/backups}"
|
|
15
|
+
BUNDLE_PATH="${BUNDLE_PATH:-.bundle}"
|
|
16
|
+
RUBY_ENV="${RUBY_ENV:-production}"
|
|
17
|
+
|
|
18
|
+
# Colors
|
|
19
|
+
RED='\033[0;31m'
|
|
20
|
+
GREEN='\033[0;32m'
|
|
21
|
+
YELLOW='\033[1;33m'
|
|
22
|
+
NC='\033[0m'
|
|
23
|
+
|
|
24
|
+
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
25
|
+
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
26
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
27
|
+
|
|
28
|
+
# Run a backup
|
|
29
|
+
run_backup() {
|
|
30
|
+
local model="$1"
|
|
31
|
+
if [ -z "$model" ]; then
|
|
32
|
+
log_error "Usage: $0 run <model_name>"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Lock to prevent concurrent runs
|
|
37
|
+
LOCKFILE="/tmp/backup_nexus_backup_${model}.lock"
|
|
38
|
+
exec 200>"$LOCKFILE"
|
|
39
|
+
if ! flock -n 200; then
|
|
40
|
+
log_warn "Backup '$model' is already running. Skipping."
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
log_info "Running backup: $model"
|
|
45
|
+
cd "$(dirname "$0")/../../.."
|
|
46
|
+
|
|
47
|
+
if bundle exec backup perform -t "$model" --triggered_by backup_nexus; then
|
|
48
|
+
log_info "Backup '$model' completed successfully."
|
|
49
|
+
else
|
|
50
|
+
log_error "Backup '$model' FAILED."
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# List backup files
|
|
56
|
+
list_backups() {
|
|
57
|
+
local dir="$BACKUP_DIR"
|
|
58
|
+
if [ ! -d "$dir" ]; then
|
|
59
|
+
log_warn "No backup directory found at: $dir"
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
echo "Backup files in: $dir"
|
|
64
|
+
echo "─────────────────────────────────────────────"
|
|
65
|
+
printf "%-40s %10s %s\n" "FILE" "SIZE" "MODIFIED"
|
|
66
|
+
echo "─────────────────────────────────────────────"
|
|
67
|
+
|
|
68
|
+
find "$dir" -maxdepth 1 -type f -name "*.tar.gz*" -o -name "*.sql.gz*" | sort -r | head -20 | while read -r f; do
|
|
69
|
+
size=$(du -h "$f" | cut -f1)
|
|
70
|
+
modified=$(stat -c "%y" "$f" 2>/dev/null | cut -d'.' -f1 || date -r "$f" "+%Y-%m-%d %H:%M")
|
|
71
|
+
printf "%-40s %10s %s\n" "$(basename "$f")" "$size" "$modified"
|
|
72
|
+
done
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Show backup status
|
|
76
|
+
show_status() {
|
|
77
|
+
local dir="$BACKUP_DIR"
|
|
78
|
+
if [ ! -d "$dir" ]; then
|
|
79
|
+
echo "Status: NOT_CONFIGURED"
|
|
80
|
+
echo "No backup directory at: $dir"
|
|
81
|
+
exit 0
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
local total_files
|
|
85
|
+
total_files=$(find "$dir" -maxdepth 1 -type f | wc -l)
|
|
86
|
+
local total_size
|
|
87
|
+
total_size=$(du -sh "$dir" 2>/dev/null | cut -f1 || echo "0B")
|
|
88
|
+
|
|
89
|
+
local latest_file
|
|
90
|
+
latest_file=$(find "$dir" -maxdepth 1 -type f -name "*.tar.gz*" -o -name "*.sql.gz*" 2>/dev/null | sort -r | head -1)
|
|
91
|
+
|
|
92
|
+
if [ -z "$latest_file" ]; then
|
|
93
|
+
echo "Status: WARNING - No backup files found"
|
|
94
|
+
else
|
|
95
|
+
local age_hours
|
|
96
|
+
age_hours=$(( ($(date +%s) - $(stat -c "%Y" "$latest_file" 2>/dev/null || stat -f "%m" "$latest_file")) / 3600 ))
|
|
97
|
+
|
|
98
|
+
if [ "$age_hours" -lt 24 ]; then
|
|
99
|
+
echo "Status: HEALTHY"
|
|
100
|
+
elif [ "$age_hours" -lt 72 ]; then
|
|
101
|
+
echo "Status: WARNING - Last backup ${age_hours}h ago"
|
|
102
|
+
else
|
|
103
|
+
echo "Status: CRITICAL - Last backup ${age_hours}h ago"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
echo "Files: $total_files"
|
|
108
|
+
echo "Size: $total_size"
|
|
109
|
+
[ -n "$latest_file" ] && echo "Latest: $(basename "$latest_file")"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Clean old backups
|
|
113
|
+
clean_backups() {
|
|
114
|
+
local days="${1:-30}"
|
|
115
|
+
local dir="$BACKUP_DIR"
|
|
116
|
+
|
|
117
|
+
if [ ! -d "$dir" ]; then
|
|
118
|
+
log_warn "No backup directory found at: $dir"
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
log_info "Removing backups older than $days days..."
|
|
123
|
+
find "$dir" -maxdepth 1 -type f -mtime +"$days" -delete
|
|
124
|
+
log_info "Cleanup complete."
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Main
|
|
128
|
+
case "${1:-help}" in
|
|
129
|
+
run)
|
|
130
|
+
run_backup "$2"
|
|
131
|
+
;;
|
|
132
|
+
list)
|
|
133
|
+
list_backups
|
|
134
|
+
;;
|
|
135
|
+
status)
|
|
136
|
+
show_status
|
|
137
|
+
;;
|
|
138
|
+
clean)
|
|
139
|
+
clean_backups "$2"
|
|
140
|
+
;;
|
|
141
|
+
*)
|
|
142
|
+
echo "BackupNexus Backup Helper"
|
|
143
|
+
echo ""
|
|
144
|
+
echo "Usage: $0 <command> [args]"
|
|
145
|
+
echo ""
|
|
146
|
+
echo "Commands:"
|
|
147
|
+
echo " run <model> Run a backup model (e.g., daily_backup)"
|
|
148
|
+
echo " list List recent backup files"
|
|
149
|
+
echo " status Show backup health status"
|
|
150
|
+
echo " clean [days] Remove backups older than N days (default: 30)"
|
|
151
|
+
;;
|
|
152
|
+
esac
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Backup v5.x Configuration
|
|
5
|
+
# Generated by backup_nexus
|
|
6
|
+
#
|
|
7
|
+
# Documentation: http://backup.github.io/backup
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
# Logging
|
|
11
|
+
# Logger.configure do
|
|
12
|
+
# console.quiet = true
|
|
13
|
+
# logfile.max_bytes = 2_000_000
|
|
14
|
+
# syslog.enabled = true
|
|
15
|
+
# syslog.ident = '<%= Rails.application.class.module_parent_name.underscore %>_backup'
|
|
16
|
+
# end
|
|
17
|
+
|
|
18
|
+
# Storage defaults
|
|
19
|
+
# Storage::Local.defaults do |local|
|
|
20
|
+
# local.path = 'storage/backup_nexus/backups'
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
# Notifier defaults
|
|
24
|
+
# Notifier::Mail.defaults do |mail|
|
|
25
|
+
# mail.from = 'backup@<%= Rails.application.class.module_parent_name.underscore %>.com'
|
|
26
|
+
# mail.to = 'admin@<%= Rails.application.class.module_parent_name.underscore %>.com'
|
|
27
|
+
# end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
# Daily Backup - Generated by backup_nexus
|
|
4
|
+
# Skips large/temporary tables for faster backups
|
|
5
|
+
|
|
6
|
+
Model.new(:daily_backup, '<%= Rails.application.class.module_parent_name %> Daily Backup') do
|
|
7
|
+
|
|
8
|
+
## MySQL [Database]
|
|
9
|
+
database MySQL do |db|
|
|
10
|
+
db.name = "<%= Rails.configuration.database_configuration[Rails.env]['database'] %>"
|
|
11
|
+
db.port = <%= Rails.configuration.database_configuration[Rails.env]['port'] || 3306 %>
|
|
12
|
+
db.skip_tables = []
|
|
13
|
+
db.additional_options = ["--quick"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
## Local [Storage]
|
|
17
|
+
store_with Local do |local|
|
|
18
|
+
local.path = 'storage/backup_nexus/backups'
|
|
19
|
+
local.keep = 30
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
## Gzip [Compressor]
|
|
23
|
+
compress_with Gzip
|
|
24
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Full backup template — all tables, local storage only
|
|
3
|
+
|
|
4
|
+
model :full_backup do
|
|
5
|
+
# Database
|
|
6
|
+
database Mysql do |db|
|
|
7
|
+
db.name = ENV["DATABASE_NAME"] || "myapp_production"
|
|
8
|
+
db.username = ENV["DATABASE_USER"] || "root"
|
|
9
|
+
db.password = ENV["DATABASE_PASSWORD"] || ""
|
|
10
|
+
db.host = ENV["DATABASE_HOST"] || "localhost"
|
|
11
|
+
db.port = (ENV["DATABASE_PORT"] || 3306).to_i
|
|
12
|
+
db.dump_options = {
|
|
13
|
+
single_transaction: true,
|
|
14
|
+
add_lock_tables: false
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Compress with Gzip
|
|
19
|
+
compress_with Gzip
|
|
20
|
+
|
|
21
|
+
# Store locally
|
|
22
|
+
store_with Local do |local|
|
|
23
|
+
local.path = ENV["BACKUP_DUMP_PATH"] || File.expand_path("storage/backup_nexus/backups", Rails.root)
|
|
24
|
+
local.keep = 10
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Notify on failure (optional)
|
|
28
|
+
notify_by Mail do |mail|
|
|
29
|
+
mail.on_success = false
|
|
30
|
+
mail.on_failure = true
|
|
31
|
+
mail.from = ENV["BACKUP_MAIL_FROM"] || "backup@example.com"
|
|
32
|
+
mail.to = ENV["BACKUP_MAIL_TO"] || "admin@example.com"
|
|
33
|
+
mail.smtp = { address: ENV["SMTP_HOST"] || "localhost", port: ENV["SMTP_PORT"] || 587 }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Sync backup template — remote sync to backup server
|
|
3
|
+
|
|
4
|
+
model :sync_backup do
|
|
5
|
+
# Database
|
|
6
|
+
database Mysql do |db|
|
|
7
|
+
db.name = ENV["DATABASE_NAME"] || "myapp_production"
|
|
8
|
+
db.username = ENV["DATABASE_USER"] || "root"
|
|
9
|
+
db.password = ENV["DATABASE_PASSWORD"] || ""
|
|
10
|
+
db.host = ENV["DATABASE_HOST"] || "localhost"
|
|
11
|
+
db.port = (ENV["DATABASE_PORT"] || 3306).to_i
|
|
12
|
+
|
|
13
|
+
# Skip large/temporary tables
|
|
14
|
+
skip_tables = ENV["BACKUP_SKIP_TABLES"] || "sessions,caches,active_storage"
|
|
15
|
+
skip_tables.split(",").each { |t| db.skip_tables << t.strip }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Compress with Gzip
|
|
19
|
+
compress_with Gzip
|
|
20
|
+
|
|
21
|
+
# Store locally (staging area)
|
|
22
|
+
store_with Local do |local|
|
|
23
|
+
local.path = ENV["BACKUP_DUMP_PATH"] || File.expand_path("storage/backup_nexus/backups/sync", Rails.root)
|
|
24
|
+
local.keep = 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Sync to remote server
|
|
28
|
+
notify_by Sync do |sync|
|
|
29
|
+
sync.directories do |directory|
|
|
30
|
+
directory.path = ENV["BACKUP_DUMP_PATH"] || File.expand_path("storage/backup_nexus/backups/sync", Rails.root)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
connection do |conn|
|
|
34
|
+
conn.host = ENV["BACKUP_RSYNC_HOST"] || "backup.example.com"
|
|
35
|
+
conn.port = ENV["BACKUP_RSYNC_PORT"] || 22
|
|
36
|
+
conn.username = ENV["BACKUP_RSYNC_USER"] || "backup"
|
|
37
|
+
conn.ssh_options = {
|
|
38
|
+
keys: ENV["BACKUP_SSH_KEY"] || File.expand_path("~/.ssh/id_rsa"),
|
|
39
|
+
timeout: 30
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# BackupNexus Backup MySQL Configuration
|
|
2
|
+
# Generated by: rails generate backup_nexus:backup
|
|
3
|
+
#
|
|
4
|
+
# This file is used by the backup gem for database authentication.
|
|
5
|
+
# Keep this file secure and do not commit to version control.
|
|
6
|
+
|
|
7
|
+
[client]
|
|
8
|
+
user=<%= ENV['DATABASE_USER'] || 'root' %>
|
|
9
|
+
password=<%= ENV['DATABASE_PASSWORD'] || '' %>
|
|
10
|
+
host=<%= ENV['DATABASE_HOST'] || 'localhost' %>
|
|
11
|
+
port=<%= (ENV['DATABASE_PORT'] || 3306).to_i %>
|
|
12
|
+
database=<%= ENV['DATABASE_NAME'] || 'myapp_production' %>
|
|
13
|
+
|
|
14
|
+
# Dump options
|
|
15
|
+
default-character-set=utf8mb4
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# BackupNexus Backup Schedule
|
|
3
|
+
# Generated by: rails generate backup_nexus:backup
|
|
4
|
+
#
|
|
5
|
+
# This file is parsed by the BackupService to show schedule info in the UI.
|
|
6
|
+
# Actual cron should be installed separately: whenever --write-crontab
|
|
7
|
+
|
|
8
|
+
# ─── Backup Schedule ──────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
# Full backup daily at 2:00 AM (all tables, local only)
|
|
11
|
+
every 1.day, at: "2:00 am" do
|
|
12
|
+
command "cd #{File.expand_path('../..', __FILE__)} && bundle exec backup perform -t full_backup --triggered_by system"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Incremental backup every 4 hours (skipping large tables)
|
|
16
|
+
every 4.hours do
|
|
17
|
+
command "cd #{File.expand_path('../..', __FILE__)} && bundle exec backup perform -t incremental_backup --triggered_by system"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Remote sync weekly on Sunday at 3:00 AM
|
|
21
|
+
every 1.week, at: "3:00 am" do
|
|
22
|
+
command "cd #{File.expand_path('../..', __FILE__)} && bundle exec backup perform -t sync_backup --triggered_by system"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Cleanup old backups monthly
|
|
26
|
+
every 1.month, at: "4:00 am" do
|
|
27
|
+
command "cd #{File.expand_path('../..', __FILE__)} && bundle exec backup perform -t full_backup --cleanup"
|
|
28
|
+
end
|