createSidecarProxy arrived with the wallet but nothing else moved onto it, so five sidecars
still carried their own copy of the same two files: a sidecar-server.ts that remembered a
port announced as `<name>:server`, and a router.ts that forwarded the subpath. Byte for
byte identical once the app name was normalised away — which is exactly what the factory's
own header said it existed to end.
headscale, transmission, invoiceshelf, slskd and music are now wallet-shaped: create the
proxy, export the router and the URL getter. 386 lines deleted against 163 added, and the
five feature directories go from ~70 lines each to ~18.
Two deviations were real and moved INTO the factory rather than being dropped, because both
are HTTP concerns rather than app knowledge:
- Range and If-None-Match are now forwarded for every sidecar. music needed both (seeking,
and ETag revalidation returning a cheap 304 instead of a cover image) and slskd needed
Range. Forwarding them everywhere costs nothing and removes the reason to hand-roll.
- timeoutSeconds, used only by music at 1800. A from-scratch reindex holds the proxied
connection open for minutes with no bytes flowing, which the 60s idle timeout would drop.
It applies to the whole prefix — the proxy must not know which of a sidecar's routes are
slow.
The five side-effect imports in hono.ts are gone with them: the port listener now registers
when createSidecarProxy runs inside the router this file already imports. Vault keeps its
hand-rolled pair and its side-effect import — it is off-limits by standing instruction, and
is the one sidecar this commit deliberately does not touch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
officer-headscale owns the whole Headscale contract: the registered servers and
their admin api keys, the >=0.29 version floor, and every multi-call composition
the ui needs. the platform side is auth+forward only and holds no headscale
credentials, so the existing /api/vpn/enroll route and its HEADSCALE_* env vars
are untouched and unrelated.
officer manages many servers rather than one. the owner registers each with a url
and a key generated on that server and switches between them; exactly one is
active, enforced by a partial unique index rather than by convention. keys are
encrypted at rest and never leave the sidecar — the list projection cannot return
one. registration validates before it saves: an unauthenticated GET /version to
prove something headscale-shaped is there and meets the floor, then an
authenticated call to prove the key works. an edit that moves either half
re-validates.
there is deliberately no transparent /api/v1/* passthrough. headscale serialises
every uint64 as a json string and its rest shape moved repeatedly below 0.29;
proxying raw would push all of that into the browser, which is the mistake the
soulseek panels made with 37 raw upstream calls.
the /headscale workspace is nav + view over the panel system. only the servers
section is implemented — nodes, users and pre-auth keys say so plainly rather
than rendering an empty table that reads as a failed fetch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>