kamal-backup 0.1.0.pre.1 → 0.1.0.pre.8

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: b7d6d47cf91c1aeb85a212dace05952ea498ac6cd32b375bc528432c6f9fd94e
4
- data.tar.gz: 8e5e8902264e06c87190be87d46981a2b584a5de7ed1cc2a3a161c76cd50864a
3
+ metadata.gz: 8ed4e21bfee7ac0e789c3a0706626062055c92701128b5999a3828f5f3190057
4
+ data.tar.gz: 5983618fb0fb16db51cfa82f4ff28398c00568d7a5082fd494a7063c944b111d
5
5
  SHA512:
6
- metadata.gz: bc1ea52b549651e2a5dca8df1eb7b39cd79e17c4ee41b90a91901638a3137ac42f0a8e1b143dc131754c784601af806c48527474e8cf95eb45ff4389bdb35c49
7
- data.tar.gz: dd9e5881f3475359e8a0707c0741dc371bf2894f56ef61e244959d6e42228093b6192ec529b65d9b6e95482192e5c21c3e53600268dc3304f061cf1a621e8d8a
6
+ metadata.gz: 227a34dc536cd9d5ca13cb2853fcac13c03c5defbe87e406374c1ebc7b94e7cc094693903aab1e7b6addb2b2a11913bae419767cb0e242f56d86818171bacb79
7
+ data.tar.gz: 16788fa7108df55c2afa70f900511ce6574c931be8459774649ebcb533c415873e4424d8172e0df987804f6509f4aea7fb398676cd3741ff0c59fddf6d7e9a74
data/README.md CHANGED
@@ -1,40 +1,60 @@
1
1
  # kamal-backup
2
2
 
3
- `kamal-backup` is a small Docker image for Kamal accessories. It creates encrypted, restic-backed backups for self-hosted apps by backing up database dumps and mounted application files together.
3
+ `kamal-backup` gives Rails apps a clean backup and restore workflow for Kamal.
4
4
 
5
- The Docker image is the normal production interface. The Ruby gem packages the same `kamal-backup` executable so the image can install it cleanly, and so operators can optionally run the CLI from a laptop for restore drills when they have restic, database clients, and the right environment configured.
5
+ It backs up:
6
6
 
7
- It is aimed at common Kamal backup needs:
7
+ - PostgreSQL, MySQL/MariaDB, or SQLite
8
+ - file-backed Active Storage and other mounted app files
8
9
 
9
- - Kamal Postgres backup
10
- - Kamal MySQL and MariaDB backup
11
- - Kamal Active Storage backup
12
- - Kamal restic backup
13
- - Restore drills and evidence for security reviews such as CASA
10
+ It restores in two clear modes:
14
11
 
15
- ## What It Backs Up
12
+ - `restore local`: pull a production backup onto your machine
13
+ - `restore production`: restore back into live production
16
14
 
17
- `kamal-backup` handles two data surfaces:
15
+ And it drills in two clear modes:
18
16
 
19
- 1. A logical database dump from PostgreSQL, MySQL/MariaDB, or SQLite.
20
- 2. Mounted application files such as Rails Active Storage.
17
+ - `drill local`: prove the backup works on your machine
18
+ - `drill production`: restore into scratch targets on production infrastructure, run checks, and record evidence
21
19
 
