harn backup
harn backup <subcommand> [options]harn backup wraps restic for encrypted recovery snapshots of durable state under .harnery/. Harnery chooses the default paths from its storage catalog and refuses snapshots that exceed the configured size ceiling.
You still own the restic repository and its password. Harnery sets the repository, password file, target set, tags, and retention defaults, then invokes restic.
Subcommands
Section titled “Subcommands”harn backup initharn backup snapshot [--if-stale <duration>] [--tag <tag>] [--include-artifacts] [--allow-large]harn backup list [--json]harn backup restore <snapshot-id> [--target <path>]harn backup prune [--dry-run]harn backup checkAll subcommands accept --repo <path> and --password-file <path>. The environment variables HARNERY_RESTIC_REPO and HARNERY_RESTIC_PASSWORD_FILE override project configuration.
Initialize or join a repository
Section titled “Initialize or join a repository”harn backup initinit creates a password file with mode 0600 when the file does not exist, then creates the restic repository. A relative backup.password_file path resolves from the project root, not the shell’s current directory.
Run init only for a new repository. To join an existing repository from another machine, put the existing password in the configured file and run harn backup check. The same password opens every snapshot in one repository.
Filesystem repositories get their local parent directory automatically. Remote repository strings such as rclone:remote:path go straight to restic; Harnery does not try to create a local directory from the remote string or configure the remote provider.
Default selection
Section titled “Default selection”The default set comes from storage catalog families classified as canonical-authority, durable-object-history, or recovery-state whose roots are inside .harnery/. Harnery then removes event ledger families and managed artifacts. Event ledgers can be much larger than the coordination records needed to recover a project, while artifacts are temporary workspaces with their own retention policy.
A Harnery test checks catalog coverage. A newly registered family must be selected, excluded by name, or rooted outside .harnery/; it cannot disappear from backups because someone forgot to update a second directory list.
Use backup.include to add .harnery-relative paths and backup.exclude to remove catalog family IDs or .harnery-relative paths. The size gate applies after those changes.
Configuration
Section titled “Configuration”{ "backup": { "repo": "rclone:archive:harnery/project-a", "password_file": ".secrets/harnery-restic.password", "include": [], "exclude": [], "max_bytes": 52428800, "schedule": { "if_stale": "24h", "tags": ["daily"] }, "keep_daily": 7, "keep_weekly": 4, "keep_monthly": 6 }}| Setting | Default | Purpose |
|---|---|---|
repo |
~/.cache/harnery/restic-repo |
Restic repository path or URL. |
password_file |
~/.config/harnery/restic-password |
File containing the repository password. |
include |
[] |
Extra paths below .harnery/. |
exclude |
[] |
Catalog family IDs or paths removed from the default set. |
max_bytes |
52428800 |
Refuse a larger selection unless --allow-large is present. |
schedule |
unset | Optional session-start snapshot policy. |
keep_daily, keep_weekly, keep_monthly |
7, 4, 6 |
Retention used by prune. |
When backup.schedule is present, the session-start hook launches snapshot --if-stale <duration> in the background with the configured tags. The child owns none of the hook’s stdio, so the harness never waits for restic or the remote provider. The hook stays quiet on success.
Three small files under .harnery/logs/ carry the schedule’s state:
| File | Writer | Purpose |
|---|---|---|
backup-schedule.log |
the background runner | Timestamped output of every scheduled run; rotated when it passes 256 KiB. |
backup-schedule.status.json |
the background runner | Exit code and finish time of the last scheduled run. |
backup-host-snapshot.json |
harn backup snapshot |
This host’s newest known snapshot time. |
The hook reads the status file first. When the last scheduled run failed, it adds one line naming the exit code, the finish time, and the log path to the session’s start-up context, prints the same line on stderr, and relaunches the snapshot so a transient error heals on the next session. It then reads the host cache: when this host’s newest snapshot is younger than if_stale, the hook launches nothing, so a machine with many session starts a day asks the remote repository about once per freshness window rather than on every start. A missing, stale, or foreign-host cache always launches the command, and the command still asks restic before deciding.
Take a snapshot
Section titled “Take a snapshot”harn backup snapshot --tag manualharn backup snapshot --if-stale 24h --tag daily--tag is repeatable. --if-stale exits successfully without creating a snapshot when this restic host already has one newer than the duration. Restic’s host filter keeps snapshots independent across machines. A cross-process lock prevents two concurrent hooks on one machine from both passing the same freshness check.
If the selected files exceed backup.max_bytes, the command reports the selected size and stops. --include-artifacts adds managed working artifacts for a deliberate one-off snapshot; the size gate still applies. --allow-large bypasses that guard for one invocation.
Inspect, verify, and restore
Section titled “Inspect, verify, and restore”harn backup list --jsonharn backup checkharn backup restore 1a2b3c4d --target ./recoveredrestore writes to a new timestamped directory when --target is absent. It does not restore over the live .harnery/ tree.
Prune snapshots
Section titled “Prune snapshots”harn backup prune --dry-runharn backup prunePruning stays manual. --dry-run previews the configured 7 daily, 4 weekly, and 6 monthly retention policy. Running without --dry-run calls restic forget --prune.