Skip to content

hasna/backup

Repository files navigation

@hasna/backup

Local-first backup coverage, verification, restore planning, and AWS backup posture audits for agents and operators.

open-backup is the control layer around proven backup targets. It does not try to replace S3, AWS Backup, RDS snapshots, or local archives. It answers the operational questions those systems leave scattered:

  • What important data is covered?
  • Where is it backed up?
  • When was the last successful run?
  • Can the artifact be verified?
  • What would restore touch before anything is applied?
  • Which AWS resources have weak backup posture?

Documentation

Doc Contents
docs/COMMANDS.md CLI command reference
docs/DESTINATIONS.md Destination capability matrix (local / S3 / SFTP / B2)
docs/AWS.md AWS audit guide — checks, IAM, interpreting findings
docs/RESTORE.md Restore runbook — plan-only workflow and manual apply
docs/RUNTIME_CONTRACTS.md Runtime backup contract schema

Install

bun install -g @hasna/backup

Quick Start

backup init
backup sources add ~/.hasna --name hasna-state
backup destinations add local ~/.hasna/backup/local --name local
backup plan
backup run
backup prune --plan
backup run --contract
backup verify latest
backup verify latest --contract
backup evidence-ref latest --source hasna-state
backup restore plan latest --target /tmp/open-backup-restore
backup restore apply latest --target /tmp/open-backup-restore --yes
backup restore plan latest --target /tmp/open-backup-restore --contract

S3 Destination

backup destinations add s3 s3://my-backups/laptop --name s3-main --aws-profile default --region us-east-1
backup run --destination s3-main
backup verify latest

S3 support uses the installed AWS CLI and explicit named profiles. open-backup does not store AWS credentials. For S3-compatible providers such as Backblaze B2, MinIO, or Wasabi, add --endpoint <url> to the S3 destination:

backup destinations add s3 s3://my-b2-bucket/laptop --name b2-main --aws-profile b2 --region us-west-004 --endpoint https://s3.us-west-004.backblazeb2.com

AWS Audits

Read-only AWS posture checks are available for backup buckets, AWS Backup, RDS/Aurora, EBS, DynamoDB, and EFS:

backup aws s3-audit --profile hasna-xyz-infra --bucket hasna-xyz-infra-backups-prod
backup aws backup-audit --profile hasna-xyz-infra --regions us-east-1
backup aws rds-audit --profile hasna-xyz-infra --regions us-east-1,eu-central-1
backup aws ebs-audit --profile hasna-xyz-infra --regions us-east-1 --snapshot-age-days 7
backup aws dynamodb-audit --profile hasna-xyz-infra --regions us-east-1
backup aws efs-audit --profile hasna-xyz-infra --regions us-east-1
backup aws inventory --profile hasna-xyz-infra

Runtime Contracts

Runtime contracts describe cloud-backed app state before any backup or restore is run:

backup runtime-contract validate runtime-contract.json
backup runtime-contract plan runtime-contract.json --home ~/.hasna/backup

The validator distinguishes local SQLite and local file state from remote Postgres/RDS and S3 state. Local stores are treated as filesystem backup sources. Remote Postgres/RDS and S3 stores are treated as metadata and posture contracts only; validation does not connect to databases, create snapshots, upload objects, create buckets, change AWS settings, run Terraform, or apply restores. Contract findings reject credential-bearing fields and do not echo secret values.

See Runtime Backup Contracts for the schema and examples.

CLI Surface

backup init
backup doctor
backup inventory
backup status
backup sources list|add|remove|inspect
backup destinations list|add|remove|test|inspect
backup policy show|set
backup plan
backup run
backup prune --plan|--apply
backup list
backup hold <backup-id|latest> [--reason label] [--note text]
backup unhold <backup-id|latest>
backup holds
backup show <backup-id|latest>
backup manifest <backup-id|latest>
backup verify <backup-id|latest>
backup evidence-ref <backup-id|latest> [--source name]
backup restore plan <backup-id|latest> --target <path>
backup restore apply <backup-id|latest> --target <path> --yes
backup runtime-contract validate|plan <file>
backup aws inventory|s3-audit|backup-audit|rds-audit|ebs-audit|dynamodb-audit|efs-audit
backup-mcp

All CLI commands emit JSON by default so humans, agents, and CI can consume the same output. --contract on run, plan, verify, and restore plan emits the corresponding @hasna/contracts work run, proof bundle, or validation plan. evidence-ref emits an @hasna/contracts evidence reference for a backup manifest or source archive.

Safety Model

  • Restore apply is dry-run by default. It prints the pre-flight file diff and writes only with --apply or --yes.
  • Existing restore paths are never overwritten unless --yes is provided.
  • Prune is plan-only by default. backup prune --apply deletes only the artifacts listed in that same prune plan.
  • backup hold <backup-id> pins a backup so prune can never expire it, whatever the policy says. Held backups are listed with a hold:<reason> retention reason, and an unreadable hold store makes prune fail instead of expiring pinned backups.
  • Secrets are excluded by default through archive exclusions.
  • Archives are not encrypted by this tool. policy.encryption is always none; there is no encryption or key-management option, and none of the tgz archives written to a local, S3, or SFTP destination are encrypted or signed by backup. Any protection at rest comes entirely from the destination: bucket-level SSE and versioning protect against media theft and accidental overwrite, but not against any principal that can read the objects — anyone holding s3:GetObject (or filesystem read access, or the SFTP account) can read every archive in plaintext. Do not describe these archives as encrypted. Restrict read access at the destination and treat archive contents as sensitive.
  • .git is excluded by default, so an archive of a repository working tree carries no commit history. sources add and run warn about that, run records gitHistoryExcluded on the affected manifest archive entry, and doctor reports it. Pass backup sources add <path> --include-git-history to keep the object database while every other default exclude (secrets patterns, node_modules, build output) stays in force — that exclude list is derived from the defaults, so it cannot drift from them. On one machine 31 of 34 registered sources carried this exclude and no source targeted any object database, so no git history was captured at all while coverage reported green; see docs/GIT_HISTORY.md. Where the objects live outside the source path (a linked worktree), back up commit history as a git bundle create <file> --all or git clone --mirror artifact instead — and verify that artifact by cloning from it, because git bundle verify reports "The bundle records a complete history." for bundles of shallow repositories that then fail to clone.
  • The .git exclusion is a literal path segment with no wildcard, so it does not match a bare mirror directory named <name>.git. A bare mirror is archived in full — objects, packs, and refs. classifyGitSource reports the history shape rather than asking the misleading question "is there a git object database here": bare-repository (history included, not flagged — unless the repository is shallow, which is reported as git-history-shallow because a complete archive of an incomplete object database still cannot restore), work-tree and nested-work-tree (history excluded, flagged git-history-excluded), work-tree-pointer (a linked worktree whose .git is a pointer file; the objects live in the parent repository, so it is flagged git-history-external only when no other configured source actually archives that object database), and inside-work-tree (a plain subdirectory of someone else's checkout, not flagged — that repository's .git was never under the source path, so the exclusion is not what drops its history). A bare repository is identified by layout, including bare = true, never by a .git name suffix. The nested scan runs before the ancestor walk, so a source that is both inside a checkout and contains its own nested repository or submodule is still reported.
  • A work-tree scan that runs out of budget reports git-history-unknown (and gitHistoryScanTruncated on the manifest entry), never silence: "not proven clean" is not the same claim as "clean".
  • Two related coverage traps are reported as red, because in both the archive contains far less than the tree the source names: source-self-excluded, where the source's own basename matches its exclude list so tar -C <parent> <basename> drops the only top-level member (adding a repository's own .git directory as a source does this, since .git is excluded by default); and source-symlink, where a symlinked source archives the link rather than the tree it points at. verify only compares checksums, so neither is detectable after the fact.
  • doctor probes only the external tools the configured destinations actually need. In its tools array, null means "not probed", true means "exited 0", and false means "found, exited non-zero" — a tool absent from $PATH fails the whole command instead, which is tracked as its own fix. See Command Reference.
  • Source mode inventory-only records coverage metadata without archiving file contents.
  • Runtime contract validation rejects credential-bearing fields and never performs live cloud/database mutations.
  • AWS audit commands are read-only. S3 backup, verify, and prune operations perform explicit upload, download, and delete operations against the selected backup destination.
  • Retention uses UTC daily, ISO-weekly, and UTC monthly buckets from the configured GFS policy, decided per source rather than per run. Each source gets its own GFS timeline, so a run that archives one source can never evict a different source's only copy. Retention is then applied per run: if any source in a run still needs a copy, the whole run is kept, so a run is never left with manifests describing archives that were deleted. This over-retains multi-source runs on purpose — see docs/COMMANDS.md for why, and for the capacity impact. Zero retention (--daily 0 --weekly 0 --monthly 0) still expires everything; per-source scoping is not a floor, backup hold is.
  • Prune refuses to expire a preservation-marked source, whatever the policy says. A source whose name or path carries a marker token — predelete, preservation, delete-prep, do-not-prune, and the rest listed in docs/COMMANDS.md — is pinned with a preserved:<marker> retention reason, and every artifact of that run carries preservedBy. It reads the naming convention as the intent it already encodes: an archive called <repo>-predelete exists so that deleting <repo> is safe, and GFS keeping one backup per calendar day per source would otherwise evict it the moment a later run the same UTC day took its daily slot. Matched against the source name, source path, and archive name only, never the destination root or catalog path. Extend per installation with backup policy set --preserve-markers a,b; the built-ins cannot be removed, and an unusable marker list makes prune refuse to produce a plan rather than produce an unguarded one. This complements backup hold and does not replace it: a marker protects archives whose name declares the intent, a hold protects a specific id whatever it is called.

Local State

The default state directory is:

~/.hasna/backup/
  config.json
  manifests/
  runs.jsonl
  restore-plans/
  tmp/

Override it for tests or isolated runs:

HASNA_BACKUP_HOME=/tmp/backup-state backup init

Hasna Baseline

The first internal profile should cover:

backup init
backup sources add ~/.hasna --name hasna-state
backup sources add ~/.codewith --name codewith-state
backup sources add ~/workspace --name workspace
backup destinations add s3 s3://hasna-xyz-infra-backups-prod/open-backup/$(hostname) --name infra-s3 --aws-profile hasna-xyz-infra --region us-east-1
backup run --destination infra-s3
backup verify latest

MCP

backup-mcp separates inspection, read-only audit, artifact-only, and mutating tools.

Inspection tools:

  • backup_status
  • backup_inventory
  • backup_list
  • backup_verify

Read-only AWS audit tool:

  • backup_runtime_contract_validate
  • backup_aws_inventory

Local inspection can initialize the backup home, and verification can materialize temporary archive copies for remote backups.

Artifact-only tool:

  • backup_restore_plan writes a local restore-plan JSON file and may materialize temporary archive copies for remote backups, but it does not apply a restore.

Mutating tool:

  • backup_run requires allowMutating: true and confirmation: "run backup" before it can run.

The MCP surface intentionally does not expose restore apply, prune, or destructive lifecycle changes.

License

Apache-2.0

About

Local-first backup coverage verification restore planning and AWS audits

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages