secrets/CLAUDE.md
Brian Majewski 09a7bdcc46 chore: persist deploy config in CLAUDE.md
CLI tool with no platform deploy. Released via merge to main +
optional version tags. Recorded so /land-and-deploy skips the
dry-run on subsequent invocations.
2026-05-09 14:30:50 -07:00

72 lines
2.9 KiB
Markdown

# secrets
Encrypted env file sync between machines using `age` key-file encryption + a private git repo.
## Quick Start
```bash
brew install age
./secrets init # Create ~/.secrets repo + generate age key
cd ~/my-project && ./secrets push # Encrypt .env* files, commit, push
# On other machine:
cd ~/my-project && ./secrets pull # Pull + decrypt .env* files
```
## Testing
```bash
brew install bats-core
bats test/secrets.bats
```
## Architecture
Single bash script (`secrets`) with subcommands: init, push, pull, list, rm, rekey.
- Encryption: `age` with key files (not passphrases — age passphrases are non-scriptable)
- Storage: Private git repo at `~/.secrets/`
- Convention: Tracks `.env`, `.env.*`, and `.dev.vars` (not `.envrc`, `.environment-*`)
- Workspaces: `--workspaces` flag reads `package.json` workspaces, requires `jq`
- Safety: Pre-commit hook rejects plaintext secret files
## Project Structure
```
secrets # CLI script (~300 lines bash)
hooks/pre-commit # Pre-commit hook template
test/
secrets.bats # bats-core test suite (25 tests)
test_helper.bash # Shared setup/teardown
README.md # User-facing documentation
CLAUDE.md # This file
```
## Key file
`~/.secrets/key.txt` is the age identity (private key). It is gitignored and must be copied manually to each machine once.
## Multi-store resolution
The active store directory is picked by `resolve_store()` using these rules, highest precedence first:
1. `--store <dir>` flag (parsed in the main pre-pass into `STORE_OVERRIDE`).
2. `.secrets-store` file in cwd or any ancestor, walk-up bounded by `$HOME` (never reads `$HOME/.secrets-store` itself or anything above).
3. `SECRETS_DIR` env var (legacy escape hatch).
4. `~/.secrets` default.
`resolve_store` mutates BOTH `SECRETS_DIR` and `KEY_FILE` so the existing single-store code paths just work. `STORE_SOURCE` reports which rule won. `_LAST_FOUND_AT` (when rule 2 fires) holds the path of the file that was read.
`.secrets-store` parsing is deliberately conservative: first non-empty non-comment line wins, no shell expansion (no `$VAR`, `$()`, backticks). Bare names map via `_expand_store_path`: `work``$HOME/.secrets-work`, `default``$HOME/.secrets`.
## Deploy Configuration
- Platform: NONE (distributed via `git clone` from GitHub)
- Production URL: N/A (no live service)
- Release model: merge to `main` is the release. Optionally tagged with `v<X.Y.Z.W>`.
- Verification after merge: a fresh `git clone` should produce a working `secrets which` against an isolated `$HOME`. No canary URL.
- Staging: none.
- Rollback: revert the merge commit on `main` (and delete the tag) to roll back.
## Environment variable
`SECRETS_DIR` overrides the default `~/.secrets` location (useful for testing). Per-project bindings via `.secrets-store` file beat this env var; use `--store <dir>` for one-shot overrides that beat everything.