Documentation
¶
Overview ¶
Package fedproxy federates multiple tmux-webui instances behind one UI.
One instance acts as the "hub": it lists and proxies sessions from remote instances ("hosts", config.Hosts). A remote session surfaces under a "<host>/<session>" prefix, and every SESSION-SCOPED request is forwarded to the owning host with the prefix stripped and the host's tw_token cookie attached; the response is piped back verbatim. A dead/slow host is skipped fail-soft — the local view always returns.
What proxies (forwarded to the owning host, prefix stripped) ¶
- GET /api/sessions — MERGED: local + each host's list, fanned out concurrently; remote entries carry host=<name> and a "<name>/…" name.
- GET /api/history?session=… — session query param carries the prefix.
- GET /api/panes?session=… — flat pane list; forwarded when the session param targets a host, else the local first-session default.
- GET /api/sessions/{name}/panes — name path segment carries the prefix.
- GET /api/sessions/{name}/windows — window list for a remote session.
- PATCH /api/sessions/{name} — rename.
- DELETE /api/sessions/{name} — kill.
- POST /api/sessions — optional "host" body field routes create.
- /ws?session=<host>/<rest> — DUMB byte pipe to the host's /ws.
What stays LOCAL-ONLY (never federated, always served by this instance) ¶
relay, notify (webhook push), metrics, upload, deck, agents (/api/agents is its OWN status-only federation via config.notify.peers — see monitor.go, and the /api/agents/{session}/{pane}/record|frames I4 recorders which gate on the LOCAL snapshot), plus rescue, autocomplete, link/QR, version, and health. These are machine-local by nature (a metrics snapshot, an upload target, a rescue console for THIS tmux server) and carry no host prefix to route on.
Not to be confused with notify.peers ¶
config.notify.peers is STATUS-ONLY agent federation (each Monitor scan GETs a peer's /api/agents). config.Hosts is the FULL session proxy. They are kept separate on purpose and are not unified here.
Index ¶
- func SplitTarget(s string) (host, session string)
- type Host
- func (h *Host) Down() bool
- func (h *Host) Forward(w http.ResponseWriter, r *http.Request, path string)
- func (h *Host) ForwardBody(w http.ResponseWriter, r *http.Request, path string, body []byte)
- func (h *Host) Name() string
- func (h *Host) ProxyWS(w http.ResponseWriter, r *http.Request, remoteSession string)
- type Registry
- type Session
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func SplitTarget ¶
SplitTarget splits a federated target "<host>/<session>" on the FIRST "/". A target with no "/" has an empty host (a plain local session name). This is the naive split; use Registry.Resolve for authoritative routing, which also requires the prefix to be a CONFIGURED host — tmux permits "/" inside a local session name, so the "/" alone is not proof of a remote target.
Types ¶
type Host ¶
type Host struct {
// contains filtered or unexported fields
}
Host is the hub's client for ONE remote tmux-webui instance. It owns the primary + fallback base URLs, the shared tw_token, and a sticky record of which base last worked so a healthy request path is reused without re-probing. Never copy a Host — the mutex and atomic pointer must stay put; always *Host.
func (*Host) Forward ¶
Forward proxies the incoming request r to this host at path (already prefix-stripped, query included), piping the response back verbatim: status code, Content-Type, and body. The request body is buffered so a fallback retry can replay it. A host that cannot be reached becomes 502 Bad Gateway.
func (*Host) ForwardBody ¶
ForwardBody is Forward with an explicit JSON body — for a handler that already consumed r.Body and rewrote it (e.g. POST /api/sessions strips the "host" field before forwarding the create).
func (*Host) ProxyWS ¶
ProxyWS bridges the browser WebSocket (already routed to this hub) to the host's /ws for remoteSession, piping frames verbatim in BOTH directions until either side closes. It is a DUMB PIPE: each frame is relayed byte-for-byte with its message type, never inspected or re-encoded. On a dial failure the accepted browser socket is closed with a status the frontend treats as a normal drop (it just reconnects).
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the hub's hosts, keyed by name for O(1) resolve and ordered by config position for a deterministic session fan-out.
func NewRegistry ¶
func NewRegistry(hosts []config.HostConfig) *Registry
NewRegistry builds a Registry from validated host configs (config.Load has already enforced non-empty/unique/no-slash/not-"local" names). A nil/empty list yields an empty Registry whose Empty() is true — the caller then keeps every path byte-identical to a standalone server.
func (*Registry) Empty ¶
Empty reports whether any hosts are configured. When true, callers take the unfederated fast path (no fan-out, no proxy checks).
func (*Registry) MergeSessions ¶
MergeSessions returns the local sessions (host="") followed by each host's sessions (host="<name>", name prefixed "<name>/…"), fanned out concurrently under a short shared deadline. A host that errors or times out contributes nothing and is logged only on a reachability change — the local list is never dropped or delayed past sessionFanoutTimeout.
func (*Registry) Resolve ¶
Resolve maps a wire target to its owning host. remote is true ONLY when the target is "<host>/<session>" AND <host> is a configured host name; otherwise remote is false and the caller serves it locally (target may be a local session whose own name contains "/", which is returned intact as session).
type Session ¶
type Session struct {
Name string `json:"name"`
Windows int `json:"windows"`
Attached int `json:"attached"`
Host string `json:"host,omitempty"`
}
Session is the hub's merged session-list entry. It mirrors the local session shape and adds host: empty (omitted) for a local session, "<name>" for a remote one, so the frontend CAN group by machine without parsing the name.