xunx · Multi-user shared service¶
xunx uses one image to maintain a long-lived container per user, then forwards requests by URL prefix through a single gateway process. Users see http://gateway:18960/alice/chat/, while the container still runs xuns --base-path /alice internally.
xunx user-add alice
xunx serve --host 0.0.0.0 --port 18960
Concepts¶
| Concept | Description |
|---|---|
| User store | SQLite database at {XUN_HOME}/x/xunx.db, fields name / token / generation |
| Token | one random token per user (secrets.token_urlsafe(24)), which also serves as xuns --token for their container |
| URL prefix | fixed to /{username}, so the container generates the correct relative links |
| Container name | xunx-<first 8 chars of instance ID>-<username>; the instance ID is a hash of the user store path |
| Port | a free port picked at random within --port-range, bound to 127.0.0.1 only (host port = container port) |
| generation | incremented by every upgrade; during reconciliation a stale generation triggers a container rebuild |
| reconcile | runs once every --interval seconds: clean up out-of-range containers, adopt existing ones, start missing ones, remove orphaned containers |
Subcommands¶
xunx user-add alice
User names may contain only letters, digits, _ and -; adding a duplicate raises an error. The output is a table of users and tokens, where the token is the access password.
xunx user-del alice # delete the user; their container is cleaned up on the next reconciliation
xunx user-list # list USER / TOKEN / BASE PATH
xunx upgrade alice # or xunx upgrade --all
This only increments the generation in the database and prints "Queued upgrade"; the actual rebuild happens on the next serve reconciliation, and the data inside the container (workspace, saved sessions) is discarded with it.
| Parameter | Default | Description |
|---|---|---|
--host |
0.0.0.0 |
gateway listen address |
--port |
18960 |
gateway port (excluded from the available port pool) |
--port-range |
17960-18959 |
port range available to containers, format START-END |
--image |
xun |
image to use |
--env |
empty | same meaning as xunc --env: NAME=VALUE assignment or wildcard forwarding; XUN_*/_XUN_* are always forwarded |
--interval |
5 |
reconciliation interval in seconds (hidden in the help output), must be greater than 0 |
How requests are forwarded¶
flowchart LR
U["browser<br/>/alice/chat/"] --> G["xunx gateway<br/>:18960"]
G --> R["user store<br/>names and tokens"]
G --> C["xuns in container<br/>--base-path /alice"]
C -.->|"relative links keep the /alice prefix"| G
- Paths are forwarded verbatim (including the
/aliceprefix and the query string), with no rewriting:xunsinside the container already knows it lives under/alicethanks to--base-path. - Unknown user name →
404 User not found; upstream unavailable →502 Upstream unavailable. - WebSockets (the event stream) are supported, forwarded by two pumps with a 20 second heartbeat; hop-by-hop headers (
connection,transfer-encoding,upgrade, etc.) are stripped in both directions. - Each user's URL is printed by that user's own container, in the form
http://<gateway>:18960/alice/chat/?session=/&token=<token>.
Persistence and reconciliation¶
Restarting serve does not rebuild containers: during reconciliation it first adopts containers that are already running, matched by container name (reading their published port and labels), and rebuilds only when the token or generation is stale; containers belonging to deleted users, containers that have exited, or containers that do not belong to this instance are stopped/cleaned up (the first reconciliation additionally removes orphaned containers left behind by this instance). Container logs are forwarded to serve's stderr, prefixed with the container name.
xunx user-list
xunx upgrade --all # after an image update, have all users rebuilt with the new image on the next reconciliation
Publishing it externally
When the gateway listens on 0.0.0.0, the token is the only credential; in production, put it behind an HTTPS reverse proxy and pass through X-Forwarded-Proto so that the login cookie gets Secure.