22
- Database backups are logical dumps, not raw database data directories. File backups use one `restic backup` snapshot per run containing all configured mounted paths, so `restore-files latest` restores all file paths from that run.
20
+ Under the hood it uses [restic](https://restic.net/) for encrypted backup storage and repository management.
23
21
 
24
- Database dump snapshots are tagged with `kamal-backup`, `app:<name>`, `type:database`, `adapter:<adapter>`, and `run:<timestamp>`. The dump object uses a flat restic stdin filename such as `databases-chatwithwork-postgres-20260422T120000Z.pgdump` because restic stdin backups do not support nested virtual paths consistently.
22
+ ## Why Rails teams use it
25
23
 
26
- ## Quick Start With Kamal
24
+ `kamal-backup` is aimed at the common self-hosted Rails setup where:
27
25
 
28
- Add a backup accessory to your Kamal deploy config:
26
+ - the app is deployed with Kamal
27
+ - the database is PostgreSQL, MySQL/MariaDB, or SQLite
28
+ - file data lives on a mounted volume
29
+ - you need real restore drills and evidence for CASA or another security review
29
30
 
30
- ```yaml
31
- aliases:
32
- backup: accessory exec backup "kamal-backup backup"
33
- backup-list: accessory exec backup "kamal-backup list"
34
- backup-check: accessory exec backup "kamal-backup check"
35
- backup-evidence: accessory exec backup "kamal-backup evidence"
36
- backup-logs: accessory logs backup -f
31
+ If your app already stores Active Storage blobs directly in S3, there may be no local file path for `BACKUP_PATHS` to capture. In that case, `kamal-backup` still covers the database side, but object-storage backups are a separate concern.
32
+
33
+ ## Quick Start
34
+
35
+ Add the gem in your Rails app:
36
+
37
+ ```ruby
38
+ group :development do
39
+ gem "kamal-backup"
40
+ end
41
+ ```
42
+
43
+ Install it and generate the local config stubs:
44
+
45
+ ```sh
46
+ bundle install
47
+ bundle exec kamal-backup init
48
+ ```
49
+
50
+ That creates:
37
51
 
52
+ - `config/kamal-backup.yml`
53
+ - `config/kamal-backup.local.yml`
54
+
55
+ Then add the backup accessory to `config/deploy.yml`:
56
+
57
+ ```yaml
38
58
  accessories:
39
59
  backup:
40
60
  image: ghcr.io/crmne/kamal-backup:latest
@@ -64,282 +84,206 @@ bin/kamal accessory boot backup
64
84
  bin/kamal accessory logs backup
65
85
  ```
66
86
 
67
- Run manual commands:
87
+ Run the first backup from your app checkout with the local gem and Kamal-style destination selection:
68
88
 
69
89
  ```sh
70
- bin/kamal backup
71
- bin/kamal backup-list
72
- bin/kamal backup-evidence
90
+ bundle exec kamal-backup -d production backup
91
+ bundle exec kamal-backup -d production list
92
+ bundle exec kamal-backup -d production evidence
73
93
  ```
74
94
 
75
- ## Commands
76
-
77
- Commands usually run inside the production backup accessory with `bin/kamal accessory exec backup "kamal-backup <command>"`, or through Kamal aliases such as `bin/kamal backup`. A local gem install is useful when you intentionally want the operator laptop to run restic and database client commands directly.
95
+ If you keep multiple deploy configs, pass `-c` the same way Kamal does:
78
96
 
79
97
  ```sh
80
- kamal-backup backup
81
- kamal-backup restore-db [snapshot-or-latest]
82
- kamal-backup restore-files [snapshot-or-latest] [target-dir]
83
- kamal-backup list
84
- kamal-backup check
85
- kamal-backup evidence
86
- kamal-backup schedule
87
- kamal-backup version
98
+ bundle exec kamal-backup -c config/deploy.staging.yml -d staging backup
88
99
  ```
89
100
 
90
- | Command | What it does |
91
- |---|---|
92
- | `backup` | Runs one immediate backup, creating one database snapshot and one file snapshot for all `BACKUP_PATHS`. |
93
- | `restore-db [snapshot-or-latest]` | Restores a database dump. Defaults to `latest` and requires explicit restore environment. |
94
- | `restore-files [snapshot-or-latest] [target-dir]` | Restores file paths from a file snapshot. Defaults to `latest /restore/files`. |
95
- | `list` | Lists restic snapshots for the configured app tags. |
96
- | `check` | Runs `restic check` and records the latest result for evidence output. |
97
- | `evidence` | Prints redacted JSON with backup configuration, latest snapshots, check status, and tool versions. |
98
- | `schedule` | Runs the foreground scheduler loop used by the container default command. |
99
- | `version` | Prints the gem version. `--version` and `-v` do the same. |
100
-
101
- The default container command is:
101
+ Examples live in:
102
102
 
103
- ```sh
104
- kamal-backup schedule
105
- ```
103
+ - [examples/kamal-accessory.yml](examples/kamal-accessory.yml)
104
+ - [examples/kamal-backup.yml.example](examples/kamal-backup.yml.example)
105
+ - [examples/kamal-backup.local.yml.example](examples/kamal-backup.local.yml.example)
106
106
 
107
- ## Configuration
107
+ ## What Restic Does Here
108
108
 
109
- Required common environment:
109
+ `kamal-backup` uses restic as the backup engine and repository format.
110
110
 
111
- ```sh
112
- APP_NAME=chatwithwork
113
- DATABASE_ADAPTER=postgres
114
- RESTIC_REPOSITORY=s3:https://s3.example.com/chatwithwork-backups
115
- RESTIC_PASSWORD=change-me
116
- BACKUP_PATHS=/data/storage
117
- ```
111
+ In the normal Kamal setup, you do not install restic on the Rails app host. The backup accessory image already includes it. You only point the accessory at a restic repository, usually:
118
112
 
119
- `BACKUP_PATHS` accepts colon-separated or newline-separated paths. Every path must exist. Suspicious broad paths such as `/`, `/var`, `/etc`, and `/root` are refused unless `KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS=true`.
113
+ - S3-compatible object storage
114
+ - a restic REST server
115
+ - a filesystem path for local development
120
116
 
121
- PostgreSQL:
117
+ If you choose a `rest:` repository, `kamal-backup` does not install or operate that server for you. It is a separate service.
122
118
 
123
- ```sh
124
- DATABASE_ADAPTER=postgres
125
- DATABASE_URL=postgres://app@app-db:5432/app_production
126
- PGPASSWORD=change-me
127
- ```
119
+ ## Commands
128
120
 
129
- MySQL/MariaDB:
121
+ The operator-facing command surface is:
130
122
 
131
123
  ```sh
132
- DATABASE_ADAPTER=mysql
133
- DATABASE_URL=mysql2://app@app-mysql:3306/app_production
134
- MYSQL_PWD=change-me
124
+ kamal-backup init
125
+ kamal-backup backup
126
+ kamal-backup restore local [snapshot-or-latest]
127
+ kamal-backup restore production [snapshot-or-latest]
128
+ kamal-backup drill local [snapshot-or-latest]
129
+ kamal-backup drill production [snapshot-or-latest]
130
+ kamal-backup list
131
+ kamal-backup check
132
+ kamal-backup evidence
133
+ kamal-backup schedule
134
+ kamal-backup version
135
135
  ```
136
136
 
137
- SQLite:
137
+ Production-side commands shell out through Kamal when you pass `-d` or `-c`. Local commands run on your machine.
138
138
 
139
- ```sh
140
- DATABASE_ADAPTER=sqlite
141
- SQLITE_DATABASE_PATH=/data/db/production.sqlite3
142
- ```
143
-
144
- Retention defaults:
139
+ Common examples:
145
140
 
146
141
  ```sh
147
- RESTIC_KEEP_LAST=7
148
- RESTIC_KEEP_DAILY=7
149
- RESTIC_KEEP_WEEKLY=4
150
- RESTIC_KEEP_MONTHLY=6
151
- RESTIC_KEEP_YEARLY=2
152
- RESTIC_FORGET_AFTER_BACKUP=true
142
+ bundle exec kamal-backup -d production backup
143
+ bundle exec kamal-backup -d production check
144
+ bundle exec kamal-backup -d production evidence
145
+ bundle exec kamal-backup -d production restore production latest
146
+ bundle exec kamal-backup -d production drill production latest --database app_restore_20260423 --files /restore/files
147
+ bundle exec kamal-backup -d production version
148
+ bundle exec kamal-backup restore local latest
149
+ bundle exec kamal-backup drill local latest --check "bin/rails runner 'puts User.count'"
153
150
  ```
154
151
 
155
- Set `RESTIC_FORGET_AFTER_BACKUP=false` for append-only repositories, such as a restic REST server started with `--append-only`. In that mode, run retention and prune from the backup server or another trusted maintenance process with delete permissions.
152
+ Use `kamal-backup help`, `kamal-backup help restore`, or `kamal-backup help drill` for task-specific usage.
156
153
 
157
- Scheduler and checks:
154
+ ## How a Backup Run Works
158
155
 
159
- ```sh
160
- BACKUP_SCHEDULE_SECONDS=86400
161
- BACKUP_START_DELAY_SECONDS=0
162
- RESTIC_CHECK_AFTER_BACKUP=false
163
- RESTIC_CHECK_READ_DATA_SUBSET=5%
164
- ```
156
+ When `kamal-backup backup` runs, it does five things:
165
157
 
166
- For S3-compatible restic repositories, provide the standard restic/AWS variables as Kamal secrets:
158
+ 1. Validates the app name, restic repository, database settings, and `BACKUP_PATHS`.
159
+ 2. Creates a database backup with the database-native export tool.
160
+ 3. Streams that database backup into restic with tags such as `type:database`, `adapter:<adapter>`, and `run:<timestamp>`.
161
+ 4. Runs one `restic backup` for all configured `BACKUP_PATHS`, tagged as `type:files` with the same `run:<timestamp>`.
162
+ 5. Optionally runs `restic forget --prune` and `restic check`.
167
163
 
168
- ```sh
169
- AWS_ACCESS_KEY_ID=...
170
- AWS_SECRET_ACCESS_KEY=...
171
- AWS_DEFAULT_REGION=...
172
- ```
164
+ That shared `run:<timestamp>` tag lets you match the database backup and file backup from the same run.
173
165
 
174
- ## Restore Drills
166
+ ## Restore
175
167
 
176
- Restores are intentionally hard to run by accident. Every restore command requires:
168
+ `restore` means "put data back."
177
169
 
178
- ```sh
179
- KAMAL_BACKUP_ALLOW_RESTORE=true
180
- ```
170
+ `restore local` runs on your machine. With `-d` or `-c`, it asks Kamal for the backup accessory config and uses that as the source of truth for:
181
171
 
182
- Database restores use restore-specific environment by default. They do not restore to `DATABASE_URL`.
172
+ - `APP_NAME`
173
+ - `DATABASE_ADAPTER`
174
+ - `RESTIC_REPOSITORY`
175
+ - `LOCAL_RESTORE_SOURCE_PATHS` from the accessory `BACKUP_PATHS`
183
176
 
184
- PostgreSQL restore:
177
+ You still provide the local targets yourself in `config/kamal-backup.local.yml` or env:
185
178
 
186
- ```sh
187
- bin/kamal accessory exec backup \
188
- --env KAMAL_BACKUP_ALLOW_RESTORE=true \
189
- --env RESTORE_DATABASE_URL=postgres://app@app-db:5432/app_restore \
190
- "kamal-backup restore-db latest"
191
- ```
179
+ - `DATABASE_URL` or `SQLITE_DATABASE_PATH`
180
+ - `BACKUP_PATHS`
181
+ - local secrets such as `RESTIC_PASSWORD` and DB passwords
192
182
 
193
- MySQL/MariaDB restore:
183
+ Example:
194
184
 
195
185
  ```sh
196
- bin/kamal accessory exec backup \
197
- --env KAMAL_BACKUP_ALLOW_RESTORE=true \
198
- --env RESTORE_DATABASE_URL=mysql2://app@app-mysql:3306/app_restore \
199
- "kamal-backup restore-db latest"
186
+ bundle exec kamal-backup -d production restore local latest
200
187
  ```
201
188
 
202
- SQLite restore:
189
+ `restore production` is the emergency path back into the live production database and live production file paths:
203
190
 
204
191
  ```sh
205
- bin/kamal accessory exec backup \
206
- --env KAMAL_BACKUP_ALLOW_RESTORE=true \
207
- --env RESTORE_SQLITE_DATABASE_PATH=/restore/db/restore.sqlite3 \
208
- "kamal-backup restore-db latest"
192
+ bundle exec kamal-backup -d production restore production latest
209
193
  ```
210
194
 
211
- File restore:
195
+ It prompts locally, then shells out through Kamal to the backup accessory.
212
196
 
213
- ```sh
214
- bin/kamal accessory exec backup \
215
- --env KAMAL_BACKUP_ALLOW_RESTORE=true \
216
- "kamal-backup restore-files latest /restore/files"
217
- ```
197
+ ## Restore Drills
218
198
 
219
- Restore targets that look production-like are refused unless:
199
+ `drill` means "restore, check, and record the result."
200
+
201
+ `drill local` is often the fastest proof for a small app:
220
202
 
221
203
  ```sh
222
- KAMAL_BACKUP_ALLOW_PRODUCTION_RESTORE=true
204
+ bundle exec kamal-backup -d production drill local latest --check "bin/rails runner 'puts User.count'"
223
205
  ```
224
206
 
225
- File restores to configured backup paths are refused unless:
207
+ `drill production` restores into scratch targets on production infrastructure. It does not touch the live production database:
226
208
 
227
209
  ```sh
228
- KAMAL_BACKUP_ALLOW_IN_PLACE_FILE_RESTORE=true
210
+ bundle exec kamal-backup -d production drill production latest \
211
+ --database app_restore_20260423 \
212
+ --files /restore/files \
213
+ --check "test -d /restore/files/data/storage"
229
214
  ```
230
215
 
231
- ## Evidence
232
-
233
- `kamal-backup evidence` prints a redacted JSON summary suitable for operational evidence:
234
-
235
- - app name
236
- - current time
237
- - database adapter
238
- - redacted restic repository
239
- - configured file backup paths
240
- - whether client-side forget/prune is enabled
241
- - retention policy
242
- - latest database and file snapshots
243
- - last tracked `restic check` result
244
- - image version
245
- - installed tool versions
216
+ Every drill writes `last_restore_drill.json` under `KAMAL_BACKUP_STATE_DIR`, and `kamal-backup evidence` includes that latest result.
246
217
 
247
- Secrets, passwords, access keys, and database URL credentials are redacted.
218
+ ## Evidence for CASA and Similar Reviews
248
219
 
249
- Run:
250
-
251
- ```sh
252
- bin/kamal accessory exec backup "kamal-backup evidence"
253
- ```
220
+ `evidence` is the JSON summary you can attach to an ops record or security review.
254
221
 
255
- ## Local Development
222
+ It includes:
256
223
 
257
- Run tests:
224
+ - latest database and file snapshots
225
+ - latest `restic check` result
226
+ - latest restore drill result
227
+ - retention settings
228
+ - tool versions
258
229
 
259
- ```sh
260
- bin/test
261
- ```
230
+ For many reviews, the useful sequence is:
262
231
 
263
- Run docs locally:
232
+ 1. scheduled backups
233
+ 2. repository checks
234
+ 3. a real restore drill
235
+ 4. `kamal-backup evidence`
264
236
 
265
- ```sh
266
- cd docs
267
- bundle install
268
- bundle exec jekyll serve --livereload
269
- ```
237
+ That reads much better to a reviewer than "the backup job is green."
270
238
 
271
- Published docs are configured for `https://kamal-backup.dev`.
239
+ ## Configuration Highlights
272
240
 
273
- Build the image:
241
+ Core accessory environment:
274
242
 
275
243
  ```sh
276
- docker build -t kamal-backup .
244
+ APP_NAME=chatwithwork
245
+ DATABASE_ADAPTER=postgres
246
+ RESTIC_REPOSITORY=s3:https://s3.example.com/chatwithwork-backups
247
+ RESTIC_PASSWORD=change-me
248
+ BACKUP_PATHS=/data/storage
277
249
  ```
278
250
 
279
- CI publishes container images to `ghcr.io/crmne/kamal-backup`. Pull requests build the image without pushing; branch, tag, SHA, default-branch `latest`, and default-branch version tags are pushed on non-PR builds. The version tag comes from `lib/kamal_backup/version.rb`, matching the gem version.
280
-
281
- The CLI is packaged as the `kamal-backup` gem. The Docker image builds and installs that gem, which is why `kamal-backup` is on `PATH` inside the container. On default-branch CI, a new gem version is published to RubyGems and GitHub Packages when it does not already exist. The RubyGems publish step expects the repository secret `RUBYGEMS_AUTH_TOKEN`.
282
-
283
- For local Ruby use:
251
+ PostgreSQL:
284
252
 
285
253
  ```sh
286
- gem build kamal-backup.gemspec
287
- gem install ./kamal-backup-*.gem
288
- kamal-backup --help
254
+ DATABASE_ADAPTER=postgres
255
+ DATABASE_URL=postgres://app@app-db:5432/app_production
256
+ PGPASSWORD=change-me
289
257
  ```
290
258
 
291
- In normal Kamal use, you do not need to install the gem on the app host. Run the command inside the accessory:
259
+ MySQL/MariaDB:
292
260
 
293
261
  ```sh
294
- bin/kamal accessory exec backup "kamal-backup evidence"
262
+ DATABASE_ADAPTER=mysql
263
+ DATABASE_URL=mysql2://app@app-mysql:3306/app_production
264
+ MYSQL_PWD=change-me
295
265
  ```
296
266
 
297
- Run a local backup against a filesystem restic repository:
267
+ SQLite:
298
268
 
299
269
  ```sh
300
- export APP_NAME=local-app
301
- export DATABASE_ADAPTER=sqlite
302
- export SQLITE_DATABASE_PATH=/tmp/app.sqlite3
303
- export BACKUP_PATHS=/tmp/app-files
304
- export RESTIC_REPOSITORY=/tmp/kamal-backup-restic
305
- export RESTIC_PASSWORD=local-password
306
- export RESTIC_INIT_IF_MISSING=true
307
-
308
- kamal-backup backup
309
- kamal-backup list
310
- kamal-backup evidence
270
+ DATABASE_ADAPTER=sqlite
271
+ SQLITE_DATABASE_PATH=/data/db/production.sqlite3
311
272
  ```
312
273
 
313
- An example Docker Compose setup for local integration work is in `examples/docker-compose.integration.yml`.
314
-
315
- ## Container Contents
316
-
317
- The image is based on Debian slim Ruby and includes:
318
-
319
- - Ruby runtime
320
- - `pg_dump` and `pg_restore`
321
- - `mariadb-dump` or `mysqldump`, plus `mariadb` or `mysql`
322
- - `sqlite3`
323
- - `restic`
324
- - CA certificates
325
- - `tini`
326
-
327
- ## Security Notes
274
+ Local config files:
328
275
 
329
- - Subprocesses are executed with argument arrays, not shell interpolation.
330
- - The CLI redacts secrets in errors and evidence output.
331
- - Database backups use logical dump tools.
332
- - File data should be mounted read-only in the backup accessory.
333
- - Restores require explicit environment flags.
334
- - Object storage credentials should be least-privilege for the backup bucket or prefix.
276
+ - `config/kamal-backup.yml`
277
+ - `config/kamal-backup.local.yml`
335
278
 
336
- ## Non-Goals
279
+ Keep secrets such as `RESTIC_PASSWORD`, cloud credentials, and local DB passwords in environment variables, not in YAML files.
337
280
 
338
- - Not a hosted backup service.
339
- - Not a replacement for database point-in-time recovery.
340
- - Not a physical replication tool.
341
- - Not a secret manager.
281
+ ## Docs
342
282
 
343
- ## License
283
+ Full docs live in [`docs/`](docs/):
344
284
 
345
- MIT
285
+ - [`docs/_guide/getting-started.md`](docs/_guide/getting-started.md)
286
+ - [`docs/_guide/configuration.md`](docs/_guide/configuration.md)
287
+ - [`docs/_guide/restore.md`](docs/_guide/restore.md)
288
+ - [`docs/_guide/restore-drills.md`](docs/_guide/restore-drills.md)
289
+ - [`docs/_reference/commands.md`](docs/_reference/commands.md)
data/exe/kamal-backup CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
4
+
5
+ require "bundler/setup"
6
+
3
7
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
4
8
 
5
9
  require "kamal_backup"