ruborg 0.2.0 โ†’ 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca72109e555872d29ba3b56a07b6ed10ea5b41fec351631232265c42216fd376
4
- data.tar.gz: 2049a2ba62a07171f2fc62237243043be6dacf83200f0ffa4f10b6a05285d5b1
3
+ metadata.gz: b3dfe277e22cf29ef1132f479b8e8a969e87c69b383529b7f05eb7238ac5ea07
4
+ data.tar.gz: fcf98cabcd5f4636fc513e69e3b446747c9ca2f054f6480f4da60416ec865f0d
5
5
  SHA512:
6
- metadata.gz: ce9e39076fbb118801cf910ca241e930d2139e0f15c8adaf139a62e36bf8dad5a7495d81618db5f82d45d7c1e6c0784c143258afbc0c4ad524945d13b91ca345
7
- data.tar.gz: 94534dfd27da62827c5976bcbbe291d9c6906a7e3355905a970b6f9ea65bb3b7612ae50373445a249f2b57ad9283028ca06ab5db87d85f05e6ff76e3b6d34121
6
+ metadata.gz: 159296240a1cec2e7791edb3689bf032569d8c1f4fda0ac5c1522e92076fec8c719dba3117326c9fcb9e074ffa295ed71a75b500657625d1ce71fab391b441c2
7
+ data.tar.gz: f6385fa1ff56520719b1f8b3985931b4083b3139889b34ed4d1dcb5d449eec8ba98e96f030988cf03cdcb5744f47255ef3b7a605e7bb040d6b1f2c0446ab3b81
data/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2025-10-05
11
+
12
+ ### Added
13
+ - Auto-initialization feature: Set `auto_init: true` in config to automatically initialize repositories on first use
14
+ - Multi-repository configuration support with per-repository sources
15
+ - `--repository` / `-r` option to target specific repository in multi-repo configs
16
+ - `--all` option to backup all repositories at once
17
+ - Repository-specific Passbolt integration (overrides global settings)
18
+ - Per-source exclude patterns in multi-repo configs
19
+ - BackupConfig wrapper class for multi-repo compatibility
20
+ - Automatic format detection (single vs multi-repo)
21
+ - Support for multiple backup sources per repository
22
+ - Global settings with per-repository overrides
23
+ - `log_file` configuration option to set log path in config file
24
+ - Log file priority: CLI option > config file > default
25
+
26
+ ### Changed
27
+ - Config class now detects and handles both single-repo and multi-repo formats
28
+ - Backup command automatically routes to single or multi-repo implementation
29
+ - Archive naming includes repository name for multi-repo configs
30
+ - CLI now reads log_file from config if --log option not provided
31
+
10
32
  ## [0.2.0] - 2025-10-05
11
33
 
12
34
  ### Added
data/README.md CHANGED
@@ -15,6 +15,8 @@ A friendly Ruby frontend for [Borg Backup](https://www.borgbackup.org/). Ruborg
15
15
  - ๐Ÿ—‚๏ธ **Selective Restore** - Restore individual files or directories from archives
16
16
  - ๐Ÿงน **Auto-cleanup** - Optionally remove source files after successful backup
17
17
  - ๐Ÿ“Š **Logging** - Comprehensive logging with daily rotation
18
+ - ๐Ÿ—„๏ธ **Multi-Repository** - Manage multiple backup repositories with different sources
19
+ - ๐Ÿ”„ **Auto-initialization** - Automatically initialize repositories on first use
18
20
  - โœ… **Well-tested** - Comprehensive test suite with RSpec
19
21
 
20
22
  ## Prerequisites
@@ -63,7 +65,9 @@ gem install ruborg
63
65
 
64
66
  ## Configuration
65
67
 
66
- Create a `ruborg.yml` configuration file:
68
+ Ruborg supports two configuration formats: **single repository** (legacy) and **multi-repository** (recommended for complex setups).
69
+
70
+ ### Single Repository Configuration
67
71
 
68
72
  ```yaml
69
73
  # Repository path
@@ -73,15 +77,11 @@ repository: /path/to/borg/repository
73
77
  backup_paths:
74
78
  - /home/user/documents
75
79
  - /home/user/projects
76
- - /etc
77
80
 
78
81
  # Exclude patterns
79
82
  exclude_patterns:
80
83
  - "*.tmp"
81
84
  - "*.log"
82
- - "*/.cache/*"
83
- - "*/node_modules/*"
84
- - "*/.git/*"
85
85
 
86
86
  # Compression algorithm (lz4, zstd, zlib, lzma, none)
87
87
  compression: lz4
@@ -92,9 +92,62 @@ encryption: repokey
92
92
  # Passbolt integration (optional)
93
93
  passbolt:
94
94
  resource_id: "your-passbolt-resource-uuid"
95
+
96
+ # Auto-initialize repository (optional, default: false)
97
+ auto_init: true
98
+
99
+ # Log file path (optional, default: ~/.ruborg/logs/ruborg.log)
100
+ log_file: /var/log/ruborg.log
101
+ ```
102
+
103
+ ### Multi-Repository Configuration
104
+
105
+ For managing multiple repositories with different sources:
106
+
107
+ ```yaml
108
+ # Global settings (applied to all repositories unless overridden)
109
+ compression: lz4
110
+ encryption: repokey
111
+ auto_init: true
112
+ passbolt:
113
+ resource_id: "global-passbolt-id"
114
+
115
+ # Multiple repositories
116
+ repositories:
117
+ - name: documents
118
+ path: /mnt/backup/documents
119
+ sources:
120
+ - name: home-docs
121
+ paths:
122
+ - /home/user/documents
123
+ exclude:
124
+ - "*.tmp"
125
+ - name: work-docs
126
+ paths:
127
+ - /home/user/work
128
+ exclude:
129
+ - "*.log"
130
+
131
+ - name: databases
132
+ path: /mnt/backup/databases
133
+ # Repository-specific passbolt (overrides global)
134
+ passbolt:
135
+ resource_id: "db-specific-passbolt-id"
136
+ sources:
137
+ - name: mysql
138
+ paths:
139
+ - /var/lib/mysql/dumps
140
+ - name: postgres
141
+ paths:
142
+ - /var/lib/postgresql/dumps
95
143
  ```
96
144
 
97
- See `ruborg.yml.example` for a complete configuration template.
145
+ **Multi-repo benefits:**
146
+ - Organize backups by type (documents, databases, media)
147
+ - Different encryption keys per repository
148
+ - Multiple sources per repository
149
+ - Per-source exclude patterns
150
+ - Repository-specific settings override global ones
98
151
 
99
152
  ## Usage
100
153
 
@@ -110,6 +163,7 @@ ruborg init /path/to/repository --passbolt-id "resource-uuid"
110
163
 
111
164
  ### Create a Backup
112
165
 
166
+ **Single repository:**
113
167
  ```bash
114
168
  # Using default configuration (ruborg.yml)
115
169
  ruborg backup
@@ -124,6 +178,18 @@ ruborg backup --name "my-backup-2025-10-04"
124
178
  ruborg backup --remove-source
125
179
  ```
126
180
 
181
+ **Multi-repository:**
182
+ ```bash
183
+ # Backup specific repository
184
+ ruborg backup --repository documents
185
+
186
+ # Backup all repositories
187
+ ruborg backup --all
188
+
189
+ # Backup specific repository with custom name
190
+ ruborg backup --repository databases --name "db-backup-2025-10-05"
191
+ ```
192
+
127
193
  ### List Archives
128
194
 
129
195
  ```bash
@@ -151,13 +217,23 @@ ruborg info
151
217
 
152
218
  ## Logging
153
219
 
154
- Ruborg automatically logs all operations to `~/.ruborg/logs/ruborg.log` with daily rotation. You can specify a custom log file:
220
+ Ruborg automatically logs all operations with daily rotation. Log file location priority:
221
+
222
+ 1. **CLI option** (highest priority): `--log /path/to/custom.log`
223
+ 2. **Config file**: `log_file: /path/to/log.log`
224
+ 3. **Default**: `~/.ruborg/logs/ruborg.log`
225
+
226
+ **Examples:**
155
227
 
156
228
  ```bash
157
- ruborg backup --log /path/to/custom.log
229
+ # Use CLI option (overrides config)
230
+ ruborg backup --log /var/log/ruborg.log
231
+
232
+ # Or set in config file
233
+ log_file: /var/log/ruborg.log
158
234
  ```
159
235
 
160
- Logs include:
236
+ **Logs include:**
161
237
  - Operation start/completion timestamps
162
238
  - Paths being backed up
163
239
  - Archive names created
@@ -191,20 +267,41 @@ passbolt:
191
267
 
192
268
  Ruborg will automatically retrieve the passphrase when performing backup operations.
193
269
 
270
+ ## Auto-initialization
271
+
272
+ Set `auto_init: true` in your configuration file to automatically initialize the repository on first use:
273
+
274
+ ```yaml
275
+ repository: /path/to/borg/repository
276
+ auto_init: true
277
+ passbolt:
278
+ resource_id: "your-passbolt-resource-uuid"
279
+ backup_paths:
280
+ - /path/to/backup
281
+ ```
282
+
283
+ 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.
284
+
194
285
  ## Command Reference
195
286
 
196
287
  | Command | Description | Options |
197
288
  |---------|-------------|---------|
198
289
  | `init REPOSITORY` | Initialize a new Borg repository | `--passphrase`, `--passbolt-id`, `--log` |
199
- | `backup` | Create a backup using config file | `--config`, `--name`, `--remove-source`, `--log` |
200
- | `list` | List all archives in repository | `--config`, `--log` |
201
- | `restore ARCHIVE` | Restore files from archive | `--config`, `--destination`, `--path`, `--log` |
202
- | `info` | Show repository information | `--config`, `--log` |
290
+ | `backup` | Create a backup using config file | `--config`, `--name`, `--remove-source`, `--repository`, `--all`, `--log` |
291
+ | `list` | List all archives in repository | `--config`, `--repository`, `--log` |
292
+ | `restore ARCHIVE` | Restore files from archive | `--config`, `--destination`, `--path`, `--repository`, `--log` |
293
+ | `info` | Show repository information | `--config`, `--repository`, `--log` |
203
294
 
204
295
  ### Global Options
205
296
 
206
297
  - `--config`: Path to configuration file (default: `ruborg.yml`)
207
- - `--log`: Path to log file (default: `~/.ruborg/logs/ruborg.log`)
298
+ - `--log`: Path to log file (overrides config, default: `~/.ruborg/logs/ruborg.log`)
299
+ - `--repository` / `-r`: Repository name (required for multi-repo configs)
300
+
301
+ ### Multi-Repository Options
302
+
303
+ - `--all`: Backup all repositories (multi-repo config only)
304
+ - `--repository NAME`: Target specific repository by name
208
305
 
209
306
  ## Development
210
307
 
data/lib/ruborg/cli.rb CHANGED
@@ -7,10 +7,21 @@ module Ruborg
7
7
  class CLI < Thor
8
8
  class_option :config, type: :string, default: "ruborg.yml", desc: "Path to configuration file"
9
9
  class_option :log, type: :string, desc: "Path to log file"
10
+ class_option :repository, type: :string, aliases: "-r", desc: "Repository name (for multi-repo configs)"
10
11
 
11
12
  def initialize(*args)
12
13
  super
13
- @logger = RuborgLogger.new(log_file: options[:log])
14
+ # Priority: CLI option > config file > default
15
+ log_path = options[:log]
16
+ unless log_path
17
+ # Try to load config to get log_file setting
18
+ config_path = options[:config] || "ruborg.yml"
19
+ if File.exist?(config_path)
20
+ config_data = YAML.load_file(config_path) rescue {}
21
+ log_path = config_data["log_file"]
22
+ end
23
+ end
24
+ @logger = RuborgLogger.new(log_file: log_path)
14
25
  end
15
26
 
16
27
  desc "init REPOSITORY", "Initialize a new Borg repository"
@@ -31,26 +42,16 @@ module Ruborg
31
42
  desc "backup", "Create a backup using configuration file"
32
43
  option :name, type: :string, desc: "Archive name"
33
44
  option :remove_source, type: :boolean, default: false, desc: "Remove source files after successful backup"
45
+ option :all, type: :boolean, default: false, desc: "Backup all repositories (multi-repo config only)"
34
46
  def backup
35
47
  @logger.info("Starting backup operation with config: #{options[:config]}")
36
48
  config = Config.new(options[:config])
37
- @logger.info("Backing up paths: #{config.backup_paths.join(', ')}")
38
- passphrase = fetch_passphrase_from_config(config)
39
-
40
- repo = Repository.new(config.repository, passphrase: passphrase)
41
- backup = Backup.new(repo, config: config)
42
-
43
- archive_name = options[:name] || Time.now.strftime("%Y-%m-%d_%H-%M-%S")
44
- @logger.info("Creating archive: #{archive_name}")
45
- backup.create(name: options[:name], remove_source: options[:remove_source])
46
- @logger.info("Backup created successfully: #{archive_name}")
47
49
 
48
- if options[:remove_source]
49
- @logger.info("Removed source files: #{config.backup_paths.join(', ')}")
50
+ if config.multi_repo?
51
+ backup_multi_repo(config)
52
+ else
53
+ backup_single_repo(config)
50
54
  end
51
-
52
- puts "Backup created successfully"
53
- puts "Source files removed" if options[:remove_source]
54
55
  rescue Error => e
55
56
  @logger.error("Backup failed: #{e.message}")
56
57
  error_exit(e)
@@ -63,6 +64,14 @@ module Ruborg
63
64
  passphrase = fetch_passphrase_from_config(config)
64
65
 
65
66
  repo = Repository.new(config.repository, passphrase: passphrase)
67
+
68
+ # Auto-initialize repository if configured
69
+ if config.auto_init? && !repo.exists?
70
+ @logger.info("Auto-initializing repository at #{config.repository}")
71
+ repo.create
72
+ puts "Repository auto-initialized at #{config.repository}"
73
+ end
74
+
66
75
  repo.list
67
76
  @logger.info("Successfully listed archives")
68
77
  rescue Error => e
@@ -102,6 +111,14 @@ module Ruborg
102
111
  passphrase = fetch_passphrase_from_config(config)
103
112
 
104
113
  repo = Repository.new(config.repository, passphrase: passphrase)
114
+
115
+ # Auto-initialize repository if configured
116
+ if config.auto_init? && !repo.exists?
117
+ @logger.info("Auto-initializing repository at #{config.repository}")
118
+ repo.create
119
+ puts "Repository auto-initialized at #{config.repository}"
120
+ end
121
+
105
122
  repo.info
106
123
  @logger.info("Successfully retrieved repository information")
107
124
  rescue Error => e
@@ -129,5 +146,128 @@ module Ruborg
129
146
  puts "Error: #{error.message}"
130
147
  exit 1
131
148
  end
149
+
150
+ # Single repository backup (legacy)
151
+ def backup_single_repo(config)
152
+ @logger.info("Backing up paths: #{config.backup_paths.join(', ')}")
153
+ passphrase = fetch_passphrase_from_config(config)
154
+
155
+ repo = Repository.new(config.repository, passphrase: passphrase)
156
+
157
+ # Auto-initialize repository if configured
158
+ if config.auto_init? && !repo.exists?
159
+ @logger.info("Auto-initializing repository at #{config.repository}")
160
+ repo.create
161
+ puts "Repository auto-initialized at #{config.repository}"
162
+ end
163
+
164
+ backup = Backup.new(repo, config: config)
165
+
166
+ archive_name = options[:name] || Time.now.strftime("%Y-%m-%d_%H-%M-%S")
167
+ @logger.info("Creating archive: #{archive_name}")
168
+ backup.create(name: options[:name], remove_source: options[:remove_source])
169
+ @logger.info("Backup created successfully: #{archive_name}")
170
+
171
+ if options[:remove_source]
172
+ @logger.info("Removed source files: #{config.backup_paths.join(', ')}")
173
+ end
174
+
175
+ puts "Backup created successfully"
176
+ puts "Source files removed" if options[:remove_source]
177
+ end
178
+
179
+ # Multi-repository backup
180
+ def backup_multi_repo(config)
181
+ global_settings = config.global_settings
182
+ repos_to_backup = if options[:all]
183
+ config.repositories
184
+ elsif options[:repository]
185
+ repo_config = config.get_repository(options[:repository])
186
+ raise ConfigError, "Repository '#{options[:repository]}' not found" unless repo_config
187
+ [repo_config]
188
+ else
189
+ raise ConfigError, "Please specify --repository or --all for multi-repo config"
190
+ end
191
+
192
+ repos_to_backup.each do |repo_config|
193
+ backup_repository(repo_config, global_settings)
194
+ end
195
+ end
196
+
197
+ def backup_repository(repo_config, global_settings)
198
+ repo_name = repo_config["name"]
199
+ puts "\n--- Backing up repository: #{repo_name} ---"
200
+ @logger.info("Backing up repository: #{repo_name}")
201
+
202
+ # Merge global settings with repo-specific settings (repo-specific takes precedence)
203
+ merged_config = global_settings.merge(repo_config)
204
+
205
+ passphrase = fetch_passphrase_for_repo(merged_config)
206
+ repo = Repository.new(repo_config["path"], passphrase: passphrase)
207
+
208
+ # Auto-initialize if configured
209
+ auto_init = merged_config["auto_init"] || false
210
+ if auto_init && !repo.exists?
211
+ @logger.info("Auto-initializing repository at #{repo_config['path']}")
212
+ repo.create
213
+ puts "Repository auto-initialized at #{repo_config['path']}"
214
+ end
215
+
216
+ # Create backup config wrapper
217
+ backup_config = BackupConfig.new(repo_config, merged_config)
218
+ backup = Backup.new(repo, config: backup_config)
219
+
220
+ archive_name = options[:name] || "#{repo_name}-#{Time.now.strftime('%Y-%m-%d_%H-%M-%S')}"
221
+ @logger.info("Creating archive: #{archive_name}")
222
+
223
+ sources = repo_config["sources"] || []
224
+ @logger.info("Backing up #{sources.size} source(s)")
225
+
226
+ backup.create(name: archive_name, remove_source: options[:remove_source])
227
+ @logger.info("Backup created successfully: #{archive_name}")
228
+
229
+ puts "โœ“ Backup created: #{archive_name}"
230
+ puts " Sources removed" if options[:remove_source]
231
+ end
232
+
233
+ def fetch_passphrase_for_repo(repo_config)
234
+ passbolt_config = repo_config["passbolt"]
235
+ return nil if passbolt_config.nil? || passbolt_config.empty?
236
+
237
+ Passbolt.new(resource_id: passbolt_config["resource_id"]).get_password
238
+ end
239
+
240
+ # Wrapper class to adapt multi-repo config to existing Backup class
241
+ class BackupConfig
242
+ def initialize(repo_config, merged_settings)
243
+ @repo_config = repo_config
244
+ @merged_settings = merged_settings
245
+ end
246
+
247
+ def backup_paths
248
+ sources = @repo_config["sources"] || []
249
+ sources.flat_map do |source|
250
+ source["paths"] || []
251
+ end
252
+ end
253
+
254
+ def exclude_patterns
255
+ patterns = []
256
+ sources = @repo_config["sources"] || []
257
+ sources.each do |source|
258
+ patterns += (source["exclude"] || [])
259
+ end
260
+ patterns += (@merged_settings["exclude_patterns"] || [])
261
+ patterns.uniq
262
+ end
263
+
264
+ def compression
265
+ @merged_settings["compression"] || "lz4"
266
+ end
267
+
268
+ def encryption_mode
269
+ @merged_settings["encryption"] || "repokey"
270
+ end
271
+ end
132
272
  end
133
273
  end
data/lib/ruborg/config.rb CHANGED
@@ -11,6 +11,7 @@ module Ruborg
11
11
  def initialize(config_path)
12
12
  @config_path = config_path
13
13
  load_config
14
+ detect_format
14
15
  end
15
16
 
16
17
  def load_config
@@ -21,6 +22,7 @@ module Ruborg
21
22
  raise ConfigError, "Invalid YAML syntax: #{e.message}"
22
23
  end
23
24
 
25
+ # Legacy single-repo accessors (for backward compatibility)
24
26
  def repository
25
27
  @data["repository"]
26
28
  end
@@ -44,5 +46,43 @@ module Ruborg
44
46
  def passbolt_integration
45
47
  @data["passbolt"] || {}
46
48
  end
49
+
50
+ def auto_init?
51
+ @data["auto_init"] || false
52
+ end
53
+
54
+ def log_file
55
+ @data["log_file"]
56
+ end
57
+
58
+ # New multi-repo support
59
+ def multi_repo?
60
+ @multi_repo
61
+ end
62
+
63
+ def repositories
64
+ return [] unless multi_repo?
65
+ @data["repositories"] || []
66
+ end
67
+
68
+ def get_repository(name)
69
+ return nil unless multi_repo?
70
+ repositories.find { |r| r["name"] == name }
71
+ end
72
+
73
+ def repository_names
74
+ return [] unless multi_repo?
75
+ repositories.map { |r| r["name"] }
76
+ end
77
+
78
+ def global_settings
79
+ @data.slice("passbolt", "compression", "encryption", "auto_init")
80
+ end
81
+
82
+ private
83
+
84
+ def detect_format
85
+ @multi_repo = @data.key?("repositories")
86
+ end
47
87
  end
48
88
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruborg
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruborg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michail Pantelelis