Skip to content

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 /alice prefix and the query string), with no rewriting: xuns inside the container already knows it lives under /alice thanks 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.