bard-backup 0.13.0 → 0.15.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 +4 -4
- data/CLAUDE.md +6 -5
- data/README.md +5 -1
- data/lib/bard/backup/destination/s3_destination.rb +7 -3
- data/lib/bard/backup/destination.rb +4 -5
- data/lib/bard/backup/file_tree.rb +0 -1
- data/lib/bard/backup/finder.rb +13 -0
- data/lib/bard/backup/tasks.rake +4 -2
- data/lib/bard/backup/version.rb +1 -1
- data/lib/bard/backup.rb +11 -2
- metadata +2 -3
- data/lib/bard/plugins/encrypt.rb +0 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d27a5d0b3ccda89bf0b15345414f6143ba1ef13e2b388545f32bbe980f36eb2f
|
|
4
|
+
data.tar.gz: 7ac24670444a407c21b9ce062feed27580da64d6f62f02326db65882a14d86b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1adf405bb06825fe7fd2e176dc1bd4565a2200977a5dead6e2d3c92bb7415b7144a9c2f08bd6995e60afa6b0069c77428bd6e509ddbca1a470b04cfd2708f0de
|
|
7
|
+
data.tar.gz: f5870aa10fcb9df65c920c8b7516ab9f82577ad263cd5578002cec49d19c2ae223837441f1b3ec7cd71ab1df85cea5cf7fd5f46f87be62b57d41fd58edbbbee5
|
data/CLAUDE.md
CHANGED
|
@@ -45,19 +45,20 @@ Tests require AWS credentials at `spec/support/credentials.json`. In CI, this is
|
|
|
45
45
|
**Entry points**:
|
|
46
46
|
- `Bard::Backup.create!` accepts destination configs (or reads from `Bard::Config`) and delegates to destination strategies. Returns a `Bard::Backup` instance with timestamp/size/destinations.
|
|
47
47
|
- `Bard::Backup::FileTree.create!` syncs configured data directories to S3.
|
|
48
|
-
- `Bard::Backup.restore!(at:)` downloads the backup nearest `at` (or the latest when omitted), decrypts it if necessary, and loads it into the local database via `backhoe` (drop-and-create). `Bard::Backup.available` lists the available
|
|
48
|
+
- `Bard::Backup.restore!(at:)` downloads the backup nearest `at` (or the latest when omitted), decrypts it if necessary, and loads it into the local database via `backhoe` (drop-and-create). `Bard::Backup.available` lists the available backups (as `Bard::Backup` objects carrying timestamp and size).
|
|
49
49
|
|
|
50
|
-
**Destination strategy pattern**: `Destination.build(config)` is a factory that picks the right class based on `:type`. `Destination.resolve(hashes)` is the single entry point both `Database.create!` and `Finder` use — it falls back to the configured destinations, normalizes a lone Hash/Array, and builds them.
|
|
50
|
+
**Destination strategy pattern**: `Destination.build(config)` is a factory that picks the right class based on `:type`. `Destination.resolve(hashes)` is the single entry point both `Database.create!` and `Finder` use — it falls back to the configured destinations, normalizes a lone Hash/Array, and builds them. A destination's `encryption_key` comes from its config, which `tasks.rake` merges from the `bard_backup` Rails credentials entry; there is no other source.
|
|
51
51
|
- `S3Destination` — dumps DB locally via backhoe, uploads to S3, runs `Deleter` for retention, verifies previous hour's backup
|
|
52
52
|
- `UploadDestination` — dumps DB and uploads to presigned URLs (multi-threaded)
|
|
53
53
|
|
|
54
|
-
**Config DSL** (loaded via `bard/plugins/backup
|
|
54
|
+
**Config DSL** (loaded via `bard/plugins/backup`; `backup` extends `Bard::Config`):
|
|
55
55
|
```ruby
|
|
56
56
|
backup do
|
|
57
57
|
s3 "primary", path: "bucket/subfolder", region: "us-west-2"
|
|
58
|
-
encrypt true # reads key from config/master.key
|
|
59
58
|
end
|
|
60
59
|
```
|
|
60
|
+
Encryption at rest is opt-in per destination via an `encryption_key` in the `bard_backup`
|
|
61
|
+
credentials entry — there is no DSL toggle.
|
|
61
62
|
|
|
62
63
|
**Key classes**:
|
|
63
64
|
- `S3Tree` — `Data.define`-based S3 wrapper used by both `S3Destination` and `FileTree`. Methods: `list_objects`, `put_file`, `put_body`, `get`, `delete_keys`, `mv`, `empty!`. Supports encryption via `Encryptor` and STS `session_token`.
|
|
@@ -65,7 +66,7 @@ end
|
|
|
65
66
|
- `Encryptor` — AES-256-GCM with HKDF-derived keys and a deterministic IV (HMAC of plaintext), enabling content-addressable encryption
|
|
66
67
|
- `Deleter` — implements the retention policy via `Filter` structs that check time-based granularities
|
|
67
68
|
- `LocalBackhoe` / `CachedLocalBackhoe` — database dump strategies (cached variant avoids conflicts when running parallel destinations)
|
|
68
|
-
- `Finder` — lists backups across destinations and selects one: by timestamp (nearest match), the latest, or the most-recent as a `Bard::Backup` (with size + destination info). Backs `Bard::Backup.available`/`.latest` and `Restore`
|
|
69
|
+
- `Finder` — lists backups across destinations and selects one: by timestamp (nearest match), the latest, or the most-recent as a `Bard::Backup` (with size + destination info). `#available` returns one `Bard::Backup` per timestamp (with size) for listing. Backs `Bard::Backup.available`/`.latest` and `Restore`
|
|
69
70
|
- `Restore` — downloads the backup selected by `Finder`, decrypts it via `S3Tree#get`, and loads it into the local database via backhoe (drop-and-create). Backs `Bard::Backup.restore!`
|
|
70
71
|
- `BackupConfig` — the `backup do ... end` DSL surface (`bard`, `disabled`, `s3 name, **kwargs`); `create!` reads `bard_config.backup.destinations` from it
|
|
71
72
|
- `Railtie` — loads `tasks.rake` which provides `bard:backup` (DB + data), `bard:backup:data` (data only), and `bard:backup:restore[at]` (restore/list) rake tasks in Rails apps
|
data/README.md
CHANGED
|
@@ -22,7 +22,6 @@ In a Rails app, configure destinations in `config/bard.rb` using the `Bard::Conf
|
|
|
22
22
|
```ruby
|
|
23
23
|
backup do
|
|
24
24
|
s3 "primary", path: "my-bucket/my-project", region: "us-west-2"
|
|
25
|
-
encrypt true # optional: encrypt payloads at rest. Reads key from config/master.key.
|
|
26
25
|
end
|
|
27
26
|
```
|
|
28
27
|
|
|
@@ -33,8 +32,13 @@ bard_backup:
|
|
|
33
32
|
- name: primary
|
|
34
33
|
access_key_id: ...
|
|
35
34
|
secret_access_key: ...
|
|
35
|
+
encryption_key: ... # optional: encrypts payloads at rest with AES-256-GCM
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
Adding `encryption_key` is what turns on encryption at rest; omit it and payloads are stored
|
|
39
|
+
in the clear. Changing its value makes existing backups undecryptable, so treat it as
|
|
40
|
+
permanent unless you re-key deliberately.
|
|
41
|
+
|
|
38
42
|
Then run via the rake tasks provided by the bundled Railtie:
|
|
39
43
|
|
|
40
44
|
```bash
|
|
@@ -21,16 +21,20 @@ module Bard
|
|
|
21
21
|
resolved_config.slice(:name, :type, :path, :region)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Credentials can supply the key, so resolve rather than reading raw config.
|
|
25
|
+
def encryption_key
|
|
26
|
+
resolved_config[:encryption_key]
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
private
|
|
25
30
|
|
|
26
|
-
# The raw config enriched with Rails credentials (by name), defaults,
|
|
27
|
-
# computed endpoint
|
|
31
|
+
# The raw config enriched with Rails credentials (by name), defaults, and
|
|
32
|
+
# the computed endpoint.
|
|
28
33
|
def resolved_config
|
|
29
34
|
@resolved_config ||= begin
|
|
30
35
|
credentials = RailsCredentials.find(name: config[:name])
|
|
31
36
|
resolved = { type: :s3, region: "us-west-2" }.merge(credentials).merge(config)
|
|
32
37
|
resolved[:endpoint] ||= "https://s3.#{resolved[:region]}.amazonaws.com"
|
|
33
|
-
resolved[:encryption_key] ||= Bard::Config.current.backup.encryption_key
|
|
34
38
|
resolved
|
|
35
39
|
end
|
|
36
40
|
end
|
|
@@ -11,8 +11,7 @@ module Bard
|
|
|
11
11
|
# Normalizes destination config into built destinations: falls back to the
|
|
12
12
|
# configured destinations when none are given, and accepts a lone Hash or
|
|
13
13
|
# an Array. A +now+ stamps every destination with one run timestamp (an
|
|
14
|
-
# explicit per-destination +now+ still wins).
|
|
15
|
-
# own encryption key (see #encryption_key), so callers don't have to inject it.
|
|
14
|
+
# explicit per-destination +now+ still wins).
|
|
16
15
|
def self.resolve(destination_hashes = nil, now: nil)
|
|
17
16
|
hashes = destination_hashes || Bard::Config.current.backup.destinations
|
|
18
17
|
hashes = hashes.is_a?(Hash) ? [hashes] : Array(hashes)
|
|
@@ -24,12 +23,12 @@ module Bard
|
|
|
24
23
|
raise NotImplementedError
|
|
25
24
|
end
|
|
26
25
|
|
|
27
|
-
private
|
|
28
|
-
|
|
29
26
|
def encryption_key
|
|
30
|
-
config[:encryption_key]
|
|
27
|
+
config[:encryption_key]
|
|
31
28
|
end
|
|
32
29
|
|
|
30
|
+
private
|
|
31
|
+
|
|
33
32
|
def now
|
|
34
33
|
@now ||= config.fetch(:now, Time.now.utc)
|
|
35
34
|
end
|
|
@@ -15,7 +15,6 @@ module Bard
|
|
|
15
15
|
return if data_paths.empty?
|
|
16
16
|
|
|
17
17
|
encryption_key = s3_config.delete(:encryption_key)
|
|
18
|
-
encryption_key ||= bard_config&.backup&.encryption_key
|
|
19
18
|
|
|
20
19
|
s3_tree = S3Tree.new(path: "#{bucket}/#{project_name}", encryption_key: encryption_key, **s3_config)
|
|
21
20
|
new(s3_tree, data_paths).call
|
data/lib/bard/backup/finder.rb
CHANGED
|
@@ -26,6 +26,19 @@ module Bard
|
|
|
26
26
|
all.map { |backup| backup[:timestamp] }.uniq.sort
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
def available
|
|
30
|
+
all
|
|
31
|
+
.group_by { |backup| backup[:timestamp] }
|
|
32
|
+
.sort_by { |timestamp, _| timestamp }
|
|
33
|
+
.map do |timestamp, backups|
|
|
34
|
+
Bard::Backup.new(
|
|
35
|
+
timestamp:,
|
|
36
|
+
size: file_size(backups.first[:destination].s3_tree, backups.first[:filename]),
|
|
37
|
+
destinations: backups.map { |backup| backup[:destination].info },
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
29
42
|
def find(at: nil)
|
|
30
43
|
backups = all
|
|
31
44
|
raise NotFound, "No backups found" if backups.empty?
|
data/lib/bard/backup/tasks.rake
CHANGED
|
@@ -26,9 +26,11 @@ namespace :bard do
|
|
|
26
26
|
|
|
27
27
|
destinations = Bard::Config.current.backup.destinations
|
|
28
28
|
if args[:at].to_s.strip.empty?
|
|
29
|
-
|
|
29
|
+
backups = Bard::Backup.available(destinations: destinations)
|
|
30
30
|
puts "Available backups:"
|
|
31
|
-
|
|
31
|
+
backups.each do |backup|
|
|
32
|
+
puts " #{backup.timestamp.iso8601} #{backup.human_size}"
|
|
33
|
+
end
|
|
32
34
|
else
|
|
33
35
|
backup = Bard::Backup.restore!(at: args[:at], destinations: destinations)
|
|
34
36
|
puts "Restored database from backup #{backup.timestamp.iso8601}."
|
data/lib/bard/backup/version.rb
CHANGED
data/lib/bard/backup.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require "bard"
|
|
2
2
|
require "bard/plugins/backup"
|
|
3
|
-
require "bard/plugins/encrypt"
|
|
4
3
|
require "bard/backup/database"
|
|
5
4
|
require "bard/backup/file_tree"
|
|
6
5
|
require "bard/backup/finder"
|
|
@@ -22,7 +21,7 @@ module Bard
|
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def self.available(destinations: nil)
|
|
25
|
-
Finder.new(destinations).
|
|
24
|
+
Finder.new(destinations).available
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def self.restore!(at:, drop_and_create: true, destinations: nil)
|
|
@@ -44,5 +43,15 @@ module Bard
|
|
|
44
43
|
destinations: destinations
|
|
45
44
|
}.compact
|
|
46
45
|
end
|
|
46
|
+
|
|
47
|
+
def human_size
|
|
48
|
+
units = %w[Bytes KB MB GB TB PB EB]
|
|
49
|
+
value, unit = size.to_f, 0
|
|
50
|
+
while value >= 1024 && unit < units.size - 1
|
|
51
|
+
value /= 1024
|
|
52
|
+
unit += 1
|
|
53
|
+
end
|
|
54
|
+
format("%5.2f %s", value, units[unit])
|
|
55
|
+
end
|
|
47
56
|
end
|
|
48
57
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bard-backup
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Micah Geisel
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: backhoe
|
|
@@ -113,7 +113,6 @@ files:
|
|
|
113
113
|
- lib/bard/backup/version.rb
|
|
114
114
|
- lib/bard/plugins/backup.rb
|
|
115
115
|
- lib/bard/plugins/backup_restore.rb
|
|
116
|
-
- lib/bard/plugins/encrypt.rb
|
|
117
116
|
- sig/bard/backup.rbs
|
|
118
117
|
homepage: https://github.com/botandrose/bard-backup
|
|
119
118
|
licenses:
|
data/lib/bard/plugins/encrypt.rb
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
require "bard/plugins/backup"
|
|
2
|
-
|
|
3
|
-
module Bard
|
|
4
|
-
class BackupConfig
|
|
5
|
-
def encrypt(value = nil)
|
|
6
|
-
if value.nil?
|
|
7
|
-
@encrypt
|
|
8
|
-
else
|
|
9
|
-
@encrypt = value
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def encryption_key
|
|
14
|
-
return nil unless encrypt
|
|
15
|
-
File.read("config/master.key").strip
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|