fix(router) fixed routing of different accounts for headroom

This commit is contained in:
nvrl committed 2026-09-23 13:00:19 +02:00
1 parent cc8c764a67
commit aea75fbf1c
9 files changed
+764 -87

No files matched your search

+61 -6
View File
@@ -8,7 +8,8 @@ which of them the `claude` command runs as, and every account gets a Headroom
proxy of its own, reachable under a name that does not change.
```sh
claude-account setup # wrapper, a proxy per account, the router
claude-account setup # wrapper, a proxy per account, the router, completions
claude-account add kunde # another account: profile, port, workspace, proxy
claude-account switch work # `claude` runs as the work account from now on
claude-account status # everything, and everything that is wrong
```
@@ -35,6 +36,12 @@ started deliberately in another profile -- a jail does this -- stays where it
was put. Sessions that are already open are never disturbed by a switch; each
keeps the profile it started with.
One thing beats the wrapper and should not: an `alias claude=...` or a shell
function of that name, which is found before anything in the `PATH` and, where
it sets `CLAUDE_CONFIG_DIR` itself, makes every switch look ignored. `status`
reads the startup files of bash, zsh and fish and names the file and the line,
because nothing else in the report would show why the account never changes.
## A proxy per account
A Headroom proxy keeps what it sees: a cache, a retrieval store and logs, all
@@ -45,12 +52,23 @@ written from one systemd template:
| Address | What |
| --- | --- |
| `headroom.localhost` | the proxy of the account that is active right now |
| `<account>.headroom.localhost` | one account's proxy, active or not |
| `<account>.headroom.localhost` | the proxy of that account, active or not |
| `<jail>.headroom.localhost` | the proxy a jail started for one session |
| `headroom.localhost` | the proxy of the account that is active right now |
| `headroom.localhost/names` | everything that resolves, and `?port=` for one name |
The bare name follows the switch. That is the whole point of it: one address in
a bookmark, always showing the account being worked in.
Every account has its own name, so two accounts in use at the same time --
one in each terminal -- never share an address. The bare domain follows the
switch, for a bookmark that should show whichever account is being worked in,
but nothing prints it: an address printed in one session would open the other
account's proxy after a switch.
A jail called like an account does not get the name; the account keeps it, and
`status` points the jail out with the command that renames it. Programs that
want the name of a port ask `headroom.localhost/names?port=8791`, which answers
with the address as plain text, or 404 where the port has no name. That is how
the `enqs-jail` and `enqs-info` plugins find it, so the rules of who gets which
name live here alone.
Jails keep their own names. They are published as run files by the `enqs-jail`
plugin, and the router here reads the same directory in the same format, so a
@@ -67,12 +85,49 @@ workspaces over unchanged.
| `doctor` | the same, and a non-zero exit when something is wrong |
| `list`, `current`, `dir`, `env`, `url` | the small answers, for scripts |
| `switch <account>` | make an account the one `claude` runs as |
| `setup` | wrapper, proxies and router in one go |
| `add <name>` | register an account: profile, port, workspace, proxy, all at once |
| `remove <account>` | forget one; its profile directory stays |
| `completions [shell]` | print a completion script, or `--install` it |
| `setup` | wrapper, proxies, router and completions in one go |
| `adopt` | take over services that were written by hand |
| `shim install \| uninstall` | the `claude` wrapper |
| `headroom install \| start \| stop \| restart \| port \| logs` | one account's proxy |
| `router serve \| install \| uninstall \| port \| list` | the name router |
## Adding an account
```sh
claude-account add kunde # ~/.claude-kunde, the next free port
claude-account add kunde --dir ~/elsewhere --port 8795
claude-account add kunde --switch # and run as it straight away
```
`add` does everything an account needs rather than half of it: it creates the
profile directory, reserves a port, gives the account a workspace of its own,
writes and starts its proxy service, and points the profile's `settings.json`
at that proxy. A login is the one thing left, because only a browser can do it:
`claude-account switch kunde && claude /login`.
`remove` is the counterpart and undoes the registration alone. The profile
directory and the proxy workspace stay where they are -- they hold a login,
projects and memories -- so a profile that still has credentials keeps showing
up in `list`, found rather than configured.
## Completions
```sh
claude-account completions --install # every shell on this machine
claude-account completions fish # or print one
```
`setup` installs them as well. The subcommands come from the parser, so they
cannot drift; the account names are not written into the file but asked for
when Tab is pressed (`claude-account list --names`), so an account added a
minute ago completes without regenerating anything. Files go to
`~/.config/fish/completions`, `~/.local/share/bash-completion/completions` and
`~/.local/share/zsh/site-functions` -- the last one has to be in the `fpath`
before `compinit`.
## Configuration
`~/.config/claude-account/config.json`. Everything in it has a default derived