backy_rb 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.backyrc.example +31 -15
- data/CHANGELOG.md +12 -0
- data/Gemfile +0 -6
- data/Gemfile.lock +5 -5
- data/README.md +11 -12
- data/backy_rb.gemspec +4 -0
- data/lib/backy/app_config.rb +1 -1
- data/lib/backy/configuration.rb +97 -93
- data/lib/backy/db.rb +2 -1
- data/lib/backy/logger.rb +17 -3
- data/lib/backy/pg_dump.rb +99 -40
- data/lib/backy/pg_restore.rb +3 -3
- data/lib/backy/s3.rb +1 -1
- data/lib/backy/version.rb +1 -1
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7cc1e70142f6fa0c98d12c5e1187e05596a847e58ff13060b49d713bea3189c
|
4
|
+
data.tar.gz: 702727d108936670bcfa86a203cd67c672eb0dfb2a3f84d4f22e538962c2e151
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16e3cc5cecf30e88be20ed71685cd5c615ace3ad960f0bcca92c03cda64f0c586da5d6905072e99c3fac2b334e598e46588eb438a0406dd10956a73fb1d4ec78
|
7
|
+
data.tar.gz: 55e8e14e5edb195cc882abe7cf4568cbb986d1e39b955c198fea5f25091b0adc00e42136b8ccd6df5a522ddf61662a4ee0ff617ee877221fc6540f91b7813aee
|
data/.backyrc.example
CHANGED
@@ -1,17 +1,33 @@
|
|
1
1
|
# ~/.backyrc
|
2
|
-
|
2
|
+
shared:
|
3
3
|
use_parallel: true
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
4
|
+
pause_replication: true
|
5
|
+
s3_access_key: "YOUR_AWS_ACCESS_KEY_ID"
|
6
|
+
s3_secret: "YOUR_AWS_SECRET_ACCESS_KEY"
|
7
|
+
s3_region: "eu-central-1"
|
8
|
+
s3_bucket: "your-s3-bucket-name"
|
9
|
+
s3_prefix: "./db/dump/"
|
10
|
+
pg_host: "localhost"
|
11
|
+
pg_port: 5432
|
12
|
+
pg_username: "your-db-username"
|
13
|
+
pg_password: "your-db-password"
|
14
|
+
pg_database: "your-database-name"
|
15
|
+
app_name: "backy"
|
16
|
+
environment: "development"
|
17
|
+
log_file: "./log/backy.log"
|
18
|
+
local_backup_path: "/path/to/your/local/backup/directory"
|
19
|
+
|
20
|
+
production:
|
21
|
+
pg_host: "production-host"
|
22
|
+
s3_bucket: "production-s3-bucket-name"
|
23
|
+
log_file: "./log/production_backy.log"
|
24
|
+
|
25
|
+
staging:
|
26
|
+
pg_host: "staging-host"
|
27
|
+
s3_bucket: "staging-s3-bucket-name"
|
28
|
+
log_file: "./log/staging_backy.log"
|
29
|
+
|
30
|
+
development:
|
31
|
+
pg_host: "localhost"
|
32
|
+
s3_bucket: "development-s3-bucket-name"
|
33
|
+
log_file: "./log/development_backy.log"
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,18 @@
|
|
3
3
|
All notable changes to `Backy` will be documented in this file.
|
4
4
|
|
5
5
|
## [Unreleased]
|
6
|
+
## [0.2.0] - 2024-06-24
|
7
|
+
### Added
|
8
|
+
- Support for turning off replication
|
9
|
+
- Added support for config by environment in .backyrc
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- Breaking change; Change in config keys
|
13
|
+
- Internal refactoring
|
14
|
+
|
15
|
+
## [0.1.8] - 2024-06-22
|
16
|
+
### Fixed
|
17
|
+
- Ensure that the dependencies are installed
|
6
18
|
|
7
19
|
## [0.1.7] - 2024-06-21
|
8
20
|
### Added
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
backy_rb (0.
|
4
|
+
backy_rb (0.2.0)
|
5
5
|
activerecord (>= 4.0)
|
6
6
|
activesupport (>= 4.0)
|
7
7
|
aws-sdk-s3 (>= 1.117)
|
8
|
+
pg (~> 1.5)
|
9
|
+
thor (~> 1.2)
|
8
10
|
|
9
11
|
GEM
|
10
12
|
remote: https://rubygems.org/
|
@@ -136,7 +138,7 @@ GEM
|
|
136
138
|
parallel (1.22.1)
|
137
139
|
parser (3.2.1.1)
|
138
140
|
ast (~> 2.4.1)
|
139
|
-
pg (1.5.
|
141
|
+
pg (1.5.6)
|
140
142
|
puma (6.3.0)
|
141
143
|
nio4r (~> 2.0)
|
142
144
|
racc (1.7.1)
|
@@ -228,14 +230,13 @@ GEM
|
|
228
230
|
|
229
231
|
PLATFORMS
|
230
232
|
arm64-darwin-22
|
233
|
+
arm64-darwin-23
|
231
234
|
x86_64-linux
|
232
235
|
|
233
236
|
DEPENDENCIES
|
234
|
-
aws-sdk-s3 (~> 1.117)
|
235
237
|
backy_rb!
|
236
238
|
brakeman (~> 5.4)
|
237
239
|
faker (>= 3.1)
|
238
|
-
pg
|
239
240
|
puma
|
240
241
|
rails (>= 4.0)
|
241
242
|
rake (~> 13.0)
|
@@ -245,7 +246,6 @@ DEPENDENCIES
|
|
245
246
|
rubocop-rake (~> 0.6.0)
|
246
247
|
rubocop-rspec (~> 2.18)
|
247
248
|
standard (~> 1.3)
|
248
|
-
thor (~> 1.2)
|
249
249
|
timecop (~> 0.9.6)
|
250
250
|
zlib (~> 3.0)
|
251
251
|
|
data/README.md
CHANGED
@@ -68,19 +68,18 @@ Backy can be configured through a .backyrc YAML file. Place this file in your ho
|
|
68
68
|
Example `.backyrc`:
|
69
69
|
|
70
70
|
```yaml
|
71
|
-
|
71
|
+
shared:
|
72
72
|
use_parallel: true
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
database_name: DB_NAME
|
73
|
+
pause_replication: true
|
74
|
+
s3_access_key_id: YOUR_ACCESS_KEY
|
75
|
+
s3_secret_access_key: YOUR_SECRET_KEY
|
76
|
+
s3_region: YOUR_REGION
|
77
|
+
s3_bucket: YOUR_BUCKET
|
78
|
+
pg_host: DB_HOST
|
79
|
+
pg_port: DB_PORT
|
80
|
+
pg_username: DB_USERNAME
|
81
|
+
pg_password: DB_PASSWORD
|
82
|
+
pg_database: DB_NAME
|
84
83
|
```
|
85
84
|
|
86
85
|
## Development
|
data/backy_rb.gemspec
CHANGED
@@ -7,6 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.version = Backy::VERSION
|
8
8
|
spec.authors = ["Alexey Kharchenko", "Martin Ulleberg", "Pål André Sundt"]
|
9
9
|
spec.email = ["akharchenko@gmail.com", "martin.ulleberg@gmail.com", "pal@rubynor.com"]
|
10
|
+
spec.license = "MIT"
|
10
11
|
|
11
12
|
spec.summary = "Backy is a powerful and user-friendly database backup gem designed specifically for Ruby on Rails applications. It streamlines the backup process, ensuring your data is safe, secure, and easily retrievable. With its versatile features and easy integration, Backy is the go-to solution for Rails developers looking to protect their valuable information."
|
12
13
|
spec.description = "Backy is a comprehensive database backup solution for Ruby on Rails applications, created to help developers manage and safeguard their data with ease. This robust gem offers a wide range of features"
|
@@ -43,6 +44,9 @@ Gem::Specification.new do |spec|
|
|
43
44
|
spec.add_dependency "activerecord", ">= 4.0"
|
44
45
|
spec.add_dependency "activesupport", ">= 4.0"
|
45
46
|
spec.add_dependency "aws-sdk-s3", ">= 1.117"
|
47
|
+
spec.add_dependency "pg", "~> 1.5"
|
48
|
+
spec.add_dependency "thor", "~> 1.2"
|
49
|
+
|
46
50
|
# For more information and examples about making a new gem, check out our
|
47
51
|
# guide at: https://bundler.io/guides/creating_gem.html
|
48
52
|
end
|
data/lib/backy/app_config.rb
CHANGED
data/lib/backy/configuration.rb
CHANGED
@@ -1,29 +1,44 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "etc"
|
3
|
+
require "open3"
|
4
|
+
require "yaml"
|
5
|
+
require "uri"
|
6
|
+
|
1
7
|
module Backy
|
2
8
|
class Configuration
|
3
|
-
|
4
|
-
:
|
5
|
-
:
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
|
9
|
+
DEFAULTS = {
|
10
|
+
pg_host: nil,
|
11
|
+
pg_port: nil,
|
12
|
+
pg_database: nil,
|
13
|
+
pg_username: nil,
|
14
|
+
pg_password: nil,
|
15
|
+
s3_region: nil,
|
16
|
+
s3_access_key: nil,
|
17
|
+
s3_secret: nil,
|
18
|
+
s3_bucket: nil,
|
19
|
+
s3_prefix: "./db/dump/",
|
20
|
+
app_name: "backy",
|
21
|
+
environment: "development",
|
22
|
+
use_parallel: false,
|
23
|
+
pause_replication: true,
|
24
|
+
log_file: "./log/backy.log",
|
25
|
+
local_backup_path: nil
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
CONFIG_FILE_NAME = ".backyrc"
|
29
|
+
|
30
|
+
attr_accessor(*DEFAULTS.keys)
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
DEFAULTS.each do |key, value|
|
34
|
+
instance_variable_set("@#{key}", value)
|
35
|
+
end
|
36
|
+
end
|
19
37
|
|
20
38
|
def load
|
21
|
-
|
22
|
-
global_config_file = File.join(Dir.home, '.backyrc')
|
39
|
+
load_config_file
|
23
40
|
|
24
|
-
|
25
|
-
Logger.log("Loading configuration from #{config_file}...") if File.exist?(config_file)
|
26
|
-
load_from_file(config_file) if File.exist?(config_file)
|
41
|
+
load_from_env
|
27
42
|
end
|
28
43
|
|
29
44
|
def pg_url=(url)
|
@@ -40,54 +55,14 @@ module Backy
|
|
40
55
|
@pg_url ||= ENV["PG_URL"]
|
41
56
|
end
|
42
57
|
|
43
|
-
def pg_host
|
44
|
-
@pg_host ||= ENV["PG_HOST"]
|
45
|
-
end
|
46
|
-
|
47
|
-
def pg_port
|
48
|
-
@pg_port ||= ENV["PG_PORT"]
|
49
|
-
end
|
50
|
-
|
51
|
-
def pg_database
|
52
|
-
@pg_database ||= ENV["PG_DATABASE"]
|
53
|
-
end
|
54
|
-
|
55
|
-
def pg_username
|
56
|
-
@pg_username ||= ENV["PG_USERNAME"]
|
57
|
-
end
|
58
|
-
|
59
|
-
def pg_password
|
60
|
-
@pg_password ||= ENV["PG_PASSWORD"]
|
61
|
-
end
|
62
|
-
|
63
|
-
def s3_region
|
64
|
-
@s3_region ||= ENV["S3_REGION"]
|
65
|
-
end
|
66
|
-
|
67
|
-
def s3_access_key
|
68
|
-
@s3_access_key ||= ENV["S3_ACCESS_KEY"]
|
69
|
-
end
|
70
|
-
|
71
|
-
def s3_secret
|
72
|
-
@s3_secret ||= ENV["S3_SECRET"]
|
73
|
-
end
|
74
|
-
|
75
|
-
def s3_bucket
|
76
|
-
@s3_bucket ||= ENV["S3_BUCKET"]
|
77
|
-
end
|
78
|
-
|
79
|
-
def s3_prefix
|
80
|
-
@s3_prefix ||= ENV["S3_PREFIX"].presence || "/db/dump/"
|
81
|
-
end
|
82
|
-
|
83
|
-
def use_parallel
|
84
|
-
@use_parallel ||= ENV["BACKY_USE_PARALLEL"] == "true"
|
85
|
-
end
|
86
|
-
|
87
58
|
def use_parallel?
|
88
59
|
use_parallel && pigz_installed && multicore
|
89
60
|
end
|
90
61
|
|
62
|
+
def pause_replication?
|
63
|
+
pause_replication
|
64
|
+
end
|
65
|
+
|
91
66
|
# Detect if pigz binary is available
|
92
67
|
# If it is, use it to speed up the dump
|
93
68
|
# pigz is a parallel gzip implementation
|
@@ -101,14 +76,6 @@ module Backy
|
|
101
76
|
@multicore ||= Etc.nprocessors > 1
|
102
77
|
end
|
103
78
|
|
104
|
-
def app_name
|
105
|
-
@app_name ||= ENV["APP_NAME"].presence || "backy"
|
106
|
-
end
|
107
|
-
|
108
|
-
def environment
|
109
|
-
@environment ||= "development"
|
110
|
-
end
|
111
|
-
|
112
79
|
def log_file
|
113
80
|
@log_file ||= default_log_file
|
114
81
|
end
|
@@ -118,37 +85,62 @@ module Backy
|
|
118
85
|
def default_log_file
|
119
86
|
if Gem.win_platform?
|
120
87
|
# Windows default path
|
121
|
-
File.join(Dir.home, "AppData", "Local",
|
88
|
+
File.join(Dir.home, "AppData", "Local", app_name, "log", app_name, ".log")
|
122
89
|
else
|
123
90
|
# Unix-like systems default path
|
124
|
-
File.join(Dir.home, ".local", "share",
|
91
|
+
File.join(Dir.home, ".local", "share", app_name, "log", app_name, ".log")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_config_file
|
96
|
+
local_config_file = File.join(Dir.pwd, CONFIG_FILE_NAME)
|
97
|
+
global_config_file = File.join(Dir.home, CONFIG_FILE_NAME)
|
98
|
+
|
99
|
+
config_file = File.exist?(local_config_file) ? local_config_file : global_config_file
|
100
|
+
if File.exist?(config_file)
|
101
|
+
Logger.log("Loading configuration from #{config_file}...")
|
102
|
+
load_from_file(config_file)
|
125
103
|
end
|
126
104
|
end
|
127
105
|
|
128
106
|
def load_from_file(file_path)
|
129
107
|
configuration = YAML.load_file(file_path)
|
130
108
|
|
131
|
-
|
132
|
-
|
133
|
-
@s3_region = configuration.dig("defaults", "s3", "region")
|
134
|
-
@s3_bucket = configuration.dig("defaults", "s3", "bucket")
|
135
|
-
@s3_prefix = configuration.dig("defaults", "s3", "prefix") || s3_prefix
|
109
|
+
shared_config = configuration.fetch("shared", {})
|
110
|
+
environment_config = configuration.fetch(environment, {})
|
136
111
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
@pg_database = configuration.dig("defaults", "database", "database_name")
|
112
|
+
merged_config = deep_merge(shared_config, environment_config)
|
113
|
+
|
114
|
+
apply_config(merged_config)
|
115
|
+
end
|
116
|
+
|
117
|
+
def apply_config(config)
|
118
|
+
config.each do |key, value|
|
119
|
+
instance_variable_set("@#{key}", value) if respond_to?("#{key}=")
|
146
120
|
end
|
147
121
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
122
|
+
self.pg_url = @pg_url if @pg_url
|
123
|
+
end
|
124
|
+
|
125
|
+
def load_from_env
|
126
|
+
ENV.each do |key, value|
|
127
|
+
case key
|
128
|
+
when "PG_HOST" then @pg_host = value
|
129
|
+
when "PG_PORT" then @pg_port = value
|
130
|
+
when "PG_DATABASE" then @pg_database = value
|
131
|
+
when "PG_USERNAME" then @pg_username = value
|
132
|
+
when "PG_PASSWORD" then @pg_password = value
|
133
|
+
when "S3_REGION" then @s3_region = value
|
134
|
+
when "S3_ACCESS_KEY" then @s3_access_key = value
|
135
|
+
when "S3_SECRET" then @s3_secret = value
|
136
|
+
when "S3_BUCKET" then @s3_bucket = value
|
137
|
+
when "S3_PREFIX" then @s3_prefix = value
|
138
|
+
when "APP_NAME" then @app_name = value
|
139
|
+
when "BACKY_USE_PARALLEL" then @use_parallel = value == "true"
|
140
|
+
when "BACKY_PAUSE_REPLICATION" then @pause_replication = value == "true"
|
141
|
+
when "LOCAL_BACKUP_PATH" then @local_backup_path = value
|
142
|
+
end
|
143
|
+
end
|
152
144
|
end
|
153
145
|
|
154
146
|
def parse_postgres_uri(uri)
|
@@ -163,5 +155,17 @@ module Backy
|
|
163
155
|
database_name: parsed_uri.path[1..]
|
164
156
|
}
|
165
157
|
end
|
158
|
+
|
159
|
+
def deep_merge(hash1, hash2)
|
160
|
+
merged = hash1.dup
|
161
|
+
hash2.each do |key, value|
|
162
|
+
merged[key] = if value.is_a?(Hash) && hash1[key].is_a?(Hash)
|
163
|
+
deep_merge(hash1[key], value)
|
164
|
+
else
|
165
|
+
value
|
166
|
+
end
|
167
|
+
end
|
168
|
+
merged
|
169
|
+
end
|
166
170
|
end
|
167
171
|
end
|
data/lib/backy/db.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "forwardable"
|
2
2
|
|
3
3
|
module Backy
|
4
4
|
module Db
|
@@ -12,6 +12,7 @@ module Backy
|
|
12
12
|
def_delegator "Backy.configuration", :pg_username, :username
|
13
13
|
def_delegator "Backy.configuration", :pg_password, :password
|
14
14
|
def_delegator "Backy.configuration", :use_parallel?, :use_parallel?
|
15
|
+
def_delegator "Backy.configuration", :pause_replication?, :pause_replication?
|
15
16
|
|
16
17
|
def pg_password_env
|
17
18
|
password.present? ? "PGPASSWORD='#{password}' " : ""
|
data/lib/backy/logger.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require "thor"
|
2
2
|
|
3
3
|
module Backy
|
4
4
|
class Logger
|
5
|
+
@log_messages = []
|
6
|
+
|
5
7
|
# Logs a message with the specified color using Thor's shell
|
6
8
|
def self.log(message, color = nil)
|
7
|
-
|
8
|
-
|
9
|
+
@log_messages << message
|
10
|
+
say("[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] #{message}\n", color)
|
9
11
|
end
|
10
12
|
|
11
13
|
def self.success(message)
|
@@ -23,5 +25,17 @@ module Backy
|
|
23
25
|
def self.error(message)
|
24
26
|
log(message, :red)
|
25
27
|
end
|
28
|
+
|
29
|
+
def self.say(message, color = nil)
|
30
|
+
thor_shell.say(message, color)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.log_messages
|
34
|
+
@log_messages
|
35
|
+
end
|
36
|
+
|
37
|
+
private_class_method def self.thor_shell
|
38
|
+
@thor_shell ||= Thor::Base.shell.new
|
39
|
+
end
|
26
40
|
end
|
27
41
|
end
|
data/lib/backy/pg_dump.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "fileutils"
|
2
2
|
require "etc"
|
3
|
+
require "open3"
|
3
4
|
|
4
5
|
module Backy
|
5
6
|
class PgDump
|
@@ -10,9 +11,44 @@ module Backy
|
|
10
11
|
DUMP_CMD_OPTS = "--no-acl --no-owner --no-subscriptions --no-publications"
|
11
12
|
|
12
13
|
def call
|
14
|
+
setup_backup_directory
|
15
|
+
log_start
|
16
|
+
|
17
|
+
begin
|
18
|
+
handle_replication { backup }
|
19
|
+
rescue => e
|
20
|
+
Logger.error("An error occurred during backup: #{e.message}")
|
21
|
+
ensure
|
22
|
+
if replica? && pause_replication?
|
23
|
+
log_replication_resume
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def setup_backup_directory
|
13
31
|
FileUtils.mkdir_p(DUMP_DIR)
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_start
|
14
35
|
Logger.log("Starting backy for #{database}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle_replication
|
39
|
+
if replica? && pause_replication?
|
40
|
+
if pause_replication
|
41
|
+
Logger.log("Replication paused.")
|
42
|
+
yield
|
43
|
+
else
|
44
|
+
Logger.error("Failed to pause replication. Aborting backup.")
|
45
|
+
end
|
46
|
+
else
|
47
|
+
yield
|
48
|
+
end
|
49
|
+
end
|
15
50
|
|
51
|
+
def backup
|
16
52
|
if use_parallel?
|
17
53
|
Logger.log("Using multicore dump with pigz")
|
18
54
|
parallel_backup
|
@@ -22,64 +58,47 @@ module Backy
|
|
22
58
|
end
|
23
59
|
end
|
24
60
|
|
25
|
-
|
61
|
+
def log_replication_resume
|
62
|
+
if resume_replication
|
63
|
+
Logger.log("Replication resumed.")
|
64
|
+
else
|
65
|
+
Logger.error("Failed to resume replication. Manual intervention required.")
|
66
|
+
end
|
67
|
+
end
|
26
68
|
|
27
69
|
def plain_text_backup
|
28
|
-
timestamp =
|
29
|
-
dump_file = "#{DUMP_DIR}/#{database}_#{whoami}@#{hostname}
|
70
|
+
timestamp = current_timestamp
|
71
|
+
dump_file = "#{DUMP_DIR}/#{database}_#{whoami}@#{hostname}_#{timestamp}.sql.gz"
|
30
72
|
|
31
73
|
cmd = "(#{pg_password_env}pg_dump #{pg_credentials} #{database} #{DUMP_CMD_OPTS} | gzip -9 > #{dump_file}) 2>&1 >> #{log_file}"
|
32
74
|
|
33
|
-
|
75
|
+
Logger.log("Saving to #{dump_file} ... ")
|
34
76
|
|
35
|
-
|
36
|
-
Logger.success("done")
|
37
|
-
else
|
38
|
-
Logger.error("error. See #{log_file}")
|
39
|
-
return
|
40
|
-
end
|
41
|
-
|
42
|
-
dump_file
|
77
|
+
execute_command(cmd, "error. See #{log_file}")
|
43
78
|
end
|
44
79
|
|
45
80
|
def parallel_backup
|
46
|
-
timestamp =
|
81
|
+
timestamp = current_timestamp
|
47
82
|
dump_dir = "#{DUMP_DIR}/#{database}_dump_parallel_#{timestamp}"
|
48
83
|
dump_file = "#{dump_dir}.tar.gz"
|
49
84
|
|
50
|
-
pg_dump_cmd = "pg_dump -Z0 -j #{Etc.nprocessors} -Fd #{database} -f #{dump_dir} #{pg_credentials} #{DUMP_CMD_OPTS}"
|
85
|
+
pg_dump_cmd = "#{pg_password_env}pg_dump -Z0 -j #{Etc.nprocessors} -Fd #{database} -f #{dump_dir} #{pg_credentials} #{DUMP_CMD_OPTS}"
|
51
86
|
tar_cmd = "tar -cf - #{dump_dir} | pigz -p #{Etc.nprocessors} > #{dump_file}"
|
52
87
|
cleanup_cmd = "rm -rf #{dump_dir}"
|
53
88
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
else
|
58
|
-
Logger.error("pg_dump failed. See #{log_file} for details.")
|
59
|
-
return
|
60
|
-
end
|
61
|
-
|
62
|
-
# Execute tar command
|
63
|
-
Logger.log("Compressing #{dump_dir}")
|
64
|
-
if system(tar_cmd)
|
65
|
-
Logger.log("Compression completed successfully.")
|
66
|
-
else
|
67
|
-
Logger.error("Compression failed. See #{log_file} for details.")
|
68
|
-
return
|
69
|
-
end
|
70
|
-
|
71
|
-
# Execute cleanup command
|
72
|
-
Logger.log("Cleaning up #{dump_dir}")
|
73
|
-
if system(cleanup_cmd)
|
74
|
-
Logger.log("Cleanup completed successfully.")
|
75
|
-
else
|
76
|
-
Logger.error("Cleanup failed. See #{log_file} for details.")
|
77
|
-
return
|
78
|
-
end
|
89
|
+
execute_command("#{pg_password_env}#{pg_dump_cmd} 2>&1 >> #{log_file}", "pg_dump failed. See #{log_file} for details.")
|
90
|
+
execute_command(tar_cmd, "Compression failed. See #{log_file} for details.")
|
91
|
+
execute_command(cleanup_cmd, "Cleanup failed. See #{log_file} for details.")
|
79
92
|
|
80
93
|
Logger.success("Backup process completed. Output file: #{dump_file}")
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute_command(cmd, error_message)
|
97
|
+
Logger.error(error_message) unless system(cmd)
|
98
|
+
end
|
81
99
|
|
82
|
-
|
100
|
+
def current_timestamp
|
101
|
+
Time.now.strftime("%Y%m%d_%H%M%S")
|
83
102
|
end
|
84
103
|
|
85
104
|
def hostname
|
@@ -89,5 +108,45 @@ module Backy
|
|
89
108
|
def whoami
|
90
109
|
@whoami ||= `whoami`.strip
|
91
110
|
end
|
111
|
+
|
112
|
+
def pause_replication
|
113
|
+
query = "SELECT pg_wal_replay_pause();"
|
114
|
+
success, _output = execute_sql(query)
|
115
|
+
success
|
116
|
+
end
|
117
|
+
|
118
|
+
def resume_replication
|
119
|
+
query = "SELECT pg_wal_replay_resume();"
|
120
|
+
success, _output = execute_sql(query)
|
121
|
+
success
|
122
|
+
end
|
123
|
+
|
124
|
+
def execute_sql(query)
|
125
|
+
command = %(#{pg_password_env}psql #{pg_credentials} -d #{database} -c "#{query}")
|
126
|
+
output = ""
|
127
|
+
Open3.popen3(command) do |_stdin, stdout, stderr, wait_thr|
|
128
|
+
while (line = stdout.gets)
|
129
|
+
output << line
|
130
|
+
end
|
131
|
+
while (line = stderr.gets)
|
132
|
+
puts "Error: #{line}"
|
133
|
+
end
|
134
|
+
exit_status = wait_thr.value
|
135
|
+
[exit_status.success?, output]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def replica?
|
140
|
+
@is_replica ||= begin
|
141
|
+
query = "SELECT pg_is_in_recovery();"
|
142
|
+
success, output = execute_sql(query)
|
143
|
+
if success && output.include?("t")
|
144
|
+
Logger.log("Database is a replica.")
|
145
|
+
true
|
146
|
+
else
|
147
|
+
false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
92
151
|
end
|
93
152
|
end
|
data/lib/backy/pg_restore.rb
CHANGED
@@ -10,16 +10,16 @@ module Backy
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def call
|
13
|
-
pigz_installed = system(
|
13
|
+
pigz_installed = system("which pigz > /dev/null 2>&1")
|
14
14
|
multicore = Etc.nprocessors > 1
|
15
15
|
use_multicore = ENV["BACKY_USE_PARALLEL"] == "true"
|
16
16
|
|
17
17
|
if pigz_installed && multicore && use_multicore
|
18
|
-
Logger.log(
|
18
|
+
Logger.log("Using parallel restore with pigz")
|
19
19
|
parallel_restore
|
20
20
|
else
|
21
21
|
Logger.log("Pigz not installed or system is not multicore")
|
22
|
-
Logger.log(
|
22
|
+
Logger.log("Using plain text restore")
|
23
23
|
plain_text_restore
|
24
24
|
end
|
25
25
|
end
|
data/lib/backy/s3.rb
CHANGED
data/lib/backy/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backy_rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexey Kharchenko
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-06-
|
13
|
+
date: 2024-06-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rspec
|
@@ -208,6 +208,34 @@ dependencies:
|
|
208
208
|
- - ">="
|
209
209
|
- !ruby/object:Gem::Version
|
210
210
|
version: '1.117'
|
211
|
+
- !ruby/object:Gem::Dependency
|
212
|
+
name: pg
|
213
|
+
requirement: !ruby/object:Gem::Requirement
|
214
|
+
requirements:
|
215
|
+
- - "~>"
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
version: '1.5'
|
218
|
+
type: :runtime
|
219
|
+
prerelease: false
|
220
|
+
version_requirements: !ruby/object:Gem::Requirement
|
221
|
+
requirements:
|
222
|
+
- - "~>"
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '1.5'
|
225
|
+
- !ruby/object:Gem::Dependency
|
226
|
+
name: thor
|
227
|
+
requirement: !ruby/object:Gem::Requirement
|
228
|
+
requirements:
|
229
|
+
- - "~>"
|
230
|
+
- !ruby/object:Gem::Version
|
231
|
+
version: '1.2'
|
232
|
+
type: :runtime
|
233
|
+
prerelease: false
|
234
|
+
version_requirements: !ruby/object:Gem::Requirement
|
235
|
+
requirements:
|
236
|
+
- - "~>"
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '1.2'
|
211
239
|
description: Backy is a comprehensive database backup solution for Ruby on Rails applications,
|
212
240
|
created to help developers manage and safeguard their data with ease. This robust
|
213
241
|
gem offers a wide range of features
|
@@ -252,7 +280,8 @@ files:
|
|
252
280
|
- lib/backy_rb.rb
|
253
281
|
- lib/tasks/backy_tasks.rake
|
254
282
|
homepage: https://rubynor.com
|
255
|
-
licenses:
|
283
|
+
licenses:
|
284
|
+
- MIT
|
256
285
|
metadata:
|
257
286
|
homepage_uri: https://rubynor.com
|
258
287
|
source_code_uri: https://github.com/rubynor/backy
|
@@ -272,7 +301,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
272
301
|
- !ruby/object:Gem::Version
|
273
302
|
version: '0'
|
274
303
|
requirements: []
|
275
|
-
rubygems_version: 3.
|
304
|
+
rubygems_version: 3.4.22
|
276
305
|
signing_key:
|
277
306
|
specification_version: 4
|
278
307
|
summary: Backy is a powerful and user-friendly database backup gem designed specifically
|