v0.1.0.0 feat: multi-store support (EGB-281) (#1)
* 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>
This commit is contained in:
parent
edb1614941
commit
7e6ddf3a12
7 changed files with 812 additions and 11 deletions
91
README.md
91
README.md
|
|
@ -223,6 +223,95 @@ Stopping the dev server (or any failing command) ends the process; the `EXIT` tr
|
|||
|
||||
If you prefer to keep decrypted files on disk for a long editing session, use `secrets pull` and `secrets clear` manually instead.
|
||||
|
||||
### Multiple stores
|
||||
|
||||
By default, all your encrypted secrets live in one store at `~/.secrets/`. That works great if you have one set of secrets shared across machines. If you want **separate stores** — for example, work secrets isolated from personal projects, or one store per client — `secrets` supports that without any special setup.
|
||||
|
||||
A "store" is just a directory with its own `.git` repo, age key, and remote. You can have as many as you want.
|
||||
|
||||
#### How a store gets picked
|
||||
|
||||
When you run `secrets push` or `secrets pull`, the tool resolves the active store using the first matching rule (highest precedence first):
|
||||
|
||||
```
|
||||
1. --store <dir> flag passed on the command line
|
||||
2. .secrets-store file in the current directory or any ancestor up to $HOME
|
||||
3. SECRETS_DIR environment variable (legacy escape hatch)
|
||||
4. ~/.secrets default
|
||||
```
|
||||
|
||||
Run `secrets which` from any project directory to see which rule won and which store is active. Aliases `secrets where` and `secrets status` do the same thing.
|
||||
|
||||
#### Set up a second store on this machine
|
||||
|
||||
```bash
|
||||
# Create a fresh store at ~/.secrets-work with its own age key
|
||||
secrets --store work init
|
||||
|
||||
# Connect it to a separate private GitHub repo
|
||||
cd ~/.secrets-work
|
||||
git remote add origin git@github.com:<you>/work-secrets.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
The bare name `work` expands to `$HOME/.secrets-work`. Use `secrets --store /any/abs/path init` if you want a custom location.
|
||||
|
||||
#### Bind a project to a non-default store
|
||||
|
||||
In any project directory, write a `.secrets-store` file with the store's name (or path) and commit it:
|
||||
|
||||
```bash
|
||||
cd ~/myapp
|
||||
echo work > .secrets-store
|
||||
git add .secrets-store
|
||||
git commit -m "use work secrets store"
|
||||
```
|
||||
|
||||
After that, every `secrets push` / `secrets pull` from this project (or any subdirectory) automatically uses `~/.secrets-work`. Teammates who clone the project get the same binding for free — the file is in the repo.
|
||||
|
||||
When you push or pull from a non-default store, `secrets` echoes which one is active so you can spot mistakes immediately:
|
||||
|
||||
```
|
||||
==> Pushing secrets for project: myapp
|
||||
==> Store: /Users/you/.secrets-work (from .secrets-store file (~/myapp/.secrets-store))
|
||||
```
|
||||
|
||||
#### Joining a teammate's bound project
|
||||
|
||||
If you clone a project that has a committed `.secrets-store: work` file but you don't have `~/.secrets-work` set up locally, `secrets pull` will tell you exactly what to do:
|
||||
|
||||
```
|
||||
ERROR: Store not initialized: /Users/you/.secrets-work
|
||||
Resolved from: .secrets-store file (~/myapp/.secrets-store)
|
||||
This path doesn't exist on this machine yet.
|
||||
|
||||
If you're joining a teammate's existing store:
|
||||
git clone <their-store-remote> /Users/you/.secrets-work
|
||||
# then copy their key.txt to /Users/you/.secrets-work/key.txt
|
||||
|
||||
If you want a fresh new store at this path:
|
||||
secrets --store /Users/you/.secrets-work init
|
||||
```
|
||||
|
||||
You'll need two things from the teammate who set it up:
|
||||
|
||||
1. **The git remote URL** of the work-secrets repo — clone it to `~/.secrets-work` (or wherever the `.secrets-store` file resolves to on your machine).
|
||||
2. **The age key file** (`key.txt`) — same as standing up any new machine. AirDrop, scp, or USB.
|
||||
|
||||
Once both are in place, `secrets pull` works.
|
||||
|
||||
#### Undo or change a binding
|
||||
|
||||
```bash
|
||||
# Stop using a non-default store for this project
|
||||
rm .secrets-store
|
||||
git commit -am "go back to default secrets store"
|
||||
|
||||
# Or change which store the project is bound to
|
||||
echo personal > .secrets-store
|
||||
git commit -am "switch to personal secrets"
|
||||
```
|
||||
|
||||
### Monorepo support
|
||||
|
||||
For projects with multiple packages (monorepos using `package.json` workspaces), add the `-w` flag to operate on all workspaces at once:
|
||||
|
|
@ -273,7 +362,7 @@ For complete rotation with no historical exposure, create a fresh `~/.secrets/`
|
|||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `SECRETS_DIR` | `~/.secrets` | Override the secrets store location |
|
||||
| `SECRETS_DIR` | `~/.secrets` | Override the secrets store location (legacy; prefer `--store` or a `.secrets-store` file — see "Multiple stores" above) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue