niche
CLI — encrypted environments, per repository
Managing the environment when working on software is hard. It requires tracking both the variables needed by the system and the values those variables must contain for your desired configuration. All this and you want it to be safe, easy, simple, boring.
- Status
- Active
- Stack
- Rust · X25519 · ChaCha20-Poly1305 · mDNS
- License
- MIT
Notes
I made niche to solve the problem for myself, and I hope it helps others as well.
The .env file has been the bane of many onboarding efforts in my career as a tech lead.
Figuring out which variables need to be sent over to a new dev, or onto my own machine, to get things running.
Keeping me up nights worrying that a credential now lives forever in version control.
Of course, I still use it, all the time, it’s easy. A plain file next to the
code, apparently containing a clear list of the configuration needed by the application.
But easy and simple are different things.
Easy is about effort — how close at hand something is. Simple is about entanglement —
whether a thing is one strand or several woven together.
A .env file is easy, and it is entangled.
Managing it means considering your codebase (one git add . from
living there forever), your chat history (how it got to your laptop), and your
application code (the extra code that loads it into the environment).
niche is an attempt at simple environment management: easy enough that I’ll actually reach for it, and simple enough that I can describe it to others and reason about it myself.
Using it
Installing
cargo install --git https://gitlab.com/whytheplatypus/niche.git niche,
and if you haven’t already, add the cargo install directory (usually
$HOME/.cargo/bin) to your path. There’s no account to make and no daemon to
run — the first niche command of any kind, even niche --help, quietly
generates this machine’s X25519 keypair and writes it to
~/.config/niche/identity. niche whoami prints the public half and
a fingerprint.
Each machine is its own identity, easy and simple.
Linking an environment to a repository
From within a repository, run
niche link
This establishes the environment’s identity as the repository’s root commit SHA and nothing else which means cloning the same repo again on the same machine finds the values already there: nothing to transfer or configure.
Adding and removing variables
niche add DATABASE_URL "postgresql://localhost:5432/mydb"
niche list # names only
niche list --show-values # decrypted
niche remove DATABASE_URL
One blob holding every variable would be a small braid of its own: touch one
value and you’ve rewritten them all. So each variable is its own encrypted
file. This makes remove an unlink, and add never touches a value it wasn’t asked
to. The environment’s data key sits
beside them in .key, and it’s local, each machine has its own, and a
transfer re-encrypts for the destination rather than copying it.
list shows names only unless you ask for values. This provides a quick way to see what variables are recorded for the system.
Getting values into something that runs
niche run -- npm start
Nothing touches the filesystem: the environment variables are set only for the child process.
A .env
For a codebase already set up to need a .env file,
niche generate writes a file to /dev/shm and symlinks .env in the project to
it. Now a file sits where the code expects one, readable
by anything that opens it, but the entanglement doesn’t. The bytes are on
tmpfs, never on persistent disk, they’re gone at reboot. The generated file carries a
header marking it as niche’s, which
lets generate refuse to overwrite a .env it didn’t write. --persist writes
a real file and warns on stderr.
Getting values into your shell
eval "$(niche apply)"
eval "$(niche clear)"
A child process can’t reach into its parent shell’s environment, so apply
prints shell statements and your shell runs them itself with eval.
apply also exports NICHE_APPLIED listing the names it set, so clear undoes
exactly what was applied, including variables you’ve since deleted from the
vault.
Named environments
niche add --env staging DATABASE_URL "postgresql://staging.internal/app"
niche envs
export NICHE_ENV=staging
A named environment uses what it sets and inherits everything else from the
default. list tells you which layer each value came from, making any complexity obvious.
Sharing between machines
On the machine that has the values:
$ niche export
Code: 482-916
Verification code: BRAVE-OTTER-4217
On the other, in a clone that has already run niche link:
$ niche import 482-916
ℹ Found the relay for this code at http://192.168.1.43:41207
Verification code: BRAVE-OTTER-4217
Neither machine has to be told where the relay is. export looks for one
announced on the network and otherwise starts its own, on a port the OS picks,
announced over mDNS under the pairing code — so two exports on one network can’t
send the wrong machine to the wrong relay. It withdraws the announcement on the
way out.
Combine --relay and a long-lived niche relay where multicast doesn’t carry.
A transfer carries one layer, not the merged cascade: niche export --env staging
sends only what that layer sets, and the receiving machine’s own default still
shows through underneath.
In practice
My hope is that this maintains the ease that made me reach for a .env file in the first place. A single
binary, no account, no daemon, and the first command you run makes the identity it needs and gets on with it.
Easy isn’t a bad thing. Entanglement is, and making something simple and easy is decidedly hard.
Hopefully I’ve managed that for this problem, but we’ll see what happens as it faces reality.
If you happen to use it I’d love to hear what works and what doesn’t.