sg_tiny_backup 1.0.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/.github/workflows/main.yml +51 -0
- data/.gitignore +14 -0
- data/.octocov.yml +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +70 -0
- data/.ruby-version +1 -0
- data/.simplecov +14 -0
- data/CHANGELOG.md +73 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/database.ci.yml +15 -0
- data/lib/sg_tiny_backup/commands/aws_cli.rb +32 -0
- data/lib/sg_tiny_backup/commands/base.rb +23 -0
- data/lib/sg_tiny_backup/commands/gzip.rb +20 -0
- data/lib/sg_tiny_backup/commands/mysql_dump.rb +38 -0
- data/lib/sg_tiny_backup/commands/openssl.rb +40 -0
- data/lib/sg_tiny_backup/commands/pg_dump.rb +38 -0
- data/lib/sg_tiny_backup/commands/tar.rb +68 -0
- data/lib/sg_tiny_backup/commands.rb +8 -0
- data/lib/sg_tiny_backup/config.rb +71 -0
- data/lib/sg_tiny_backup/error.rb +25 -0
- data/lib/sg_tiny_backup/pipeline.rb +62 -0
- data/lib/sg_tiny_backup/pipeline_builders/base.rb +37 -0
- data/lib/sg_tiny_backup/pipeline_builders/db.rb +70 -0
- data/lib/sg_tiny_backup/pipeline_builders/log.rb +36 -0
- data/lib/sg_tiny_backup/pipeline_builders/s3.rb +26 -0
- data/lib/sg_tiny_backup/railtie.rb +9 -0
- data/lib/sg_tiny_backup/runner.rb +77 -0
- data/lib/sg_tiny_backup/spawner.rb +113 -0
- data/lib/sg_tiny_backup/templates/sg_tiny_backup.yml +43 -0
- data/lib/sg_tiny_backup/utils.rb +34 -0
- data/lib/sg_tiny_backup/version.rb +5 -0
- data/lib/sg_tiny_backup.rb +21 -0
- data/lib/tasks/sg_tiny_backup.rake +78 -0
- data/sg_tiny_backup.gemspec +38 -0
- metadata +87 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require "sg_tiny_backup/error"
|
|
5
|
+
|
|
6
|
+
module SgTinyBackup
|
|
7
|
+
class Spawner
|
|
8
|
+
attr_reader :stdout, :stderr, :exit_code_errors
|
|
9
|
+
|
|
10
|
+
def initialize(commands:, env: {}, output_path: nil)
|
|
11
|
+
@commands = commands
|
|
12
|
+
@env = env
|
|
13
|
+
@output_path = output_path
|
|
14
|
+
@stderr_message = nil
|
|
15
|
+
@exit_code_errors = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def succeeded?
|
|
19
|
+
exit_code_errors.empty?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stderr_message
|
|
23
|
+
@stderr_message ||=
|
|
24
|
+
if @stderr.empty?
|
|
25
|
+
""
|
|
26
|
+
else
|
|
27
|
+
<<~END_OF_MESSAGE
|
|
28
|
+
STDERR messages:
|
|
29
|
+
|
|
30
|
+
#{@stderr}
|
|
31
|
+
END_OF_MESSAGE
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def exit_code_error_message
|
|
36
|
+
if succeeded?
|
|
37
|
+
""
|
|
38
|
+
else
|
|
39
|
+
<<~END_OF_MESSAGE
|
|
40
|
+
The following errors were returned:
|
|
41
|
+
|
|
42
|
+
#{@exit_code_errors.join("\n")}
|
|
43
|
+
END_OF_MESSAGE
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def spawn_and_wait
|
|
48
|
+
spawn_pipeline_command do |pid, out_r, err_r, status_r|
|
|
49
|
+
@stdout = out_r.read
|
|
50
|
+
@stderr = err_r.read
|
|
51
|
+
pipe_status_str = status_r.read
|
|
52
|
+
Process.wait(pid)
|
|
53
|
+
parse_pipe_status(pipe_status_str)
|
|
54
|
+
end
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
raise SpawnError.new("Pipeline failed to execute", e)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def spawn_pipeline_command
|
|
62
|
+
opts = {}
|
|
63
|
+
out_r, out_w = IO.pipe
|
|
64
|
+
opts[:out] = out_w
|
|
65
|
+
|
|
66
|
+
err_r, err_w = IO.pipe
|
|
67
|
+
opts[:err] = err_w
|
|
68
|
+
|
|
69
|
+
status_r, status_w = IO.pipe
|
|
70
|
+
opts[3] = status_w
|
|
71
|
+
|
|
72
|
+
pid = spawn(@env, pipeline_command, opts)
|
|
73
|
+
out_w.close
|
|
74
|
+
err_w.close
|
|
75
|
+
status_w.close
|
|
76
|
+
|
|
77
|
+
yield pid, out_r, err_r, status_r
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# pipe_status_str format:
|
|
81
|
+
# {command index}|{exit status}:\n{command index}|{exit status}:\n
|
|
82
|
+
#
|
|
83
|
+
# For example, "0|2:\n1|0:\n" means:
|
|
84
|
+
# * command 0 exited with status 2
|
|
85
|
+
# * command 1 exited with status 0
|
|
86
|
+
def parse_pipe_status(pipe_status_str)
|
|
87
|
+
@exit_code_errors = []
|
|
88
|
+
pipe_statuses = pipe_status_str.delete("\n").split(":").sort
|
|
89
|
+
pipe_statuses.each do |status|
|
|
90
|
+
index, exit_code = status.split("|").map(&:to_i)
|
|
91
|
+
command = @commands[index]
|
|
92
|
+
next if command.success_codes.member?(exit_code)
|
|
93
|
+
|
|
94
|
+
@exit_code_errors << "`#{command.command}` returned exit code: #{exit_code}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Pipeline command example:
|
|
99
|
+
#
|
|
100
|
+
# { tar -c log/production.log log/production.log.1 ; echo "0|$?:" >&3 ; } | { gzip ; echo "1|$?:" >&3 ; } > log.tar.gz
|
|
101
|
+
#
|
|
102
|
+
# This output each command's exit status to file descriptor 3.
|
|
103
|
+
def pipeline_command
|
|
104
|
+
parts = []
|
|
105
|
+
@commands.each_with_index do |command, index|
|
|
106
|
+
parts << %({ #{command.command} ; echo "#{index}|$?:" >&3 ; })
|
|
107
|
+
end
|
|
108
|
+
command = parts.join(" | ")
|
|
109
|
+
command += " > #{Shellwords.escape(@output_path)}" if @output_path
|
|
110
|
+
command
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
s3:
|
|
2
|
+
db:
|
|
3
|
+
# Destination url is `s3://{BUCKET}/{PREFIX}_{TIMESTAMP}.sql.gz.enc`.
|
|
4
|
+
bucket: YOUR_S3_BUCKET_NAME
|
|
5
|
+
prefix: YOUR_PROJECT/YOUR_PROJECT
|
|
6
|
+
access_key_id: <%= ENV.fetch('AWS_ACCESS_KEY_ID') %>
|
|
7
|
+
secret_access_key: <%= ENV.fetch('AWS_SECRET_ACCESS_KEY') %>
|
|
8
|
+
# If your backup file size exceeds 50GB, you must specify `expected_upload_size`.
|
|
9
|
+
# It must be larger than your backup file size.
|
|
10
|
+
# It is passed to `aws s3 cp`'s `--expected-size` option.
|
|
11
|
+
# See https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html
|
|
12
|
+
#
|
|
13
|
+
# It is used for the calculation of S3 multipart upload chunk size not to exceed the part number limit.
|
|
14
|
+
# See https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html
|
|
15
|
+
#
|
|
16
|
+
# expected_upload_size: 100000000000
|
|
17
|
+
log:
|
|
18
|
+
# Destination url is `s3://{BUCKET}/{PREFIX}_{HOSTNAME}_{TIMESTAMP}.tar.gz`.
|
|
19
|
+
bucket: YOUR_S3_BUCKET_NAME
|
|
20
|
+
prefix: YOUR_PROJECT/YOUR_PROJECT
|
|
21
|
+
access_key_id: <%= ENV.fetch('AWS_ACCESS_KEY_ID') %>
|
|
22
|
+
secret_access_key: <%= ENV.fetch('AWS_SECRET_ACCESS_KEY') %>
|
|
23
|
+
# expected_upload_size: 100000000000
|
|
24
|
+
# You can generate ENCRYPTION_KEY with `bundle exec rails secret`.
|
|
25
|
+
encryption_key: <%= ENV.fetch('ENCRYPTION_KEY') %>
|
|
26
|
+
log:
|
|
27
|
+
files:
|
|
28
|
+
- log/production.log
|
|
29
|
+
optional_files:
|
|
30
|
+
- log/production.log.1
|
|
31
|
+
pg_dump:
|
|
32
|
+
extra_options: -xc --if-exists --encoding=utf8
|
|
33
|
+
mysqldump:
|
|
34
|
+
extra_options: --single-transaction --quick --hex-blob
|
|
35
|
+
# gzip:
|
|
36
|
+
# level: 6 # 1 (fastest) to 9 (best compression), default: 6
|
|
37
|
+
# The following settings is not required since database config can be loaded from config/database.yml in typical rails application.
|
|
38
|
+
# db:
|
|
39
|
+
# adapter: postgresql
|
|
40
|
+
# host: localhost
|
|
41
|
+
# port: 5432
|
|
42
|
+
# username: postgres
|
|
43
|
+
# password: YOUR_DB_PASSWORD
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "erb"
|
|
6
|
+
|
|
7
|
+
module SgTinyBackup
|
|
8
|
+
module Utils
|
|
9
|
+
class << self
|
|
10
|
+
def timestamp
|
|
11
|
+
Time.now.strftime("%Y%m%d_%H%M%S")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def basename(target)
|
|
15
|
+
if target == "log"
|
|
16
|
+
"#{Socket.gethostname}_#{timestamp}"
|
|
17
|
+
else
|
|
18
|
+
timestamp
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load_yaml_with_erb(io)
|
|
23
|
+
yaml_content = io.read
|
|
24
|
+
resolved = ERB.new(yaml_content).result
|
|
25
|
+
YAML.safe_load(
|
|
26
|
+
resolved,
|
|
27
|
+
permitted_classes: [],
|
|
28
|
+
permitted_symbols: [],
|
|
29
|
+
aliases: true
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "sg_tiny_backup/config"
|
|
5
|
+
require "sg_tiny_backup/runner"
|
|
6
|
+
require "sg_tiny_backup/utils"
|
|
7
|
+
require "sg_tiny_backup/version"
|
|
8
|
+
require "sg_tiny_backup/railtie" if defined?(Rails)
|
|
9
|
+
|
|
10
|
+
module SgTinyBackup
|
|
11
|
+
class << self
|
|
12
|
+
attr_accessor :raise_on_error
|
|
13
|
+
attr_writer :logger
|
|
14
|
+
|
|
15
|
+
def logger
|
|
16
|
+
@logger ||= Logger.new(STDOUT)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
SgTinyBackup.raise_on_error = true
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :sg_tiny_backup do
|
|
4
|
+
default_config_path = ENV["SG_TINY_BACKUP_CONFIG_PATH"] || "config/sg_tiny_backup.yml"
|
|
5
|
+
default_backup_target = ENV["BACKUP_TARGET"] || "db"
|
|
6
|
+
|
|
7
|
+
desc "Generate config file"
|
|
8
|
+
task generate: :environment do
|
|
9
|
+
SgTinyBackup::Config.generate_template_file(default_config_path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc "Backup and upload to s3"
|
|
13
|
+
task :backup, [:backup_target] => :environment do |_task, args|
|
|
14
|
+
backup_target = args[:backup_target] || default_backup_target
|
|
15
|
+
config = SgTinyBackup::Config.read_file(default_config_path)
|
|
16
|
+
runner = SgTinyBackup::Runner.new(
|
|
17
|
+
config: config,
|
|
18
|
+
target: backup_target,
|
|
19
|
+
basename: SgTinyBackup::Utils.basename(backup_target)
|
|
20
|
+
)
|
|
21
|
+
url = runner.s3_destination_url
|
|
22
|
+
SgTinyBackup.logger.info "Starting backup to #{url}"
|
|
23
|
+
if runner.run
|
|
24
|
+
SgTinyBackup.logger.info "Backup done!"
|
|
25
|
+
else
|
|
26
|
+
SgTinyBackup.logger.error "Backup failed!"
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc "Backup to current directory"
|
|
32
|
+
task :backup_local, [:backup_target] => :environment do |_task, args|
|
|
33
|
+
backup_target = args[:backup_target] || default_backup_target
|
|
34
|
+
config = SgTinyBackup::Config.read_file(default_config_path)
|
|
35
|
+
runner = SgTinyBackup::Runner.new(
|
|
36
|
+
config: config,
|
|
37
|
+
target: backup_target,
|
|
38
|
+
basename: SgTinyBackup::Utils.basename(backup_target),
|
|
39
|
+
local: true
|
|
40
|
+
)
|
|
41
|
+
SgTinyBackup.logger.info "Starting backup to #{runner.base_filename}"
|
|
42
|
+
if runner.run
|
|
43
|
+
SgTinyBackup.logger.info "Backup done!"
|
|
44
|
+
else
|
|
45
|
+
SgTinyBackup.logger.error "Backup failed!"
|
|
46
|
+
exit 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc "Show backup command"
|
|
51
|
+
task :command, [:backup_target] => :environment do |_task, args|
|
|
52
|
+
backup_target = args[:backup_target] || default_backup_target
|
|
53
|
+
config = SgTinyBackup::Config.read_file(default_config_path)
|
|
54
|
+
runner = SgTinyBackup::Runner.new(
|
|
55
|
+
config: config,
|
|
56
|
+
target: backup_target,
|
|
57
|
+
basename: SgTinyBackup::Utils.timestamp
|
|
58
|
+
)
|
|
59
|
+
puts runner.piped_command
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
desc "Show backup command environment variables"
|
|
63
|
+
task :env, [:backup_target] => :environment do |_task, args|
|
|
64
|
+
backup_target = args[:backup_target] || default_backup_target
|
|
65
|
+
config = SgTinyBackup::Config.read_file(default_config_path)
|
|
66
|
+
runner = SgTinyBackup::Runner.new(
|
|
67
|
+
config: config,
|
|
68
|
+
target: backup_target,
|
|
69
|
+
basename: SgTinyBackup::Utils.timestamp
|
|
70
|
+
)
|
|
71
|
+
puts runner.env
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc "Show decryption command"
|
|
75
|
+
task decryption_command: :environment do
|
|
76
|
+
puts SgTinyBackup::Commands::Openssl.decryption_command
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/sg_tiny_backup/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "sg_tiny_backup"
|
|
7
|
+
spec.version = SgTinyBackup::VERSION
|
|
8
|
+
spec.authors = ["Shunichi Ikegami"]
|
|
9
|
+
spec.email = ["shunichi@sonicgarden.jp"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Tiny backup"
|
|
12
|
+
spec.description = "Backup postgresql database and logs to S3"
|
|
13
|
+
spec.homepage = "https://github.com/SonicGarden/sg_tiny_backup"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
16
|
+
|
|
17
|
+
# spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
|
18
|
+
|
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/SonicGarden/sg_tiny_backup"
|
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/SonicGarden/sg_tiny_backup/blob/main/CHANGELOG.md"
|
|
22
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
23
|
+
|
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
28
|
+
end
|
|
29
|
+
spec.bindir = "exe"
|
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
31
|
+
spec.require_paths = ["lib"]
|
|
32
|
+
|
|
33
|
+
# Uncomment to register a new dependency of your gem
|
|
34
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
|
35
|
+
|
|
36
|
+
# For more information and examples about making a new gem, checkout our
|
|
37
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sg_tiny_backup
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Shunichi Ikegami
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-03 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Backup postgresql database and logs to S3
|
|
14
|
+
email:
|
|
15
|
+
- shunichi@sonicgarden.jp
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".github/workflows/main.yml"
|
|
21
|
+
- ".gitignore"
|
|
22
|
+
- ".octocov.yml"
|
|
23
|
+
- ".rspec"
|
|
24
|
+
- ".rubocop.yml"
|
|
25
|
+
- ".ruby-version"
|
|
26
|
+
- ".simplecov"
|
|
27
|
+
- CHANGELOG.md
|
|
28
|
+
- Gemfile
|
|
29
|
+
- Gemfile.lock
|
|
30
|
+
- LICENSE.txt
|
|
31
|
+
- README.md
|
|
32
|
+
- Rakefile
|
|
33
|
+
- bin/console
|
|
34
|
+
- bin/setup
|
|
35
|
+
- config/database.ci.yml
|
|
36
|
+
- lib/sg_tiny_backup.rb
|
|
37
|
+
- lib/sg_tiny_backup/commands.rb
|
|
38
|
+
- lib/sg_tiny_backup/commands/aws_cli.rb
|
|
39
|
+
- lib/sg_tiny_backup/commands/base.rb
|
|
40
|
+
- lib/sg_tiny_backup/commands/gzip.rb
|
|
41
|
+
- lib/sg_tiny_backup/commands/mysql_dump.rb
|
|
42
|
+
- lib/sg_tiny_backup/commands/openssl.rb
|
|
43
|
+
- lib/sg_tiny_backup/commands/pg_dump.rb
|
|
44
|
+
- lib/sg_tiny_backup/commands/tar.rb
|
|
45
|
+
- lib/sg_tiny_backup/config.rb
|
|
46
|
+
- lib/sg_tiny_backup/error.rb
|
|
47
|
+
- lib/sg_tiny_backup/pipeline.rb
|
|
48
|
+
- lib/sg_tiny_backup/pipeline_builders/base.rb
|
|
49
|
+
- lib/sg_tiny_backup/pipeline_builders/db.rb
|
|
50
|
+
- lib/sg_tiny_backup/pipeline_builders/log.rb
|
|
51
|
+
- lib/sg_tiny_backup/pipeline_builders/s3.rb
|
|
52
|
+
- lib/sg_tiny_backup/railtie.rb
|
|
53
|
+
- lib/sg_tiny_backup/runner.rb
|
|
54
|
+
- lib/sg_tiny_backup/spawner.rb
|
|
55
|
+
- lib/sg_tiny_backup/templates/sg_tiny_backup.yml
|
|
56
|
+
- lib/sg_tiny_backup/utils.rb
|
|
57
|
+
- lib/sg_tiny_backup/version.rb
|
|
58
|
+
- lib/tasks/sg_tiny_backup.rake
|
|
59
|
+
- sg_tiny_backup.gemspec
|
|
60
|
+
homepage: https://github.com/SonicGarden/sg_tiny_backup
|
|
61
|
+
licenses:
|
|
62
|
+
- MIT
|
|
63
|
+
metadata:
|
|
64
|
+
homepage_uri: https://github.com/SonicGarden/sg_tiny_backup
|
|
65
|
+
source_code_uri: https://github.com/SonicGarden/sg_tiny_backup
|
|
66
|
+
changelog_uri: https://github.com/SonicGarden/sg_tiny_backup/blob/main/CHANGELOG.md
|
|
67
|
+
rubygems_mfa_required: 'true'
|
|
68
|
+
post_install_message:
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 3.2.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 3.4.19
|
|
84
|
+
signing_key:
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: Tiny backup
|
|
87
|
+
test_files: []
|