The CLI
envend is the one thing you install on the machine you work from.
It is a single static binary and it is the whole client: the tunnel, the mount, ssh, and every
command that changes a sandbox. Every command takes a sandbox's name — never a
repository's, because several sandboxes of one repository run at once, which is the point of
them.
Install
cd cli && go build -o envend .
Or take a binary from a release: darwin and linux,
amd64 and arm64, built on an envend-v* tag.
Point it at a gateway
envend login https://gateway.envend.dev --token-stdin
The token is read from standard input, never from argv, so it does not land in
your shell history or in another process's view of the process table. It is written to
~/.envend/config at mode 0600. ENVEND_GATEWAY and
ENVEND_TOKEN override the file when they are set — a config file is a standing
preference and an exported variable is a decision about this shell.
Every command
| Command | What it does |
|---|---|
envend login <gateway> --token-stdin | Save the gateway and your token. |
envend up <profile> [--detach] | Create a sandbox from a profile. Prints the generated name on stdout, then follows the setup hooks on stderr and exits 0 when the workspace is ready, 1 when a hook failed. --detach returns straight after the name. |
envend list | Every sandbox, with state, idle time, what its backend keeps, and whether anything will ever stop it. |
envend status <name> | Three lines: whether your tunnel is running and on which local port, whether /work is mounted and where, and the gateway's own row for that sandbox. |
envend logs <name> [--since N] [--no-follow] | The setup hooks' own output, live, ending when the workspace is ready or failed. --since 0 is the whole disk's history, including boots before this one. |
envend mount <name> | Put its /work at ~/envend/<name>/work. See below. |
envend unmount <name> | Take it away again, and stop the tunnel. |
envend ssh <name> [--host] [-- <cmd>] | A shell, or one command. Without --host it goes through the workspace's exec prefix; with it, to the VM. Everything after -- belongs to the command, so envend ssh dev -- grep --host x sends that --host to grep. |
envend instructions <name> | Print the block a harness needs. envend mount already writes it beside the files. |
envend share <name> <port> | Serve one port on a public HTTPS URL. |
envend unshare <name> <port> | Stop serving it. Instant, and total. |
envend link <name> <path> [--ttl <s>] | A signed, expiring URL for one file under /work. |
envend revoke-links <name> | Invalidate every link ever issued for that sandbox, at once. |
envend hold <name> [--reason <text>] | Keep it awake while a long detached job runs. Holds always expire. |
envend release <name> | Hand it back to the idle sweep. |
envend sleep <name> | Stop it now. The next command wakes it. |
envend destroy <name> | Delete it, and its /work, for good. |
envend tunnel <name> | The mount's tunnel, in the foreground. envend mount runs this for you. |
envend proxy <name> | ssh's ProxyCommand. The ssh config block runs this for you. |
What envend mount actually does
Five things, in one command, because each is useless without the others:
- Looks up the sandbox's tunnel address from the gateway and remembers it.
- Fetches the sandbox's two keys — the ssh private key and the tunnel key — into
~/.envend/<name>/. - Starts a background tunnel listening on a local port the kernel picks.
- Mounts
/workover NFSv3 through that tunnel, at~/envend/<name>/work. - Writes a
Host <name>block into~/.envend/ssh_config, and adds a singleIncludeline to~/.ssh/configif it is not already there — so plainssh <name>andscpwork.
It wakes a sleeping sandbox on the way, and it waits — printing the hook log — if the
workspace is still coming up. The mount host is always 127.0.0.1: nothing dials
the sandbox directly, because it publishes no NFS port, and the tunnel on that local port is
what proves who you are before the file server sees a packet.
macOS mounts without sudo. macOS lets a user mount NFS onto
a directory they own, which matters because a harness cannot answer a password prompt. On
Linux the kernel NFS client needs CAP_SYS_ADMIN, so envend mount
has to run as root; envend does not escalate on your behalf, and says so in one sentence if
it cannot mount.
Two things the mount does not do
- Locks are local only. A lock taken on the mount is enforced against other programs on your machine and is invisible to everybody else. For one person editing their own sandbox this changes nothing; for two people on one share it is a real gap.
- Attribute caching is the kernel's. A file changed in the sandbox over ssh can look stale on the mount for up to sixty seconds.
Waiting on a sandbox from a script
envend up puts the name and the other fact lines on stdout and the hook log on
stderr, so reading one does not mean parsing the other:
name=$(envend up omega | head -1) && envend ssh "$name" -- mix test
Waiting is bounded at twenty minutes and says so, and the log stream is resumable, so the
cost of hitting that bound is one more command. envend ssh --host never waits at
all — it is about the machine rather than about the work, and it has to stay open when the
workspace is what is stuck.
Where things live
~/.envend/config | Gateway URL and token, mode 0600. |
|---|---|
~/.envend/<name>/ | One sandbox's two keys and its running tunnel's port. |
~/.envend/ssh_config | A Host block per mounted sandbox, included from ~/.ssh/config. |
~/envend/<name>/work | The mount point. Under your home rather than /mnt, so creating it needs no privilege. |
~/envend/<name>/ENVEND.md | The instruction block, written beside the files. |