* feat: multi-store support via .secrets-store + --store flag Layer four-rule store resolution on top of the existing SECRETS_DIR primitive so users can manage multiple isolated encrypted stores (work vs personal, per-client, etc.) without giving up the tool's small-bash-script pitch. Resolution order (highest first): 1. --store <dir> flag (parsed in main pre-pass) 2. .secrets-store file in cwd or any ancestor up to $HOME 3. SECRETS_DIR env var (legacy escape hatch) 4. ~/.secrets default resolve_store() updates both SECRETS_DIR and KEY_FILE so existing single-store codepaths just work. New cmd_which / where / status report the active store. cmd_init, push, pull, push_workspaces, pull_workspaces, list, rm, rekey, run, which all call resolve_store at entry. Hardening from the EGB-281 adversarial review: - F1: cmd_run EXIT trap is now a named function (not string-interpolated), so paths with apostrophes still get plaintext cleaned up - F2: symlinked .secrets-store files are skipped, never read - F3/F4: --store flag rejects flag-shaped values and empty --store= - F5: HOME unset is detected up-front with a directed error - F11: check_initialized / check_key give context-aware errors that name both recovery paths (git clone vs secrets init) when a teammate clones a project bound to a non-existent store on their machine Tests: 37 → 66 (29 new). HOME=\$TEST_TMPDIR added to test setup so the walk-up logic stays bounded inside fixtures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: add Multiple stores section to README Five subsections walk users through: how store resolution works, how to set up a second store on a machine, how to bind a project, how teammates join a bound project, and how to undo or change a binding. SECRETS_DIR table entry now points readers at the new --store flag and .secrets-store file as the preferred mechanisms. * chore: bump version and changelog (v0.1.0.0) First formal release. EGB-281 adds multi-store support; this commit seeds the VERSION file (4-digit MAJOR.MINOR.PATCH.MICRO) and the CHANGELOG.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
2.5 KiB
Markdown
63 lines
2.5 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`.
|
|
|
|
## 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.
|