Point-in-time snapshots for S3 buckets and DynamoDB tables.
Project description
NSHM Backup Solution
AWS-native backup management CLI for NSHM datasets (ToshiAPI, THS, static
reports, Weka). Replaces AWS Backup ($1,700 NZD/month before) with
incremental S3 sync + 2-tier Glacier Instant Retrieval lifecycle and
DynamoDB Point-in-Time exports — running at **$108 NZD/month** in
production since May 2026 (per
ADR-006).
Implementation Status
| Phase | Status | Description |
|---|---|---|
| Phase 1 | ✅ Complete | CLI skeleton with Typer + Config system + S3 backup operations |
| Phase 2 | ✅ Complete | DynamoDB PITR export + EventBridge scheduling |
| Phase 3 | ✅ Complete | Slack + SNS-email; daily health report; Lambda-error alarm (ADR-005); YAML-managed recipients (ADR-008) |
| Phase 4 | ✅ Complete | Restore (S3 + DynamoDB, cross-account) |
| Phase 5 | ✅ Complete | Testing, validation, event audit log, daily-canary restore tests |
| Phase 6 | ✅ Complete | Production cutover (all 4 sources live since April 2026; AWS Backup decommissioned May 2026) |
| Phase 7 | ✅ Complete | ADR-009 signal-class taxonomy + class-1 head-check tagging; validated end-to-end against real AWS (sandbox cycles 0–3, June 2026) |
Tests: 430+ passing · Coverage: 76% · Lint: ruff + mypy clean
Features (implemented)
- Configuration: YAML config with Pydantic validation, alias→ARN mapping
- S3 Backup: Incremental sync with 2-tier lifecycle policy — Standard → Glacier Instant Retrieval at 30 days; objects retained forever (ADR-006)
- DynamoDB Backup: Point-in-Time export to S3, idempotent export bucket setup
- EventBridge Scheduling: Create/enable/disable weekly/daily/hourly rules; localised time input and display
- Lambda Handler: EventBridge-triggered backup orchestration (S3 + DynamoDB)
- Restore: S3 (direct copy + S3 Batch Operations) and DynamoDB PITR restore with async status tracking
- Testing:
backup test integrity(ETag diff + PITR check) andbackup test restore(sample restore) - Event audit log: Append-only JSONL log in backup bucket (
_events/) — all backup/restore events recorded - Daily health report (
backup health-report): per-source status + inventory freshness + source-vs-backup divergence (both directions) + class-1 head-check tagging + sampled restore verification, delivered to Slack and SNS-email. Fires automatically at 10:45 NZT (≈60 min after the 09:45 NZT scheduled backup); canary (weka) tested daily, large sources rotated through Mon/Wed/Fri. Signal classification follows ADR-009 (class 1 RED / class 2 ℹ info / class 3 yellow). See docs/user-guide/health-report.md. - Lambda-error alarm: CloudWatch alarm on backup Lambda
Errors→ SNS → email, fires within ~5 min of any hard failure. Complementary to the daily report (ADR-005 fast path). - YAML-managed notification recipients:
notifications.alerts.emails+notifications.reports.email.addresseslists inbackup-config.yaml;backup notifications applyreconciles SNS subscriptions to match. See docs/operations/enabling-notifications.md. - Dry-run mode: All mutating operations support
--dry-run - JSON output:
--output jsonfor scripting - Localised timestamps: CLI input/output in NZDT/NZST/AEST/AEDT
Installation
uv sync --all-extras # installs all deps including dev and docs extras
The backup command is registered as a console script and available immediately after install.
Usage
Global flags
backup --help
backup --dry-run run --source toshi # Simulate without executing
backup --verbose run --source all # Detailed logging
backup --output json schedule show # Machine-readable output
Configuration
backup config show # Display full loaded config
backup config validate # Validate backup-config.yaml
backup config show --key retention # Show a specific section
Run backup
backup run --source toshi # S3 sync + DynamoDB PITR export for toshi
backup run --source ths # S3 sync for ths
backup run --source all # All sources
backup run --source toshi --full-sync # Force full copy (skip ETag check)
backup --dry-run run --source toshi # Preview without executing
Schedule management
backup schedule show # List EventBridge rules with localised run times
# --time accepts UTC (HH:MM), localised (HH:MM TZ), or full datetime (YYYY-MM-DD HH:MM TZ)
backup schedule add --source toshi --frequency weekly --time '02:00 NZST'
backup schedule add --source toshi --frequency weekly --time '2026-03-29 12:15 NZDT' # day-of-week from date
backup schedule add --source toshi --frequency daily --time '01:00 NZST'
backup schedule add --source toshi --frequency hourly --time '00:30' # :30 past each hour
# Daily health report (ADR-005 slow path) — dispatched via the same Lambda
backup schedule add --source _health --task-type health_report \
--frequency daily --time '14:30 NZST'
backup schedule enable --source toshi # Enable all rules for toshi
backup schedule enable --source toshi --frequency weekly # Enable weekly only
backup schedule disable --source toshi # Disable all rules for toshi
backup schedule remove --source toshi --frequency daily # Delete rule entirely
Restore
backup restore run --source toshi --buckets nzshm-toshi-api-data
backup restore run --source toshi --tables ToshiAPI-FileTable --to-point-in-time '2026-03-25 07:50 NZDT'
backup restore status --source toshi
Testing
backup test integrity --source toshi # ETag diff + PITR check
backup test restore --source toshi # Sample restore (direct copy)
backup test restore --source toshi --use-batch # Sample restore via S3 Batch Operations
backup test alert # Force Lambda-error alarm to verify alarm path
Daily health report (ADR-005 slow path)
backup health-report preview # Build + print, skip restore tests
backup health-report run # Full report (incl. restore tests)
backup health-report run --send # Build + deliver via Slack + SNS-email
backup health-report run --weekday 0 # Force rotation (0=Mon … 6=Sun)
Operator guide: docs/user-guide/health-report.md.
Notification subscriptions
backup notifications show # List confirmed/pending subscribers
backup notifications apply # Reconcile SNS to match backup-config.yaml lists
backup notifications apply --dry-run # Preview without changing SNS
Recipient runbook: docs/operations/enabling-notifications.md.
Event audit log
backup events --source toshi # Show recent backup/restore events
backup events --source toshi --limit 50
Status & reporting
backup status # Live per-source backup state (implemented)
backup status --source toshi --output json # Machine-readable status
backup events --source toshi # Recent backup/restore event log
backup report --period 30d # Aggregate report (stub — coming soon)
backup costs predict # Cost forecasting (stub — coming soon)
Configuration
Copy backup-config.example.yaml to backup-config.yaml and fill in your account ID and resource names:
general:
region: ap-southeast-2
environment: production
lambda_arn: null # Set after first serverless deploy
sources:
toshi:
display_name: "ToshiAPI"
s3_buckets:
- arn:aws:s3:::YOUR-TOSHI-BUCKET-NAME
dynamodb_tables:
- arn:aws:dynamodb:ap-southeast-2:ACCOUNT_ID:table/ToshiAPI-FileTable
- arn:aws:dynamodb:ap-southeast-2:ACCOUNT_ID:table/ToshiAPI-ThingTable
dynamodb_export_format: DYNAMODB_JSON
ths:
display_name: "THS_dataset_prod"
s3_buckets:
- arn:aws:s3:::YOUR-THS-BUCKET-NAME
dynamodb_export_format: DYNAMODB_JSON
retention:
hot_days: 30 # S3 Standard
warm_days: 120 # Glacier Instant (must be >= hot_days + 90; Deep Archive transition derived from this)
max_age_days: 365
restore:
auto_approve_threshold: 100 # NZD — auto-approve below this
dual_approval_threshold: 500 # NZD — two approvers above this
Deployment
Prerequisites
npm install -g serverless
uv sync --all-extras
cp backup-config.example.yaml backup-config.yaml # edit with real values
Deploy Lambda
serverless deploy # Deploy to AWS
serverless deploy --stage prod # Production stage
# After deploy, update lambda_arn in backup-config.yaml, then re-deploy
# to wire up EventBridge targets.
Add schedules after deploy
backup schedule add --source toshi --frequency weekly --time 14:00
backup schedule add --source ths --frequency weekly --time 14:00
backup schedule show
Sandbox testing
See scripts/sandbox_setup.sh — creates lightweight source
resources (S3 buckets + DynamoDB tables with PITR, seeded with sample data) in a sandbox
AWS account so you can run backup run and backup schedule against real AWS without
touching production.
# One-time setup
scripts/sandbox_setup.sh setup
# Run backup against sandbox resources
backup run --source toshi
backup run --source all --dry-run
# Tear down all sandbox resources when done
scripts/sandbox_setup.sh teardown
See backup-config.sandbox.yaml for the matching config.
Development
make test # All tests with coverage
make lint # ruff + mypy
make fmt # ruff format + ruff --fix
make check # lint then test
make upgrade # upgrade deps (1-week safety margin)
uv run pytest tests/test_foo.py # single file
Architecture
EventBridge (cron)
├── per-source backup at 13:05 NZST → Lambda → S3 sync + DynamoDB PITR export
└── daily health-report at 14:30 NZST → Lambda → Slack + SNS-email
CloudWatch alarm on Lambda Errors → SNS alerts topic → email subscribers
Backup bucket naming (in the backup account, region-suffixed):
- S3:
bb-{source}-s3-{label}-{region}-{source_account_id} - DynamoDB export:
bb-{source}-dynamo-{region}-{source_account_id}
IAM: the Lambda role denies s3:DeleteObject and s3:DeleteBucket on real
backup buckets (delete-protected; lifecycle expiration still fires). Scoped Allow
for both on the bb-restore-test-* name pattern so the restore-test workflow can
clean up its temp buckets. DynamoDB restores always go to a new table (never
overwrite in-place).
Documentation
Operations:
- Operator Cheatsheet — "to change X, do Y"
- Daily Health Report (user guide)
- Enabling Notifications (runbook)
- Inventory Bucket Recovery (runbook)
- Production Deployment Log — chronological deploy history with verification commands
Architecture / design:
- Design Plan & Cost Analysis
- How It Works
- CLI Reference
- Architecture Decision Records
- Typer rationale
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aws_snapshot-0.3.0.tar.gz.
File metadata
- Download URL: aws_snapshot-0.3.0.tar.gz
- Upload date:
- Size: 537.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
01caf1641d4c2766bbe5d94e43aa7e8937fc5b10e3006dbcacdd06b9773aee9e
|
|
| MD5 |
a271cc7882b34e0a55f84b9f9eb330df
|
|
| BLAKE2b-256 |
f4888ddaa774100f079375842e332296868f74b33717e13a875a12051fe67d3e
|
Provenance
The following attestation bundles were made for aws_snapshot-0.3.0.tar.gz:
Publisher:
release.yml on GNS-Science/aws-snapshot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aws_snapshot-0.3.0.tar.gz -
Subject digest:
01caf1641d4c2766bbe5d94e43aa7e8937fc5b10e3006dbcacdd06b9773aee9e - Sigstore transparency entry: 2017403622
- Sigstore integration time:
-
Permalink:
GNS-Science/aws-snapshot@55cfe40767d1345f2afe4fda28e38a8c613aa0af -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/GNS-Science
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@55cfe40767d1345f2afe4fda28e38a8c613aa0af -
Trigger Event:
push
-
Statement type:
File details
Details for the file aws_snapshot-0.3.0-py3-none-any.whl.
File metadata
- Download URL: aws_snapshot-0.3.0-py3-none-any.whl
- Upload date:
- Size: 132.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86398a3607d17cef91702c61de02c5b9e538eae66606726245fd27e67d72a6f8
|
|
| MD5 |
65800ab22e7fd85bd747056ad238b81b
|
|
| BLAKE2b-256 |
2af924dc1c75ac37bd974fd95d9599df977a6aa8863b9e9e5a1ab7d07a3f6d57
|
Provenance
The following attestation bundles were made for aws_snapshot-0.3.0-py3-none-any.whl:
Publisher:
release.yml on GNS-Science/aws-snapshot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aws_snapshot-0.3.0-py3-none-any.whl -
Subject digest:
86398a3607d17cef91702c61de02c5b9e538eae66606726245fd27e67d72a6f8 - Sigstore transparency entry: 2017403715
- Sigstore integration time:
-
Permalink:
GNS-Science/aws-snapshot@55cfe40767d1345f2afe4fda28e38a8c613aa0af -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/GNS-Science
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@55cfe40767d1345f2afe4fda28e38a8c613aa0af -
Trigger Event:
push
-
Statement type: