ruborg 0.1.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 +4 -4
- data/.rspec +1 -1
- data/CHANGELOG.md +48 -5
- data/README.md +187 -17
- data/lib/ruborg/backup.rb +35 -6
- data/lib/ruborg/cli.rb +186 -8
- data/lib/ruborg/config.rb +40 -0
- data/lib/ruborg/logger.rb +52 -0
- data/lib/ruborg/passbolt.rb +8 -3
- data/lib/ruborg/repository.rb +4 -1
- data/lib/ruborg/version.rb +1 -1
- data/lib/ruborg.rb +1 -0
- metadata +2 -2
- data/ruborg.gemspec +0 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b3dfe277e22cf29ef1132f479b8e8a969e87c69b383529b7f05eb7238ac5ea07
|
|
4
|
+
data.tar.gz: fcf98cabcd5f4636fc513e69e3b446747c9ca2f054f6480f4da60416ec865f0d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 159296240a1cec2e7791edb3689bf032569d8c1f4fda0ac5c1522e92076fec8c719dba3117326c9fcb9e074ffa295ed71a75b500657625d1ce71fab391b441c2
|
|
7
|
+
data.tar.gz: f6385fa1ff56520719b1f8b3985931b4083b3139889b34ed4d1dcb5d449eec8ba98e96f030988cf03cdcb5744f47255ef3b7a605e7bb040d6b1f2c0446ab3b81
|
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ 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
|
+
|
|
32
|
+
## [0.2.0] - 2025-10-05
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- `--remove-source` option to delete source files after successful backup
|
|
36
|
+
- Comprehensive logging system with daily rotation (default: `~/.ruborg/logs/ruborg.log`)
|
|
37
|
+
- Custom log file support via `--log` option for all commands
|
|
38
|
+
- `--path` option for restore command to extract single files/directories from archives
|
|
39
|
+
- Comprehensive RSpec test suite with mocked Passbolt and actual Borg integration tests
|
|
40
|
+
- Borg installation instructions for macOS and Ubuntu in README
|
|
41
|
+
- Support for environment variables to prevent interactive prompts (`BORG_RELOCATED_REPO_ACCESS_IS_OK`, etc.)
|
|
42
|
+
- Automatic destination directory creation for restore operations
|
|
43
|
+
- Test helpers and fixtures for easier testing
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- Passbolt CLI command corrected from `passbolt get <id>` to `passbolt get resource <id>`
|
|
47
|
+
- Borg commands now properly redirect stdin to prevent interactive passphrase prompts
|
|
48
|
+
- Improved error handling and logging throughout the application
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Refactored Passbolt class to use testable `execute_command` method
|
|
52
|
+
- Enhanced Repository and Backup classes to properly handle environment variables
|
|
53
|
+
- Improved CLI integration with better Passbolt mock support in tests
|
|
54
|
+
|
|
55
|
+
## [0.1.0] - 2025-10-04
|
|
56
|
+
|
|
10
57
|
### Added
|
|
11
58
|
- Initial gem structure
|
|
12
59
|
- Borg repository initialization and management
|
|
@@ -14,8 +61,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
14
61
|
- YAML configuration file support
|
|
15
62
|
- Passbolt CLI integration for password management
|
|
16
63
|
- Command-line interface with Thor
|
|
17
|
-
|
|
18
|
-
## [0.1.0] - 2025-10-04
|
|
19
|
-
|
|
20
|
-
### Added
|
|
21
|
-
- Initial release
|
|
64
|
+
- Basic error handling
|
data/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Ruborg
|
|
2
2
|
|
|
3
|
-
> **⚠️ WARNING: This project is under heavy development and is not yet functional. Do not use in production.**
|
|
4
|
-
>
|
|
5
3
|
> This gem is being developed with the assistance of Claude AI.
|
|
6
4
|
|
|
7
5
|
A friendly Ruby frontend for [Borg Backup](https://www.borgbackup.org/). Ruborg simplifies backup management by providing a YAML-based configuration system and seamless integration with Passbolt for encryption password management.
|
|
@@ -14,13 +12,37 @@ A friendly Ruby frontend for [Borg Backup](https://www.borgbackup.org/). Ruborg
|
|
|
14
12
|
- 🔐 **Passbolt Integration** - Secure password management via Passbolt CLI
|
|
15
13
|
- 🎯 **Pattern Exclusions** - Flexible file exclusion patterns
|
|
16
14
|
- 🗜️ **Compression Options** - Support for multiple compression algorithms
|
|
15
|
+
- 🗂️ **Selective Restore** - Restore individual files or directories from archives
|
|
16
|
+
- 🧹 **Auto-cleanup** - Optionally remove source files after successful backup
|
|
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
|
|
20
|
+
- ✅ **Well-tested** - Comprehensive test suite with RSpec
|
|
17
21
|
|
|
18
22
|
## Prerequisites
|
|
19
23
|
|
|
20
|
-
- Ruby >= 2.
|
|
24
|
+
- Ruby >= 3.2.0
|
|
21
25
|
- [Borg Backup](https://www.borgbackup.org/) installed and available in PATH
|
|
22
26
|
- [Passbolt CLI](https://github.com/passbolt/go-passbolt-cli) (optional, for password management)
|
|
23
27
|
|
|
28
|
+
### Installing Borg Backup
|
|
29
|
+
|
|
30
|
+
**macOS:**
|
|
31
|
+
```bash
|
|
32
|
+
brew install borgbackup
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Ubuntu/Debian:**
|
|
36
|
+
```bash
|
|
37
|
+
sudo apt update
|
|
38
|
+
sudo apt install borgbackup
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Verify installation:**
|
|
42
|
+
```bash
|
|
43
|
+
borg --version
|
|
44
|
+
```
|
|
45
|
+
|
|
24
46
|
## Installation
|
|
25
47
|
|
|
26
48
|
Add this line to your application's Gemfile:
|
|
@@ -43,7 +65,9 @@ gem install ruborg
|
|
|
43
65
|
|
|
44
66
|
## Configuration
|
|
45
67
|
|
|
46
|
-
|
|
68
|
+
Ruborg supports two configuration formats: **single repository** (legacy) and **multi-repository** (recommended for complex setups).
|
|
69
|
+
|
|
70
|
+
### Single Repository Configuration
|
|
47
71
|
|
|
48
72
|
```yaml
|
|
49
73
|
# Repository path
|
|
@@ -53,15 +77,11 @@ repository: /path/to/borg/repository
|
|
|
53
77
|
backup_paths:
|
|
54
78
|
- /home/user/documents
|
|
55
79
|
- /home/user/projects
|
|
56
|
-
- /etc
|
|
57
80
|
|
|
58
81
|
# Exclude patterns
|
|
59
82
|
exclude_patterns:
|
|
60
83
|
- "*.tmp"
|
|
61
84
|
- "*.log"
|
|
62
|
-
- "*/.cache/*"
|
|
63
|
-
- "*/node_modules/*"
|
|
64
|
-
- "*/.git/*"
|
|
65
85
|
|
|
66
86
|
# Compression algorithm (lz4, zstd, zlib, lzma, none)
|
|
67
87
|
compression: lz4
|
|
@@ -72,9 +92,62 @@ encryption: repokey
|
|
|
72
92
|
# Passbolt integration (optional)
|
|
73
93
|
passbolt:
|
|
74
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
|
|
75
101
|
```
|
|
76
102
|
|
|
77
|
-
|
|
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
|
|
143
|
+
```
|
|
144
|
+
|
|
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
|
|
78
151
|
|
|
79
152
|
## Usage
|
|
80
153
|
|
|
@@ -90,6 +163,7 @@ ruborg init /path/to/repository --passbolt-id "resource-uuid"
|
|
|
90
163
|
|
|
91
164
|
### Create a Backup
|
|
92
165
|
|
|
166
|
+
**Single repository:**
|
|
93
167
|
```bash
|
|
94
168
|
# Using default configuration (ruborg.yml)
|
|
95
169
|
ruborg backup
|
|
@@ -99,6 +173,21 @@ ruborg backup --config /path/to/config.yml
|
|
|
99
173
|
|
|
100
174
|
# With custom archive name
|
|
101
175
|
ruborg backup --name "my-backup-2025-10-04"
|
|
176
|
+
|
|
177
|
+
# Remove source files after successful backup
|
|
178
|
+
ruborg backup --remove-source
|
|
179
|
+
```
|
|
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"
|
|
102
191
|
```
|
|
103
192
|
|
|
104
193
|
### List Archives
|
|
@@ -110,11 +199,14 @@ ruborg list
|
|
|
110
199
|
### Restore from Archive
|
|
111
200
|
|
|
112
201
|
```bash
|
|
113
|
-
# Restore to current directory
|
|
202
|
+
# Restore entire archive to current directory
|
|
114
203
|
ruborg restore archive-name
|
|
115
204
|
|
|
116
205
|
# Restore to specific directory
|
|
117
206
|
ruborg restore archive-name --destination /path/to/restore
|
|
207
|
+
|
|
208
|
+
# Restore a single file from archive
|
|
209
|
+
ruborg restore archive-name --path /path/to/file.txt --destination /new/location
|
|
118
210
|
```
|
|
119
211
|
|
|
120
212
|
### View Repository Information
|
|
@@ -123,13 +215,50 @@ ruborg restore archive-name --destination /path/to/restore
|
|
|
123
215
|
ruborg info
|
|
124
216
|
```
|
|
125
217
|
|
|
218
|
+
## Logging
|
|
219
|
+
|
|
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:**
|
|
227
|
+
|
|
228
|
+
```bash
|
|
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
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Logs include:**
|
|
237
|
+
- Operation start/completion timestamps
|
|
238
|
+
- Paths being backed up
|
|
239
|
+
- Archive names created
|
|
240
|
+
- Success and error messages
|
|
241
|
+
- Source file removal actions
|
|
242
|
+
|
|
126
243
|
## Passbolt Integration
|
|
127
244
|
|
|
128
245
|
Ruborg can retrieve encryption passphrases from Passbolt using the Passbolt CLI:
|
|
129
246
|
|
|
130
247
|
1. Install and configure [Passbolt CLI](https://github.com/passbolt/go-passbolt-cli)
|
|
131
|
-
2.
|
|
132
|
-
|
|
248
|
+
2. Configure Passbolt CLI with your server credentials:
|
|
249
|
+
```bash
|
|
250
|
+
passbolt configure --serverAddress https://server.address \
|
|
251
|
+
--userPrivateKeyFile /path/to/private.key \
|
|
252
|
+
--userPassword YOUR_PASSWORD
|
|
253
|
+
```
|
|
254
|
+
Or set environment variables:
|
|
255
|
+
```bash
|
|
256
|
+
export PASSBOLT_SERVER_ADDRESS=https://server.address
|
|
257
|
+
export PASSBOLT_USER_PRIVATE_KEY_FILE=/path/to/private.key
|
|
258
|
+
export PASSBOLT_USER_PASSWORD=YOUR_PASSWORD
|
|
259
|
+
```
|
|
260
|
+
3. Store your Borg repository passphrase in Passbolt
|
|
261
|
+
4. Add the resource ID to your `ruborg.yml`:
|
|
133
262
|
|
|
134
263
|
```yaml
|
|
135
264
|
passbolt:
|
|
@@ -138,15 +267,41 @@ passbolt:
|
|
|
138
267
|
|
|
139
268
|
Ruborg will automatically retrieve the passphrase when performing backup operations.
|
|
140
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
|
+
|
|
141
285
|
## Command Reference
|
|
142
286
|
|
|
143
287
|
| Command | Description | Options |
|
|
144
288
|
|---------|-------------|---------|
|
|
145
|
-
| `init REPOSITORY` | Initialize a new Borg repository | `--passphrase`, `--passbolt-id` |
|
|
146
|
-
| `backup` | Create a backup using config file | `--config`, `--name` |
|
|
147
|
-
| `list` | List all archives in repository | `--config` |
|
|
148
|
-
| `restore ARCHIVE` | Restore files from archive | `--config`, `--destination` |
|
|
149
|
-
| `info` | Show repository information | `--config` |
|
|
289
|
+
| `init REPOSITORY` | Initialize a new Borg repository | `--passphrase`, `--passbolt-id`, `--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` |
|
|
294
|
+
|
|
295
|
+
### Global Options
|
|
296
|
+
|
|
297
|
+
- `--config`: Path to configuration file (default: `ruborg.yml`)
|
|
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
|
|
150
305
|
|
|
151
306
|
## Development
|
|
152
307
|
|
|
@@ -169,9 +324,24 @@ bundle exec rake release
|
|
|
169
324
|
Run the test suite:
|
|
170
325
|
|
|
171
326
|
```bash
|
|
327
|
+
# Run all tests
|
|
172
328
|
bundle exec rspec
|
|
329
|
+
|
|
330
|
+
# Run only unit tests (no Borg required)
|
|
331
|
+
bundle exec rspec --tag ~borg
|
|
332
|
+
|
|
333
|
+
# Run only integration tests (requires Borg)
|
|
334
|
+
bundle exec rspec --tag borg
|
|
173
335
|
```
|
|
174
336
|
|
|
337
|
+
The test suite includes:
|
|
338
|
+
- Config loading and validation
|
|
339
|
+
- Repository management (with actual Borg integration)
|
|
340
|
+
- Backup and restore operations
|
|
341
|
+
- Passbolt integration (mocked)
|
|
342
|
+
- CLI commands
|
|
343
|
+
- Logging functionality
|
|
344
|
+
|
|
175
345
|
## Contributing
|
|
176
346
|
|
|
177
347
|
Bug reports and pull requests are welcome on GitHub at https://github.com/mpantel/ruborg.
|
data/lib/ruborg/backup.rb
CHANGED
|
@@ -8,22 +8,33 @@ module Ruborg
|
|
|
8
8
|
@config = config
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def create(name: nil)
|
|
11
|
+
def create(name: nil, remove_source: false)
|
|
12
12
|
raise BorgError, "Repository does not exist" unless @repository.exists?
|
|
13
13
|
|
|
14
14
|
archive_name = name || Time.now.strftime("%Y-%m-%d_%H-%M-%S")
|
|
15
15
|
cmd = build_create_command(archive_name)
|
|
16
16
|
|
|
17
17
|
execute_borg_command(cmd)
|
|
18
|
+
|
|
19
|
+
remove_source_files if remove_source
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
def extract(archive_name, destination: ".")
|
|
22
|
+
def extract(archive_name, destination: ".", path: nil)
|
|
21
23
|
raise BorgError, "Repository does not exist" unless @repository.exists?
|
|
22
24
|
|
|
23
25
|
cmd = ["borg", "extract", "#{@repository.path}::#{archive_name}"]
|
|
24
|
-
cmd
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
cmd << path if path
|
|
27
|
+
|
|
28
|
+
# Change to destination directory if specified
|
|
29
|
+
if destination != "."
|
|
30
|
+
require "fileutils"
|
|
31
|
+
FileUtils.mkdir_p(destination) unless File.directory?(destination)
|
|
32
|
+
Dir.chdir(destination) do
|
|
33
|
+
execute_borg_command(cmd)
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
execute_borg_command(cmd)
|
|
37
|
+
end
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
def list_archives
|
|
@@ -52,10 +63,28 @@ module Ruborg
|
|
|
52
63
|
end
|
|
53
64
|
|
|
54
65
|
def execute_borg_command(cmd)
|
|
55
|
-
|
|
66
|
+
env = {}
|
|
67
|
+
passphrase = @repository.instance_variable_get(:@passphrase)
|
|
68
|
+
env["BORG_PASSPHRASE"] = passphrase if passphrase
|
|
69
|
+
env["BORG_RELOCATED_REPO_ACCESS_IS_OK"] = "yes"
|
|
70
|
+
env["BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK"] = "yes"
|
|
71
|
+
|
|
72
|
+
result = system(env, *cmd, in: "/dev/null")
|
|
56
73
|
raise BorgError, "Borg command failed: #{cmd.join(' ')}" unless result
|
|
57
74
|
|
|
58
75
|
result
|
|
59
76
|
end
|
|
77
|
+
|
|
78
|
+
def remove_source_files
|
|
79
|
+
require "fileutils"
|
|
80
|
+
|
|
81
|
+
@config.backup_paths.each do |path|
|
|
82
|
+
if File.directory?(path)
|
|
83
|
+
FileUtils.rm_rf(path)
|
|
84
|
+
elsif File.file?(path)
|
|
85
|
+
FileUtils.rm(path)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
60
89
|
end
|
|
61
90
|
end
|
data/lib/ruborg/cli.rb
CHANGED
|
@@ -6,68 +6,123 @@ module Ruborg
|
|
|
6
6
|
# Command-line interface for ruborg
|
|
7
7
|
class CLI < Thor
|
|
8
8
|
class_option :config, type: :string, default: "ruborg.yml", desc: "Path to configuration file"
|
|
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)"
|
|
11
|
+
|
|
12
|
+
def initialize(*args)
|
|
13
|
+
super
|
|
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)
|
|
25
|
+
end
|
|
9
26
|
|
|
10
27
|
desc "init REPOSITORY", "Initialize a new Borg repository"
|
|
11
28
|
option :passphrase, type: :string, desc: "Repository passphrase"
|
|
12
29
|
option :passbolt_id, type: :string, desc: "Passbolt resource ID for passphrase"
|
|
13
30
|
def init(repository_path)
|
|
31
|
+
@logger.info("Initializing repository at #{repository_path}")
|
|
14
32
|
passphrase = get_passphrase(options[:passphrase], options[:passbolt_id])
|
|
15
33
|
repo = Repository.new(repository_path, passphrase: passphrase)
|
|
16
34
|
repo.create
|
|
35
|
+
@logger.info("Repository successfully initialized at #{repository_path}")
|
|
17
36
|
puts "Repository initialized at #{repository_path}"
|
|
18
37
|
rescue Error => e
|
|
38
|
+
@logger.error("Failed to initialize repository: #{e.message}")
|
|
19
39
|
error_exit(e)
|
|
20
40
|
end
|
|
21
41
|
|
|
22
42
|
desc "backup", "Create a backup using configuration file"
|
|
23
43
|
option :name, type: :string, desc: "Archive name"
|
|
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)"
|
|
24
46
|
def backup
|
|
47
|
+
@logger.info("Starting backup operation with config: #{options[:config]}")
|
|
25
48
|
config = Config.new(options[:config])
|
|
26
|
-
passphrase = fetch_passphrase_from_config(config)
|
|
27
|
-
|
|
28
|
-
repo = Repository.new(config.repository, passphrase: passphrase)
|
|
29
|
-
backup = Backup.new(repo, config: config)
|
|
30
49
|
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
if config.multi_repo?
|
|
51
|
+
backup_multi_repo(config)
|
|
52
|
+
else
|
|
53
|
+
backup_single_repo(config)
|
|
54
|
+
end
|
|
33
55
|
rescue Error => e
|
|
56
|
+
@logger.error("Backup failed: #{e.message}")
|
|
34
57
|
error_exit(e)
|
|
35
58
|
end
|
|
36
59
|
|
|
37
60
|
desc "list", "List all archives in the repository"
|
|
38
61
|
def list
|
|
62
|
+
@logger.info("Listing archives in repository")
|
|
39
63
|
config = Config.new(options[:config])
|
|
40
64
|
passphrase = fetch_passphrase_from_config(config)
|
|
41
65
|
|
|
42
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
|
+
|
|
43
75
|
repo.list
|
|
76
|
+
@logger.info("Successfully listed archives")
|
|
44
77
|
rescue Error => e
|
|
78
|
+
@logger.error("Failed to list archives: #{e.message}")
|
|
45
79
|
error_exit(e)
|
|
46
80
|
end
|
|
47
81
|
|
|
48
82
|
desc "restore ARCHIVE", "Restore files from an archive"
|
|
49
83
|
option :destination, type: :string, default: ".", desc: "Destination directory"
|
|
84
|
+
option :path, type: :string, desc: "Specific file or directory path to restore from archive"
|
|
50
85
|
def restore(archive_name)
|
|
86
|
+
restore_target = options[:path] ? "#{options[:path]} from #{archive_name}" : archive_name
|
|
87
|
+
@logger.info("Restoring #{restore_target} to #{options[:destination]}")
|
|
51
88
|
config = Config.new(options[:config])
|
|
52
89
|
passphrase = fetch_passphrase_from_config(config)
|
|
53
90
|
|
|
54
91
|
repo = Repository.new(config.repository, passphrase: passphrase)
|
|
55
92
|
backup = Backup.new(repo, config: config)
|
|
56
93
|
|
|
57
|
-
backup.extract(archive_name, destination: options[:destination])
|
|
58
|
-
|
|
94
|
+
backup.extract(archive_name, destination: options[:destination], path: options[:path])
|
|
95
|
+
@logger.info("Successfully restored #{restore_target} to #{options[:destination]}")
|
|
96
|
+
|
|
97
|
+
if options[:path]
|
|
98
|
+
puts "Restored #{options[:path]} from #{archive_name} to #{options[:destination]}"
|
|
99
|
+
else
|
|
100
|
+
puts "Archive restored to #{options[:destination]}"
|
|
101
|
+
end
|
|
59
102
|
rescue Error => e
|
|
103
|
+
@logger.error("Failed to restore archive: #{e.message}")
|
|
60
104
|
error_exit(e)
|
|
61
105
|
end
|
|
62
106
|
|
|
63
107
|
desc "info", "Show repository information"
|
|
64
108
|
def info
|
|
109
|
+
@logger.info("Retrieving repository information")
|
|
65
110
|
config = Config.new(options[:config])
|
|
66
111
|
passphrase = fetch_passphrase_from_config(config)
|
|
67
112
|
|
|
68
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
|
+
|
|
69
122
|
repo.info
|
|
123
|
+
@logger.info("Successfully retrieved repository information")
|
|
70
124
|
rescue Error => e
|
|
125
|
+
@logger.error("Failed to get repository info: #{e.message}")
|
|
71
126
|
error_exit(e)
|
|
72
127
|
end
|
|
73
128
|
|
|
@@ -91,5 +146,128 @@ module Ruborg
|
|
|
91
146
|
puts "Error: #{error.message}"
|
|
92
147
|
exit 1
|
|
93
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
|
|
94
272
|
end
|
|
95
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
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Ruborg
|
|
7
|
+
# Logging functionality for ruborg
|
|
8
|
+
class RuborgLogger
|
|
9
|
+
attr_reader :logger
|
|
10
|
+
|
|
11
|
+
def initialize(log_file: nil)
|
|
12
|
+
@log_file = log_file || default_log_file
|
|
13
|
+
ensure_log_directory
|
|
14
|
+
@logger = Logger.new(@log_file, "daily")
|
|
15
|
+
@logger.level = Logger::INFO
|
|
16
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
|
17
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def info(message)
|
|
22
|
+
@logger.info(message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def error(message)
|
|
26
|
+
@logger.error(message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def warn(message)
|
|
30
|
+
@logger.warn(message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def debug(message)
|
|
34
|
+
@logger.debug(message)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def default_log_file
|
|
40
|
+
File.join(log_directory, "ruborg.log")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def log_directory
|
|
44
|
+
dir = File.expand_path("~/.ruborg/logs")
|
|
45
|
+
dir
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ensure_log_directory
|
|
49
|
+
FileUtils.mkdir_p(File.dirname(@log_file)) unless File.directory?(File.dirname(@log_file))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/ruborg/passbolt.rb
CHANGED
|
@@ -13,10 +13,10 @@ module Ruborg
|
|
|
13
13
|
def get_password
|
|
14
14
|
raise PassboltError, "Resource ID not configured" unless @resource_id
|
|
15
15
|
|
|
16
|
-
cmd = ["passbolt", "get", @resource_id, "--json"]
|
|
17
|
-
output =
|
|
16
|
+
cmd = ["passbolt", "get", "resource", @resource_id, "--json"]
|
|
17
|
+
output, status = execute_command(cmd)
|
|
18
18
|
|
|
19
|
-
raise PassboltError, "Failed to retrieve password from Passbolt" unless
|
|
19
|
+
raise PassboltError, "Failed to retrieve password from Passbolt" unless status
|
|
20
20
|
|
|
21
21
|
parse_password(output)
|
|
22
22
|
end
|
|
@@ -29,6 +29,11 @@ module Ruborg
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def execute_command(cmd)
|
|
33
|
+
output = `#{cmd.join(' ')}`
|
|
34
|
+
[output, $?.success?]
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
def parse_password(json_output)
|
|
33
38
|
data = JSON.parse(json_output)
|
|
34
39
|
data["password"] || data["secret"]
|
data/lib/ruborg/repository.rb
CHANGED
|
@@ -40,8 +40,11 @@ module Ruborg
|
|
|
40
40
|
def execute_borg_command(cmd)
|
|
41
41
|
env = {}
|
|
42
42
|
env["BORG_PASSPHRASE"] = @passphrase if @passphrase
|
|
43
|
+
env["BORG_RELOCATED_REPO_ACCESS_IS_OK"] = "yes"
|
|
44
|
+
env["BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK"] = "yes"
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
# Redirect stdin from /dev/null to prevent interactive prompts
|
|
47
|
+
result = system(env, *cmd, in: "/dev/null")
|
|
45
48
|
raise BorgError, "Borg command failed: #{cmd.join(' ')}" unless result
|
|
46
49
|
|
|
47
50
|
result
|
data/lib/ruborg/version.rb
CHANGED
data/lib/ruborg.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michail Pantelelis
|
|
@@ -115,10 +115,10 @@ files:
|
|
|
115
115
|
- lib/ruborg/backup.rb
|
|
116
116
|
- lib/ruborg/cli.rb
|
|
117
117
|
- lib/ruborg/config.rb
|
|
118
|
+
- lib/ruborg/logger.rb
|
|
118
119
|
- lib/ruborg/passbolt.rb
|
|
119
120
|
- lib/ruborg/repository.rb
|
|
120
121
|
- lib/ruborg/version.rb
|
|
121
|
-
- ruborg.gemspec
|
|
122
122
|
- ruborg.yml.example
|
|
123
123
|
homepage: https://github.com/mpantel/ruborg
|
|
124
124
|
licenses:
|
data/ruborg.gemspec
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "lib/ruborg/version"
|
|
4
|
-
|
|
5
|
-
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name = "ruborg"
|
|
7
|
-
spec.version = Ruborg::VERSION
|
|
8
|
-
spec.authors = ["Michail Pantelelis"]
|
|
9
|
-
spec.email = ["mpantel@aegean.gr"]
|
|
10
|
-
|
|
11
|
-
spec.summary = "A friendly Ruby frontend for Borg backup"
|
|
12
|
-
spec.description = "Ruborg is a Ruby gem that provides a user-friendly interface to Borg backup. It reads YAML configuration files and orchestrates backup operations, supporting repository creation, backup management, and integration with Passbolt for encryption password management."
|
|
13
|
-
spec.homepage = "https://github.com/mpantel/ruborg"
|
|
14
|
-
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = ">= 3.2.0"
|
|
16
|
-
|
|
17
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
-
spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
|
|
19
|
-
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
20
|
-
|
|
21
|
-
# Specify which files should be added to the gem when it is released.
|
|
22
|
-
spec.files = Dir.chdir(__dir__) do
|
|
23
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
-
(File.expand_path(f) == __FILE__) ||
|
|
25
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
spec.bindir = "exe"
|
|
29
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
|
-
spec.require_paths = ["lib"]
|
|
31
|
-
|
|
32
|
-
# Dependencies
|
|
33
|
-
spec.add_dependency "thor", "~> 1.3"
|
|
34
|
-
spec.add_dependency "psych", "~> 5.0"
|
|
35
|
-
|
|
36
|
-
# Development dependencies
|
|
37
|
-
spec.add_development_dependency "bundler", "~> 2.0"
|
|
38
|
-
spec.add_development_dependency "rake", "~> 13.0"
|
|
39
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
|
40
|
-
spec.add_development_dependency "rubocop", "~> 1.0"
|
|
41
|
-
end
|