Contexts¶
Contexts are tw's kubectl-style profiles: each one is a complete identity — role, keys, relay, users — and tw config use-context switches between them. One machine can be a client of two different relays, or a relay admin and a server, without the identities ever mixing.
Listing Contexts¶
CURRENT NAME ID ROLE USER RELAY
* relay-example 3f2a9c1e relay relay.example.com
alice 81d0b44f client alice relay.example.com
backup-server c07e55aa server relay.backup.net
- CURRENT —
*marks the active context. - ID — the short ID: the first 8 hex characters of the context's Xray UUID (dashes stripped). Empty until the context is configured.
- ROLE / USER / RELAY — the context's mode, its username (client contexts), and its relay domain.
tw config current-context # print just the active context's name
tw config view # print the active context's config.yaml
tw config view --as-json # same config as indented JSON (for scripts/jq)
IDs as Selectors¶
Everywhere a command takes a <name|id>, the short ID works too:
Switching, Creating, Renaming, Deleting¶
tw config use-context <name|id> # switch (re-seals the current context, reconnects)
tw config new-context <name> # create a fresh empty context and switch to it
tw config rename-context <old|id> <new>
tw config delete-context <name|id>
new-context preserves the current context — it is sealed to disk and stays in the list, ready to switch back to. A typical use is joining a second relay from an already-configured server: tw join always stores its result as a new context (auto-named from the relay host or username, or pass --name to choose), so joining a second relay doesn't disturb the first:
One Person, All Roles¶
Running relay admin and server (or client) as one person on one machine is a first-class setup — each role simply lives in its own context. To make the relay admin's own machine a server, don't do the join-file handshake with yourself; from the relay context run:
tw relay add-server # creates context server-<relay label>, e.g. server-relay2
tw config use-context server-relay2
tw server start
One command enrolls a freshly generated server identity on the relay and stores it as a new, mode-signed context (--switch also activates it). The relay context is untouched, and the new context gets its own dashboard/API ports so both daemons can run side by side. See Tenant Management.
Deleting the last context is a full reset
If you delete the only remaining (active) context, tw removes all configuration from the machine — identity, keys, relay data — after an explicit confirmation. It refuses if the tw service is running (stop it first with tw service stop).
A running service doesn't follow the switch
A tw service keeps serving the config it loaded at startup. After tw config use-context, both the switch command and tw status warn if the running service still serves the old context — restart it to apply (tw service stop && tw service start; Windows: Restart-Service tw).
Bundles: Export & Import¶
A context travels as a single portable file, tw_<name>.twctx (dots/colons/slashes in the name become dashes). Bundles carry no passphrase — importing never prompts.
Bundles are unprotected
A .twctx file is the identity: whoever holds it can use it. Treat it like a private key and transfer it only over a trusted channel.
Export¶
tw config export # export the active context → tw_<name>.twctx
tw config export <name|id> # export a stored context
The relay's bundle is also written automatically at the end of tw relay create — it is the relay's only backup (CA keypair, relay SSH key, metadata). There is no recovery if it is lost.
Import¶
tw config import <bundle.twctx>
tw config import <bundle.twctx> --activate # switch to it immediately (applies its mode)
tw config import <bundle.twctx> --name work # store under a custom name
tw config import <bundle.twctx> --force # replace an existing same-name context without prompting
If a context of the same name already exists, tw asks before replacing it. Declining keeps the existing context but the command exits non-zero, so a script can tell nothing was imported; when stdin is closed (a non-interactive caller) the collision is an error outright — pass --force to replace without asking. Re-importing a bundle for the active context refreshes the live profile in place.
Default context names¶
When --name is omitted, the name is derived from the bundle:
| Bundle role | Default name |
|---|---|
| client | the username (e.g. alice) |
| relay | relay-<first label of domain> (e.g. relay-example) |
| server | the relay domain (sanitized) |
Names are sanitized to lowercase alphanumerics and dashes.
Issuing Client Identities: tw server user invite¶
Server operators no longer hand out client identities as files. On the server:
This mints a one-time code carrying alice's port mappings, and blocks waiting for her to redeem it. She runs, on her own machine:
Both sides show a short authentication string; she reads hers aloud, the server operator approves on an exact match, and her SSH key (generated locally — it never transits) plus a signed CSR complete the exchange. The result lands as a ready, mode-signed client context on her machine — activated immediately if it's a fresh install, otherwise tw config use-context <name> to switch to it. See Users and Tenants — enrolling a server for the full invite mechanics (the client and server flows share the same code+SAS design).
Tab Completion¶
With zsh completion loaded, context selectors complete dynamically: tw config use-context <TAB> offers stored context names and short IDs, annotated with role, user, and relay. Completion reads only local state — it never dials the relay.