Wrap ragenix's required flags in just recipes - #113
Merged
Conversation
`ragenix -e secrets/personal/foo.age` fails from anywhere in this repo, for two unrelated reasons that both look like something worse than they are: - Run from the repo root it dies with "./secrets.nix does not exist!" — `--rules` defaults to ./secrets.nix and ours lives in secrets/. - Run from secrets/ it dies with "No usable identity or identities" — the CLI hunts for SSH keys in ~/.ssh, which the dev container doesn't have. `age.identityPaths` in home-manager/modules/git.nix is consumed by the agenix *module* at activation time; the ragenix binary never reads it. So every edit needs two flags supplied by hand, and omitting -i reports a missing key rather than a missing flag — which sends you looking for a lost identity instead of re-reading the command. Bake both into a recipe. - `_age-identity` resolves ~/.age/personal-key.txt (overridable via $RAGENIX_IDENTITY), and when it's absent prints the `docker cp` recovery line the container entrypoint already suggests. Split out rather than inlined because rekeying needs the same answer. - `edit-secret` accepts the path as typed from either the repo root or from inside secrets/, since `just` runs recipes from the justfile directory and the two spellings are equally natural. Also terminates the file with a newline, which it was missing.
`ragenix --rekey` needs `--rules` and `-i` exactly as editing does, and fails the same misleading way without them. It's the rarer operation — recipient list changes, key rotation — which is precisely when you won't have the invocation in shell history, so it's the one more worth wrapping. Reuses `_age-identity`, so the key location and its recovery hint stay in one place. Verified on the real secrets: rekeying all four preserves the armored encoding secrets.nix depends on (binary blobs don't reliably survive the trip into a commit), and every result still decrypts with the same identity. The test churn was reverted — ciphertext changes on every rekey, so committing it would be noise.
Content-only change: the recipient list in secrets/secrets.nix is untouched, so this is an edit rather than a rekey. Verified before committing that it still decrypts with the personal identity and that the armored encoding survived — secrets.nix notes that binary blobs don't reliably make it into a commit, and this file is now the first one edited through `just edit-secret`. What changed inside is deliberately not described here. The overlay exists so that layer is only ever ciphertext in this repo; restating it in a commit message would defeat that.
Contributor
⊕ Entity-level changesjustfile
secrets/personal/agent-instructions.age
Summary: 6 added, 2 deleted across 2 files functions and classes, not lines · sem |
alycda
marked this pull request as ready for review
August 22, 2026 19:19
`--editor` looks optional in `ragenix --help` because it advertises `[env:
EDITOR=...]`, but clap treats it as a required argument that merely *defaults*
from the environment. With EDITOR unset the recipe dies on argument parsing:
error: the following required arguments were not provided:
--editor <EDITOR>
That's the same trap the recipe was written to close — it reads as a broken
recipe rather than an unset variable. It shows up wherever the session env is
thin: a non-interactive shell, or the failed-activation state docker/CLAUDE.md
describes, where the prompt works because dotfiles linked but the packages and
their env never arrived.
Pass it explicitly and default to hx, matching the $EDITOR the home-manager
profile sets anyway.
just's interpolation is raw text substitution into the recipe body, not
argument passing, so the path escaped its own quotes:
just edit-secret 'x.age"; echo INJECTED; :"' # printed INJECTED
Self-inflicted in a personal justfile rather than a real attack surface, but a
path with a space in it broke for the same reason, and `quote()` fixes both.
While here, honour the claim the comment above it makes. Stripping only a
literal leading `secrets/` meant the two forms a shell completion actually
produces — `./secrets/personal/x.age` and the absolute path — both got a
second `secrets/` glued on and died in ragenix's rules lookup. Strip `./` and
a `$PWD/` prefix first.
Deliberately still no check that the file exists: `ragenix -e` on a
non-existent path is how a new secret is created, so ragenix's rules lookup
stays the gate — it also rejects a file that exists but was never listed in
secrets.nix, which an existence check wouldn't.
`_age-identity` reported the path it actually looked at, then unconditionally told you to put a key at ~/.age/personal-key.txt. With $RAGENIX_IDENTITY set to a moved or mistyped path, following that instruction verbatim leaves the recipe failing identically, because the override still wins. Name the variable instead when it's the one in play. The docker cp hint is still right for the default case, which is the common one.
alycda
added a commit
that referenced
this pull request
Aug 22, 2026
Edited on top of main's current ciphertext, after merging #113 in — the copy on this branch was still the pre-#113 blob, and editing that would have silently reverted the overlay update that landed with it. Recipients in secrets/secrets.nix are unchanged, so this is an edit, not a rekey. Verified before committing that it still decrypts with the personal identity and kept its armored encoding. Contents deliberately not described: the overlay exists so that layer is only ever ciphertext in this repo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ragenix -e secrets/personal/foo.agefails from anywhere in this repo, in two different ways that both look worse than they are:./secrets.nix does not exist!—--rulesdefaults to./secrets.nix, and ours lives insecrets/.secrets/:No usable identity or identities— the CLI hunts for SSH keys in~/.ssh, which the dev container doesn't have.age.identityPathsinhome-manager/modules/git.nixis read by the agenix module at activation time; theragenixbinary never sees it.So both flags have to be supplied by hand on every invocation, and omitting
-ireports a missing key rather than a missing flag — which sends you hunting for a lost identity instead of re-reading the command.What this adds
just edit-secret personal/git-config.age # or secrets/personal/git-config.age just rekey-secrets_age-identityresolves~/.age/personal-key.txt(override with$RAGENIX_IDENTITY) and, when it's missing, prints thedocker cprecovery line the container entrypoint already suggests — or names the override, when that's what's pointing at nothing.edit-secrettakes the path in any of the four forms you'd plausibly type or tab-complete: bare (personal/x.age), repo-relative (secrets/personal/x.age),./-prefixed, or absolute.rekey-secretswraps--rekey, which needs the same two flags. It's the rarer operation — recipient changes, key rotation — which is exactly when you won't have the invocation in shell history.Also carries a content update to the private agent-instruction overlay (
secrets/personal/agent-instructions.age), which is whatedit-secretgot used for first.Review round
Three follow-up commits, one per finding, all in error paths rather than the happy path:
--editoris required, not optional. It advertises[env: EDITOR=...]in--help, but clap treats it as a required argument that merely defaults from the environment — so an unsetEDITORdied on argument parsing with a usage dump. That's the same class of confusion the recipe exists to remove, and it fires wherever the session env is thin (non-interactive shells, or the failed-activation statedocker/CLAUDE.mddescribes). Now passed explicitly, defaulting tohx.just edit-secret 'x.age"; echo INJECTED; :"'executed the injected command — and any path with a space broke for the same reason. Fixed withquote(). Stripping only a literal leadingsecrets/also meant the./and absolute forms got a secondsecrets/glued on; both are handled now.$RAGENIX_IDENTITY. It reported the path it actually looked at, then told you to place a key at the default path — useless when your own override is the broken one.There's deliberately still no check that the target file exists:
ragenix -eon a non-existent path is how a new secret gets created, so ragenix's rules lookup stays the gate. It also rejects a file that exists but was never listed insecrets.nix, which an existence check wouldn't.Testing
secrets/: ragenix reports "wasn't changed, skipping re-encryption" and the tree stays clean.EDITORunset now gets past argument parsing and launches the editor._age-identitywith the right hint for each case (default vs. override) and a non-zero exit.rekey-secretsagainst all four real secrets: the armored encodingsecrets.nixdepends on is preserved, and every result still decrypts. That churn was reverted — rekeying rewrites ciphertext every run, so committing it would be noise.Worth knowing
rekey-secretsrewrites all four secrets even when the recipient list hasn't changed, so its diff can't be reviewed by inspection — only by decrypting. That's inherent toragenix --rekey, not something the recipe introduces.