backup
Config-driven archiver that wraps the git-src backup as a tar-archive
capability. What gets archived — which source trees, where the archive lands,
and what is excluded — is defined in configuration, not code, so adding or
retuning a backup is a config edit rather than a release.
Usage
backup # run every enabled instance, in order
backup --only git-src,docs # run only these instances (comma list or repeated)
backup --tag nightly # run only instances carrying this tag
backup --list # print the configured instances and exit
backup --dry-run # walk and report what would be archived, write nothing
backup --only git-src --source DIR --out FILE # one-shot override of the selected instance
backup --show-excludes git-src --for PATH # print the resolved excludes under PATH
--source / --out override the single selected instance for one run (a
non-empty flag wins over the configured value); they require exactly one
selected instance, via --only. --show-excludes is read-only: it prints
the resolved exclude set and, with --for <path>, the .bkpignore rules
effective under that path, then exits without writing an archive.
Configuration
Instances live in buvis-backup.yaml in the buvis config stack
($BUVIS_CONFIG_DIR → ~/.config/buvis → cwd), deep-merged over a bundled
default. Instances are a name-keyed map, so a machine layer adds an instance
under a new key and overrides a field of an existing instance by that key.
# buvis-backup.yaml
instances:
git-src:
use: tar-archive # a registered capability
order: 10 # instances run in ascending order
tags: [nightly]
with: # capability inputs
source: ~/git/src
out: ~/backups/git-src-%Y%m%d-%H%M%S.tar.gz
engine: python-tarfile # or system-tar (opt-in)
excludes: # global basename/relpath exclude list
- node_modules
- target
- __pycache__
Each instance is a use entry naming a capability with optional with
inputs. Common fields:
Field |
Meaning |
|---|---|
|
registered capability name ( |
|
set |
|
integer; instances run ascending (default 100) |
|
list of strings for |
|
capability inputs ( |
The out value supports strftime codes (e.g. %Y%m%d-%H%M%S), expanded
at archive time. An unknown capability name, an unknown with input, or an
unknown engine value is a config/step error reported clearly — never a stack
trace.
The global excludes list layers via the 00080 excludes+ / excludes-
directives, so a lower layer extends or trims the shared list without re-listing
it:
# buvis-backup.local.yaml (machine-local, never `dot add`ed)
excludes+: [scratch-notes] # add to the inherited list
excludes-: [target] # drop a default on this box only
.bkpignore files
Beyond the global list, a .bkpignore file placed in any directory of a
source tree tunes exclusions for that subtree only, gitignore-style:
A bare line adds an exclude for that subtree.
A
!patternline un-ignores — it re-includes a path the global default would otherwise drop, path-scoped to that subtree (so!targetin repo A’s.bkpignorekeeps only A’starget/; repo B’s stays excluded).Blank lines and
#comments are ignored;\!escapes a literal!.
Precedence follows gitignore: a descendant .bkpignore overrides an
ancestor, and within one file a later line overrides an earlier one. So an
ancestor !target can be re-excluded by a descendant plain target line.
.bkpignore files are never themselves archived.
Engines
Two archive engines produce byte-for-byte equivalent member sets; Python owns
file selection in both, so .bkpignore scoping is identical:
Engine |
Behaviour |
|---|---|
|
default; streams a |
|
opt-in; shells out to system |
Both engines write the archive atomically (temp file, fsync, chmod 600,
os.replace), so a crash or ENOSPC never leaves a partial .tar.gz at
the final path.