ruborg 0.4.0 → 0.5.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/CHANGELOG.md +15 -0
- data/README.md +70 -3
- data/lib/ruborg/cli.rb +20 -0
- data/lib/ruborg/config.rb +1 -1
- data/lib/ruborg/version.rb +1 -1
- data/ruborg.yml.example +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1233e04a2f95e8e8aadb9ad97d043b2d9b52c446821ff89495073a6a8762b6cc
|
|
4
|
+
data.tar.gz: ee3fd1ae2256299120d7f78aac3243c2de44f8f3c072c4c29dd44cb761caf769
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59d6ad7e5797cbbd964390d0425bf1392695c24f938a40978ddea371552da84604bf0a8eb189b207318ed68dba966311d45659f2b6abd3a59711f766465a5b36
|
|
7
|
+
data.tar.gz: 6c77a312c9b820dd22d7f0396dbb24fac70da4654023c89eebc94c962d367b963a448a923ce23574862276452c441e3ebd723601fd58a2634e3337d402a9c066
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2025-10-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Hostname Validation**: Optional `hostname` configuration key to restrict backup operations to specific hosts
|
|
14
|
+
- Can be configured globally or per-repository
|
|
15
|
+
- Repository-specific hostname overrides global setting
|
|
16
|
+
- Validates system hostname before backup, list, restore, check operations
|
|
17
|
+
- Prevents accidental execution of backups on wrong machines
|
|
18
|
+
- Displayed in `info` command output
|
|
19
|
+
- Comprehensive test coverage for hostname validation (6 new test cases)
|
|
20
|
+
- Documentation for hostname feature in example config and README
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `info` command now displays hostname when configured (global or per-repository)
|
|
24
|
+
|
|
10
25
|
## [0.4.0] - 2025-10-06
|
|
11
26
|
|
|
12
27
|
### Added
|
data/README.md
CHANGED
|
@@ -24,7 +24,8 @@ e- ⏰ **Retention Policies** - Configure backup retention (hourly, daily, weekl
|
|
|
24
24
|
- 📋 **Repository Descriptions** - Document each repository's purpose
|
|
25
25
|
- 📈 **Summary View** - Quick overview of all repositories and their configurations
|
|
26
26
|
- 🔧 **Custom Borg Path** - Support for custom Borg executable paths per repository
|
|
27
|
-
-
|
|
27
|
+
- 🏠 **Hostname Validation** - NEW! Restrict backups to specific hosts (global or per-repository)
|
|
28
|
+
- ✅ **Well-tested** - Comprehensive test suite with RSpec (153 tests)
|
|
28
29
|
- 🔒 **Security-focused** - Path validation, safe YAML loading, command injection protection
|
|
29
30
|
|
|
30
31
|
## Prerequisites
|
|
@@ -127,6 +128,7 @@ repositories:
|
|
|
127
128
|
- name: databases
|
|
128
129
|
description: "MySQL and PostgreSQL database dumps"
|
|
129
130
|
path: /mnt/backup/databases
|
|
131
|
+
hostname: dbserver.local # Optional: repository-specific hostname override
|
|
130
132
|
# Repository-specific passbolt (overrides global)
|
|
131
133
|
passbolt:
|
|
132
134
|
resource_id: "db-specific-passbolt-id"
|
|
@@ -161,8 +163,9 @@ repositories:
|
|
|
161
163
|
|
|
162
164
|
**Configuration Features:**
|
|
163
165
|
- **Descriptions**: Add `description` field to document each repository's purpose
|
|
164
|
-
- **
|
|
165
|
-
- **
|
|
166
|
+
- **Hostname Validation**: Optional `hostname` field to restrict backups to specific hosts (global or per-repository)
|
|
167
|
+
- **Global Settings**: Hostname, compression, encryption, auto_init, log_file, borg_path, borg_options, and retention apply to all repositories
|
|
168
|
+
- **Per-Repository Overrides**: Any global setting can be overridden at the repository level (including hostname and custom borg_path)
|
|
166
169
|
- **Custom Borg Path**: Specify a custom Borg executable path if borg is not in PATH or to use a specific version
|
|
167
170
|
- **Retention Policies**: Define how many backups to keep (hourly, daily, weekly, monthly, yearly)
|
|
168
171
|
- **Multiple Sources**: Each repository can have multiple backup sources with their own exclude patterns
|
|
@@ -338,6 +341,70 @@ repositories:
|
|
|
338
341
|
|
|
339
342
|
When enabled, ruborg will automatically run `borg init` if the repository doesn't exist when you run `backup`, `list`, or `info` commands. The passphrase will be retrieved from Passbolt if configured.
|
|
340
343
|
|
|
344
|
+
## Hostname Validation
|
|
345
|
+
|
|
346
|
+
Restrict backup operations to specific hosts using the optional `hostname` configuration key. This prevents accidental execution of backups on the wrong machine.
|
|
347
|
+
|
|
348
|
+
### Global Hostname
|
|
349
|
+
|
|
350
|
+
Apply hostname restriction to all repositories:
|
|
351
|
+
|
|
352
|
+
```yaml
|
|
353
|
+
# Global hostname - applies to all repositories
|
|
354
|
+
hostname: myserver.local
|
|
355
|
+
|
|
356
|
+
repositories:
|
|
357
|
+
- name: documents
|
|
358
|
+
path: /mnt/backup/documents
|
|
359
|
+
sources:
|
|
360
|
+
- name: main
|
|
361
|
+
paths:
|
|
362
|
+
- /home/user/documents
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Per-Repository Hostname
|
|
366
|
+
|
|
367
|
+
Override global hostname for specific repositories:
|
|
368
|
+
|
|
369
|
+
```yaml
|
|
370
|
+
# Global hostname for most repositories
|
|
371
|
+
hostname: mainserver.local
|
|
372
|
+
|
|
373
|
+
repositories:
|
|
374
|
+
# Uses global hostname (mainserver.local)
|
|
375
|
+
- name: documents
|
|
376
|
+
path: /mnt/backup/documents
|
|
377
|
+
sources:
|
|
378
|
+
- name: main
|
|
379
|
+
paths:
|
|
380
|
+
- /home/user/documents
|
|
381
|
+
|
|
382
|
+
# Override with repository-specific hostname
|
|
383
|
+
- name: databases
|
|
384
|
+
hostname: dbserver.local # Only runs on dbserver.local
|
|
385
|
+
path: /mnt/backup/databases
|
|
386
|
+
sources:
|
|
387
|
+
- name: mysql
|
|
388
|
+
paths:
|
|
389
|
+
- /var/lib/mysql/dumps
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**How it works:**
|
|
393
|
+
- Before backup, list, restore, or check operations, Ruborg validates the system hostname
|
|
394
|
+
- If configured hostname doesn't match the current hostname, the operation fails with an error
|
|
395
|
+
- Repository-specific hostname takes precedence over global hostname
|
|
396
|
+
- If no hostname is configured, validation is skipped
|
|
397
|
+
|
|
398
|
+
**Example error:**
|
|
399
|
+
```
|
|
400
|
+
Error: Hostname mismatch: configuration is for 'dbserver.local' but current hostname is 'mainserver.local'
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Use cases:**
|
|
404
|
+
- **Multi-server environments**: Different servers backup to different repositories
|
|
405
|
+
- **Development vs Production**: Prevent production config from running on dev machines
|
|
406
|
+
- **Safety**: Avoid accidentally running wrong backups on shared configuration files
|
|
407
|
+
|
|
341
408
|
## Security Configuration
|
|
342
409
|
|
|
343
410
|
Ruborg provides configurable security options via `borg_options`:
|
data/lib/ruborg/cli.rb
CHANGED
|
@@ -54,6 +54,7 @@ module Ruborg
|
|
|
54
54
|
def backup
|
|
55
55
|
@logger.info("Starting backup operation with config: #{options[:config]}")
|
|
56
56
|
config = Config.new(options[:config])
|
|
57
|
+
validate_hostname(config.global_settings)
|
|
57
58
|
backup_repositories(config)
|
|
58
59
|
rescue Error => e
|
|
59
60
|
@logger.error("Backup failed: #{e.message}")
|
|
@@ -72,6 +73,7 @@ module Ruborg
|
|
|
72
73
|
|
|
73
74
|
global_settings = config.global_settings
|
|
74
75
|
merged_config = global_settings.merge(repo_config)
|
|
76
|
+
validate_hostname(merged_config)
|
|
75
77
|
passphrase = fetch_passphrase_for_repo(merged_config)
|
|
76
78
|
borg_opts = merged_config["borg_options"] || {}
|
|
77
79
|
borg_path = merged_config["borg_path"]
|
|
@@ -108,6 +110,7 @@ module Ruborg
|
|
|
108
110
|
|
|
109
111
|
global_settings = config.global_settings
|
|
110
112
|
merged_config = global_settings.merge(repo_config)
|
|
113
|
+
validate_hostname(merged_config)
|
|
111
114
|
passphrase = fetch_passphrase_for_repo(merged_config)
|
|
112
115
|
borg_opts = merged_config["borg_options"] || {}
|
|
113
116
|
borg_path = merged_config["borg_path"]
|
|
@@ -175,6 +178,7 @@ module Ruborg
|
|
|
175
178
|
@logger.info("Checking repository compatibility")
|
|
176
179
|
config = Config.new(options[:config])
|
|
177
180
|
global_settings = config.global_settings
|
|
181
|
+
validate_hostname(global_settings)
|
|
178
182
|
|
|
179
183
|
# Show Borg version first
|
|
180
184
|
borg_version = Repository.borg_version
|
|
@@ -207,6 +211,7 @@ module Ruborg
|
|
|
207
211
|
@logger.info("Checking repository: #{repo_name}")
|
|
208
212
|
|
|
209
213
|
merged_config = global_settings.merge(repo_config)
|
|
214
|
+
validate_hostname(merged_config)
|
|
210
215
|
passphrase = fetch_passphrase_for_repo(merged_config)
|
|
211
216
|
borg_opts = merged_config["borg_options"] || {}
|
|
212
217
|
borg_path = merged_config["borg_path"]
|
|
@@ -267,6 +272,7 @@ module Ruborg
|
|
|
267
272
|
|
|
268
273
|
# Show global settings
|
|
269
274
|
puts "Global Settings:"
|
|
275
|
+
puts " Hostname: #{global_settings["hostname"]}" if global_settings["hostname"]
|
|
270
276
|
puts " Compression: #{global_settings["compression"] || "lz4 (default)"}"
|
|
271
277
|
puts " Encryption: #{global_settings["encryption"] || "repokey (default)"}"
|
|
272
278
|
puts " Auto-init: #{global_settings["auto_init"] || false}"
|
|
@@ -284,6 +290,7 @@ module Ruborg
|
|
|
284
290
|
puts " Description: #{repo["description"]}" if repo["description"]
|
|
285
291
|
|
|
286
292
|
# Show repo-specific overrides
|
|
293
|
+
puts " Hostname: #{repo["hostname"]}" if repo["hostname"]
|
|
287
294
|
puts " Compression: #{repo["compression"]}" if repo["compression"]
|
|
288
295
|
puts " Encryption: #{repo["encryption"]}" if repo["encryption"]
|
|
289
296
|
puts " Auto-init: #{repo["auto_init"]}" unless repo["auto_init"].nil?
|
|
@@ -389,6 +396,7 @@ module Ruborg
|
|
|
389
396
|
|
|
390
397
|
# Merge global settings with repo-specific settings (repo-specific takes precedence)
|
|
391
398
|
merged_config = global_settings.merge(repo_config)
|
|
399
|
+
validate_hostname(merged_config)
|
|
392
400
|
|
|
393
401
|
passphrase = fetch_passphrase_for_repo(merged_config)
|
|
394
402
|
borg_opts = merged_config["borg_options"] || {}
|
|
@@ -460,6 +468,18 @@ module Ruborg
|
|
|
460
468
|
name.gsub(/[^a-zA-Z0-9._-]/, "_")
|
|
461
469
|
end
|
|
462
470
|
|
|
471
|
+
def validate_hostname(config)
|
|
472
|
+
configured_hostname = config["hostname"]
|
|
473
|
+
return if configured_hostname.nil? || configured_hostname.empty?
|
|
474
|
+
|
|
475
|
+
current_hostname = `hostname`.strip
|
|
476
|
+
return if current_hostname == configured_hostname
|
|
477
|
+
|
|
478
|
+
raise ConfigError,
|
|
479
|
+
"Hostname mismatch: configuration is for '#{configured_hostname}' " \
|
|
480
|
+
"but current hostname is '#{current_hostname}'"
|
|
481
|
+
end
|
|
482
|
+
|
|
463
483
|
# Wrapper class to adapt repository config to existing Backup class
|
|
464
484
|
class BackupConfig
|
|
465
485
|
def initialize(repo_config, merged_settings)
|
data/lib/ruborg/config.rb
CHANGED
data/lib/ruborg/version.rb
CHANGED
data/ruborg.yml.example
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# # Edit ruborg.yml with your settings
|
|
7
7
|
|
|
8
8
|
# Global settings (applied to all repositories unless overridden)
|
|
9
|
+
hostname: myserver.local # Optional: restrict configuration to specific hostname
|
|
9
10
|
compression: lz4 # Options: lz4 (fast), zstd (balanced), lzma (high compression), none
|
|
10
11
|
encryption: repokey # Options: repokey, keyfile, none (NOT recommended)
|
|
11
12
|
auto_init: true # Automatically initialize repositories on first use
|
|
@@ -65,6 +66,7 @@ repositories:
|
|
|
65
66
|
- name: databases
|
|
66
67
|
description: "MySQL and PostgreSQL database dumps"
|
|
67
68
|
path: /mnt/backup/borg-databases
|
|
69
|
+
hostname: dbserver.local # Optional: repository-specific hostname override
|
|
68
70
|
retention_mode: per_file # Each file gets its own archive
|
|
69
71
|
# Repository-specific passbolt (overrides global)
|
|
70
72
|
passbolt:
|