sqlite_backups 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/README.md +50 -0
- data/Rakefile +8 -0
- data/app/controllers/backups/backups_controller.rb +19 -0
- data/app/jobs/backups/all_job.rb +9 -0
- data/app/jobs/backups/application_job.rb +4 -0
- data/app/jobs/backups/database_job.rb +7 -0
- data/app/models/backups/application_record.rb +5 -0
- data/app/models/backups/backup.rb +16 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20250430005926_create_sqlite_backup_backups.rb +9 -0
- data/lib/backups/create.rb +50 -0
- data/lib/backups/engine.rb +13 -0
- data/lib/backups/railtie.rb +7 -0
- data/lib/backups/restore.rb +63 -0
- data/lib/backups/tasks.rake +15 -0
- data/lib/backups/version.rb +3 -0
- data/lib/backups.rb +36 -0
- data/lib/generators/backups/install/USAGE +5 -0
- data/lib/generators/backups/install/install_generator.rb +9 -0
- data/lib/sqlite_backups.rb +1 -0
- data/lib/tasks/backups_tasks.rake +6 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5befa74d4c48692314c28f8ca62ba631a87faaff37c3ee83ee792267127bad8b
|
4
|
+
data.tar.gz: 04e12d29cc5c7285487fd63374d598d5d2e687c113f2dd560a556701d4a80d40
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6f610817967e118afb908977f1d14c06cacf0400ed9cb202792914181326430f6a8f219f4f8eb74990d04430c3d09d29035ca66ca9c03ce1972564dbbcd0a9b1
|
7
|
+
data.tar.gz: 6bd671346cc80c2a21a160d32e74b2d09a20a2b91645416edbeea89490979edd1b72e9177d36631b006d39d999e1054c43078e551b18f77b6c8d861851a5f207
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# SqliteBackups
|
2
|
+
|
3
|
+
A dead simple Rails engine to backup your Sqlite databases utilizing Active
|
4
|
+
Storage.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
To backup a database:
|
9
|
+
```bash
|
10
|
+
$ bin/rails backup:[database_name]
|
11
|
+
```
|
12
|
+
|
13
|
+
Alternatively, you can use a job:
|
14
|
+
```ruby
|
15
|
+
Backups::DatabaseJob.perform_later(database_name)
|
16
|
+
Backups::AllJob.perform_later
|
17
|
+
```
|
18
|
+
|
19
|
+
To restore a database:
|
20
|
+
```bash
|
21
|
+
$ bin/rails restore:[database_name]
|
22
|
+
```
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
Add this line to your Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem "sqlite_backups"
|
29
|
+
```
|
30
|
+
|
31
|
+
And then execute:
|
32
|
+
```bash
|
33
|
+
$ bundle
|
34
|
+
```
|
35
|
+
|
36
|
+
Then run the installer to copy over the migration and mount a route we use for
|
37
|
+
restoring:
|
38
|
+
```bash
|
39
|
+
$ bin/rails backups:install
|
40
|
+
```
|
41
|
+
|
42
|
+
These are the available configuration options:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
config.backups.storage_service = :backups
|
46
|
+
config.backups.retention = 1.day
|
47
|
+
```
|
48
|
+
|
49
|
+
## License
|
50
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Backups
|
2
|
+
class BackupsController < ActionController::Base
|
3
|
+
before_action :verify_token
|
4
|
+
|
5
|
+
def show
|
6
|
+
files = Backup.where(database: params[:name]).map do
|
7
|
+
{ date: it.created_at, key: it.file.key }
|
8
|
+
end
|
9
|
+
|
10
|
+
render json: { name: params[:name], files: }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def verify_token
|
16
|
+
raise(ActiveRecord::RecordNotFound) unless Backups.valid_token?(params[:token])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Backups
|
2
|
+
class Backup < ApplicationRecord
|
3
|
+
self.table_name = "backups"
|
4
|
+
|
5
|
+
validates :database, inclusion: { in: Backups.databases.keys }
|
6
|
+
|
7
|
+
has_one_attached :file, service: Backups.storage_service, dependent: :purge_later
|
8
|
+
|
9
|
+
scope :expired,
|
10
|
+
-> { where(created_at: ..(Backups.retention || 1.day).ago) }
|
11
|
+
|
12
|
+
def formated_date
|
13
|
+
created_at.strftime("%Y-%m-%d_%H:%M")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Backups
|
2
|
+
class Create
|
3
|
+
def initialize(name)
|
4
|
+
@name = name
|
5
|
+
@path = Backups.databases[name.to_s]
|
6
|
+
@key = SecureRandom.hex(16)
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
execute_backup
|
11
|
+
|
12
|
+
Backup.create(database: name).tap do
|
13
|
+
it.file.attach(io: compressed_data, filename: key)
|
14
|
+
File.delete(backup_path)
|
15
|
+
File.delete("#{backup_path}.gz")
|
16
|
+
end
|
17
|
+
|
18
|
+
expire_old_backups
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :name, :path, :key
|
24
|
+
|
25
|
+
def execute_backup
|
26
|
+
`sqlite3 #{path} '.backup #{backup_path}'`
|
27
|
+
end
|
28
|
+
|
29
|
+
def compressed_data
|
30
|
+
gzip_path = "#{backup_path}.gz"
|
31
|
+
Zlib::GzipWriter.open(gzip_path) do |gz|
|
32
|
+
File.open(backup_path, "rb") do |f|
|
33
|
+
IO.copy_stream(f, gz)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
File.open(gzip_path, "rb")
|
37
|
+
end
|
38
|
+
|
39
|
+
def backup_path
|
40
|
+
Rails.root.join("tmp/#{name}_backup_#{key}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def expire_old_backups
|
44
|
+
Backup.where(database: name).expired.each do
|
45
|
+
it.file.purge
|
46
|
+
it.destroy
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Backups
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Backups
|
4
|
+
|
5
|
+
config.backups = ActiveSupport::OrderedOptions.new
|
6
|
+
|
7
|
+
initializer "backups.config" do
|
8
|
+
config.backups.each do |name, value|
|
9
|
+
Backups.public_send(:"#{name}=", value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "cli/ui"
|
2
|
+
|
3
|
+
module Backups
|
4
|
+
class Restore
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
raise StandardError, "No backups found" if backups["files"].blank?
|
11
|
+
raise StandardError, "File not found" unless service.exist?(key)
|
12
|
+
|
13
|
+
File.open(path, "wb") do |file|
|
14
|
+
file.write(ActiveSupport::Gzip.decompress(service.download(key)))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :name
|
21
|
+
|
22
|
+
def path
|
23
|
+
Backups.databases(env_name: Rails.env)[name.to_s]
|
24
|
+
end
|
25
|
+
|
26
|
+
def service
|
27
|
+
ActiveStorage::Blob.services.fetch(Backups.storage_service)
|
28
|
+
end
|
29
|
+
|
30
|
+
def token
|
31
|
+
Backups.generate_token
|
32
|
+
end
|
33
|
+
|
34
|
+
def key
|
35
|
+
@key ||=
|
36
|
+
if backups["files"].count == 1
|
37
|
+
backups["files"].first["key"]
|
38
|
+
else
|
39
|
+
CLI::UI.ask("Pick a backup to restore", options:).then do |date|
|
40
|
+
backups["files"].find { it["date"].to_s == date }["key"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def options
|
46
|
+
backups[:files].pluck("date").map(&:to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def uri
|
50
|
+
host = `RAILS_ENV=production bin/rails runner 'puts Rails.application.config.x.url'`
|
51
|
+
URI("#{host.strip}/rails/backups/#{name}").
|
52
|
+
tap { it.query = URI.encode_www_form(token:) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def backups
|
56
|
+
@backups ||= begin
|
57
|
+
ActiveSupport.parse_json_times = true
|
58
|
+
ActiveSupport::JSON.
|
59
|
+
decode(Net::HTTP.get_response(uri).body).with_indifferent_access
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
namespace :backup do
|
2
|
+
Rails.application.config_for(:database).keys.each do |name|
|
3
|
+
task name => :environment do
|
4
|
+
Backups::DatabaseJob.perform_later(name)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
namespace :restore do
|
10
|
+
Rails.application.config_for(:database).keys.each do |name|
|
11
|
+
task name => :environment do
|
12
|
+
Backups::Restore.new(name).run
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/backups.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "backups/version"
|
2
|
+
require "backups/engine"
|
3
|
+
require "backups/railtie"
|
4
|
+
require "backups/create"
|
5
|
+
require "backups/restore"
|
6
|
+
|
7
|
+
module Backups
|
8
|
+
mattr_accessor :retention, :storage_service
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def databases(env_name: "production")
|
12
|
+
ActiveRecord::Base.
|
13
|
+
configurations.
|
14
|
+
configs_for(env_name: env_name).
|
15
|
+
to_h { [ it.name, it.database ] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_token
|
19
|
+
verifier.generate(
|
20
|
+
SecureRandom.hex(16), expires_in: 5.minutes, purpose: :fetch_backups
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_token?(token)
|
25
|
+
verifier.verify(token, purpose: :fetch_backups).present?
|
26
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def verifier
|
33
|
+
Backup.generated_token_verifier
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "backups"
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sqlite_backups
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Pezza
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rails
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 8.0.2
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 8.0.2
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: cli-ui
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
email:
|
41
|
+
- pezza@hey.com
|
42
|
+
executables: []
|
43
|
+
extensions: []
|
44
|
+
extra_rdoc_files: []
|
45
|
+
files:
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- app/controllers/backups/backups_controller.rb
|
49
|
+
- app/jobs/backups/all_job.rb
|
50
|
+
- app/jobs/backups/application_job.rb
|
51
|
+
- app/jobs/backups/database_job.rb
|
52
|
+
- app/models/backups/application_record.rb
|
53
|
+
- app/models/backups/backup.rb
|
54
|
+
- config/routes.rb
|
55
|
+
- db/migrate/20250430005926_create_sqlite_backup_backups.rb
|
56
|
+
- lib/backups.rb
|
57
|
+
- lib/backups/create.rb
|
58
|
+
- lib/backups/engine.rb
|
59
|
+
- lib/backups/railtie.rb
|
60
|
+
- lib/backups/restore.rb
|
61
|
+
- lib/backups/tasks.rake
|
62
|
+
- lib/backups/version.rb
|
63
|
+
- lib/generators/backups/install/USAGE
|
64
|
+
- lib/generators/backups/install/install_generator.rb
|
65
|
+
- lib/sqlite_backups.rb
|
66
|
+
- lib/tasks/backups_tasks.rake
|
67
|
+
homepage: https://github.com/npezza93/sqlite_backup
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata:
|
71
|
+
rubygems_mfa_required: 'true'
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 3.2.0
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubygems_version: 3.6.8
|
87
|
+
specification_version: 4
|
88
|
+
summary: Simple sqlite backup for Rails
|
89
|
+
test_files: []
|