dbara 20260714__tar.gz

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.
dbara-20260714/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DBaRA Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,420 @@
1
+ Metadata-Version: 2.4
2
+ Name: dbara
3
+ Version: 20260714
4
+ Summary: Docker Backup and Restore Application — stop, archive, verify, restart
5
+ Author: NASible
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/NASible/DBaRA
8
+ Project-URL: Repository, https://github.com/NASible/DBaRA
9
+ Project-URL: Bug Tracker, https://github.com/NASible/DBaRA/issues
10
+ Keywords: docker,backup,restore,archive,homelab,containers
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: System :: Archiving :: Backup
22
+ Classifier: Topic :: System :: Systems Administration
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # DBaRA — Docker Backup and Restore Application
29
+
30
+ [![CI](https://github.com/NASible/DBaRA/actions/workflows/ci.yml/badge.svg)](https://github.com/NASible/DBaRA/actions/workflows/ci.yml)
31
+ [![PyPI](https://img.shields.io/pypi/v/dbara.svg)](https://pypi.org/project/dbara/)
32
+ [![Python](https://img.shields.io/pypi/pyversions/dbara.svg)](https://pypi.org/project/dbara/)
33
+
34
+ A production-ready Python CLI for backing up and restoring Docker application data directories.
35
+ Stops the container, archives the data, verifies integrity, and restarts.
36
+
37
+ ## Features
38
+
39
+ - **change detection**
40
+ - **checksums**
41
+ - **retry logic**
42
+ - **hooks**
43
+ - **per-app locking**
44
+
45
+ ## Requirements
46
+
47
+ - **Linux Desktop/Server OS**
48
+ - **Python 3.11+**
49
+ - **Docker** in `PATH`
50
+ - **zstd** or **gzip** (zstd recommended)
51
+ - **xxh128sum** (optional — falls back to sha256sum when absent)
52
+
53
+ ## Installation
54
+
55
+ ### From PyPI (recommended)
56
+
57
+ ```bash
58
+ pipx install dbara
59
+ ```
60
+
61
+ Or into an isolated virtualenv:
62
+
63
+ ```bash
64
+ python3 -m venv /opt/dbara
65
+ /opt/dbara/bin/pip install dbara
66
+ ln -s /opt/dbara/bin/dbara /usr/local/bin/dbara
67
+ ```
68
+
69
+ ### From source
70
+
71
+ ```bash
72
+ git clone https://github.com/NASible/DBaRA.git
73
+ cd DBaRA
74
+ pipx install .
75
+ ```
76
+
77
+ Verify the install:
78
+
79
+ ```bash
80
+ dbara --version
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Examples
86
+
87
+ ### Backup — single app
88
+
89
+ Stop the container, archive the data directory, verify the checksum, then restart:
90
+
91
+ ```bash
92
+ dbara -m backup \
93
+ -a jellyfin \
94
+ -d /srv/apps \
95
+ -b /srv/backups \
96
+ --keep-last 7 \
97
+ -v
98
+ ```
99
+
100
+ `--keep-last 7` automatically prunes archives older than the 7 most recent.
101
+ `-v` enables debug output. Use `-vv` to also write a logfile.
102
+
103
+ ### Backup — all apps
104
+
105
+ Iterate every subdirectory under `--app-folder-dir`, skipping apps whose contents have not
106
+ changed since the last backup:
107
+
108
+ ```bash
109
+ dbara -m backup \
110
+ -A \
111
+ -d /srv/apps \
112
+ -b /srv/backups \
113
+ -p prod \
114
+ --keep-last 7
115
+ ```
116
+
117
+ `-p prod` inserts `prod` into every archive filename:
118
+ `myhost_prod_jellyfin_20240615_020000.bkup.tar.zst`
119
+
120
+ ### Backup — force even when unchanged
121
+
122
+ ```bash
123
+ dbara -m backup -a sonarr -d /srv/apps -b /srv/backups -f
124
+ ```
125
+
126
+ `-f / --force` bypasses change detection and always creates a new archive.
127
+
128
+ ---
129
+
130
+ ### Restore — single app (latest archive)
131
+
132
+ Auto-selects the most recent archive for the app by modification time:
133
+
134
+ ```bash
135
+ dbara -m restore \
136
+ -a jellyfin \
137
+ -d /srv/apps \
138
+ -b /srv/backups \
139
+ -r /srv/apps \
140
+ -o 1000:1000
141
+ ```
142
+
143
+ `-o 1000:1000` runs `chown -R 1000:1000` on the restored directory.
144
+
145
+ ### Restore — single app (specific archive)
146
+
147
+ Pass the archive path directly to `--backup-dest-dir` to restore a particular snapshot:
148
+
149
+ ```bash
150
+ dbara -m restore \
151
+ -a jellyfin \
152
+ -d /srv/apps \
153
+ -b /srv/backups/myhost_jellyfin_20240615_020000.bkup.tar.zst \
154
+ -r /srv/apps
155
+ ```
156
+
157
+ ### Restore — several specific apps
158
+
159
+ `--apps` takes a space-separated list and restores each one sequentially:
160
+
161
+ ```bash
162
+ dbara -m restore \
163
+ --apps authelia authentik vaultwarden \
164
+ -d /srv/apps \
165
+ -b /srv/backups \
166
+ -r /srv/apps \
167
+ --ownership-map /etc/dbara/owners.txt
168
+ ```
169
+
170
+ Using `--ownership-map` instead of `-o` applies the right ownership per app automatically.
171
+
172
+ ### Restore — all apps
173
+
174
+ Auto-discovers every app present in the backup directory and restores each one.
175
+ Useful after a full server rebuild:
176
+
177
+ ```bash
178
+ dbara -m restore \
179
+ -A \
180
+ -d /srv/apps \
181
+ -b /srv/backups \
182
+ -r /srv/apps \
183
+ --ownership-map /etc/dbara/owners.txt \
184
+ -v
185
+ ```
186
+
187
+ > **Note:** `-A` for restore infers app names from archive filenames. App names that contain
188
+ > underscores (e.g. `my_app`) will not be auto-discovered — use `--apps` explicitly for those.
189
+
190
+ ### Restore — without restarting the container
191
+
192
+ ```bash
193
+ dbara -m restore \
194
+ -a sonarr \
195
+ -d /srv/apps \
196
+ -b /srv/backups \
197
+ -r /srv/apps \
198
+ -s
199
+ ```
200
+
201
+ `-s / --no-start-container` extracts the data but leaves the container stopped.
202
+
203
+ ---
204
+
205
+ ## Archive Filename Format
206
+
207
+ ```text
208
+ {hostname}_{prefix_}{app}_{YYYYMMDD_HHMMSS}.bkup.tar.{ext}
209
+ ```
210
+
211
+ Examples:
212
+
213
+ | Scenario | Filename |
214
+ | -------- | -------- |
215
+ | No prefix, zstd | `myhost_jellyfin_20240615_020000.bkup.tar.zst` |
216
+ | With prefix "prod" | `myhost_prod_jellyfin_20240615_020000.bkup.tar.zst` |
217
+ | gzip compressor | `myhost_jellyfin_20240615_020000.bkup.tar.tgz` |
218
+
219
+ Checksum sidecar files sit next to the archive:
220
+
221
+ - `...bkup.tar.zst.xxh128` (when xxh128sum is available)
222
+ - `...bkup.tar.zst.sha256sum` (fallback)
223
+
224
+ ---
225
+
226
+ ## CLI Reference
227
+
228
+ ### Required flags
229
+
230
+ | Flag | Description |
231
+ | ---- | ----------- |
232
+ | `-m, --mode` | Operation: `backup` or `restore` |
233
+ | `-d, --app-folder-dir` | Parent directory containing per-app folders |
234
+ | `-b, --backup-dest-dir` | Backup destination directory (or specific archive file for restore) |
235
+
236
+ ### Common options
237
+
238
+ | Flag | Default | Description |
239
+ | ---- | ------- | ----------- |
240
+ | `-a, --app-name` | — | Single app to back up or restore |
241
+ | `--apps APP [APP ...]` | — | Restore a specific list of apps (restore mode only) |
242
+ | `-A, --all-apps` | — | Backup: every subdirectory under `--app-folder-dir`; Restore: every app found in `--backup-dest-dir` |
243
+ | `-r, --restore-dest-dir` | — | Where to extract restored files |
244
+ | `-o, --owner-group` | — | Set `user:group` ownership after restore (e.g. `1000:1000`) |
245
+ | `-p, --optional-prefix` | — | Prefix inserted in the archive filename |
246
+ | `-x, --remove-after-backup` | off | Delete app directory after backup (**archive integrity verified first**) |
247
+ | `-s, --no-start-container` | off | Do not restart the container after the operation |
248
+ | `-f, --force` | off | Force backup even when no changes are detected |
249
+ | `--strict` | off | Treat any warning as a fatal error |
250
+ | `--skip-checksum` | off | Skip checksum generation/verification |
251
+ | `-v, --verbose` | — | `-v` enables debug output; `-vv` also writes a logfile |
252
+
253
+ ### Performance / behaviour
254
+
255
+ | Flag | Default | Description |
256
+ | ---- | ------- | ----------- |
257
+ | `--compress` | `zstd` | Compressor: `zstd` or `gzip` |
258
+ | `--zstd-opts` | `-T0 -3` | Options forwarded to `zstd` |
259
+ | `--fast-hash / --no-fast-hash` | enabled | Prefer `xxh128sum` over `sha256sum` when available |
260
+ | `--io-nice / --no-io-nice` | enabled | Wrap heavy I/O with `ionice -c2 -n7 nice -n 19` |
261
+ | `--keep-last N` | `0` (disabled) | Prune all but the last N archives per app |
262
+ | `--state-dir` | `/var/lib/app-backup/state` | Directory for change-detection `.sig` state files |
263
+ | `--hooks-dir` | `/etc/app-backup/hooks` | Root directory for per-app hook scripts |
264
+ | `--lock-dir` | `/var/lock` | Directory for per-app lock files |
265
+ | `--ownership-map` | — | File mapping app names to `user:group` ownership |
266
+ | `--stop-timeout` | `30` | Seconds passed to `docker stop -t` |
267
+ | `--retry-max` | `5` | Maximum retry attempts for shell commands |
268
+ | `--retry-base-sleep` | `2` | Base sleep for exponential backoff: `base ** attempt` seconds |
269
+ | `--logfile` | `/var/log/app-backup-restore.log` | Log file path (written when `-vv` is active) |
270
+
271
+ ---
272
+
273
+ ## Hooks
274
+
275
+ Place executable scripts at `HOOKS_DIR/<app>/<hook-name>`:
276
+
277
+ ```text
278
+ /etc/app-backup/hooks/
279
+ ├── jellyfin/
280
+ │ ├── pre-backup
281
+ │ ├── post-backup
282
+ │ ├── pre-restore
283
+ │ └── post-restore
284
+ └── sonarr/
285
+ └── pre-backup
286
+ ```
287
+
288
+ Hook scripts receive no arguments. The backup or restore is **aborted** if a hook exits non-zero
289
+ (subject to `--retry-max` retries with exponential backoff).
290
+
291
+ Example pre-backup hook that flushes a database:
292
+
293
+ ```bash
294
+ #!/bin/bash
295
+ sqlite3 /srv/apps/myapp/data.db "PRAGMA wal_checkpoint(FULL);"
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Ownership Map
301
+
302
+ Use `--ownership-map` to map app names to `user:group` ownership without specifying `-o` on every
303
+ restore command:
304
+
305
+ ```ini
306
+ # /etc/app-backup/owners.txt
307
+ jellyfin=1000:1000
308
+ sonarr=1001:1001
309
+ radarr=1001:1001
310
+ # Lines starting with # are ignored
311
+ ```
312
+
313
+ ```bash
314
+ dbara -m restore -a jellyfin -d /srv/apps -b /srv/backups -r /srv/apps \
315
+ --ownership-map /etc/app-backup/owners.txt
316
+ ```
317
+
318
+ If `--owner-group` is also specified it takes precedence over the map file.
319
+
320
+ ---
321
+
322
+ ## Change Detection
323
+
324
+ DBaRA computes a SHA-256 digest over the sorted list of `(relative_path, file_size, mtime_ns)`
325
+ tuples for every file under the app directory. The digest is stored as a `.sig` file in
326
+ `--state-dir`. If the digest is unchanged since the last run, the backup is skipped.
327
+
328
+ - Use `-f / --force` to override and backup anyway.
329
+ - State files are per-app: `{state-dir}/{app}.sig`.
330
+
331
+ ---
332
+
333
+ ## SQLite Safe Backup
334
+
335
+ Before archiving, DBaRA automatically creates consistent hot-backup copies of every SQLite
336
+ database it finds (`.sqlite`, `.sqlite3`, `.db`) using Python's built-in `sqlite3.backup()`.
337
+ The copies are stored as `<file>.bak` alongside the originals, included in the archive, and then
338
+ deleted after the tar operation completes.
339
+
340
+ This ensures the archive contains a consistent point-in-time snapshot even if the application
341
+ was writing to the database at the time of backup.
342
+
343
+ ---
344
+
345
+ ## Running as a Scheduled Job
346
+
347
+ ### systemd timer (recommended)
348
+
349
+ ```ini
350
+ # /etc/systemd/system/dbara-backup.timer
351
+ [Unit]
352
+ Description=Nightly DBaRA backup
353
+
354
+ [Timer]
355
+ OnCalendar=*-*-* 02:00:00
356
+ RandomizedDelaySec=300
357
+ Persistent=true
358
+
359
+ [Install]
360
+ WantedBy=timers.target
361
+ ```
362
+
363
+ ```ini
364
+ # /etc/systemd/system/dbara-backup.service
365
+ [Unit]
366
+ Description=DBaRA backup
367
+
368
+ [Service]
369
+ Type=oneshot
370
+ ExecStart=/usr/local/bin/dbara -m backup -A \
371
+ -d /srv/apps -b /srv/backups \
372
+ --keep-last 7 -vv
373
+ ```
374
+
375
+ ```bash
376
+ systemctl enable --now dbara-backup.timer
377
+ ```
378
+
379
+ ### cron
380
+
381
+ ```cron
382
+ 0 2 * * * root /usr/local/bin/dbara -m backup -A -d /srv/apps -b /srv/backups --keep-last 7
383
+ ```
384
+
385
+ ---
386
+
387
+ ## Deployment with Ansible
388
+
389
+ An Ansible role is available in a separate repository:
390
+ [**NASible/ansible-role-dbara**](https://github.com/NASible/ansible-role-dbara)
391
+
392
+ ```bash
393
+ ansible-galaxy install nasible.dbara
394
+ ```
395
+
396
+ The role installs DBaRA into a virtualenv, creates required directories,
397
+ and configures a systemd timer for scheduled backups.
398
+
399
+ ---
400
+
401
+ ## Development
402
+
403
+ ```bash
404
+ # Install dev dependencies
405
+ python3 -m venv .venv
406
+ .venv/bin/pip install -r requirements-dev.txt -e .
407
+
408
+ # Run tests
409
+ .venv/bin/pytest
410
+
411
+ # Type-check
412
+ .venv/bin/mypy dbara
413
+
414
+ # Lint
415
+ .venv/bin/ruff check dbara tests
416
+ ```
417
+
418
+ All tests are pure unit tests — no Docker, no real filesystem writes (except where `tmp_path`
419
+ is used), no subprocess calls. The `FakeRunner` and `FakeDockerClient` test doubles in
420
+ `tests/conftest.py` cover every code path without touching the system.