fedproxy

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 16 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func SplitTarget

func SplitTarget(s string) (host, session string)

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) Down

func (h *Host) Down() bool

Down reports whether the host is inside its negative-cache window.

func (*Host) Forward

func (h *Host) Forward(w http.ResponseWriter, r *http.Request, path string)

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

func (h *Host) ForwardBody(w http.ResponseWriter, r *http.Request, path string, body []byte)

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) Name

func (h *Host) Name() string

Name returns the host's configured name (its session prefix).

func (*Host) ProxyWS

func (h *Host) ProxyWS(w http.ResponseWriter, r *http.Request, remoteSession string)

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

func (r *Registry) Empty() bool

Empty reports whether any hosts are configured. When true, callers take the unfederated fast path (no fan-out, no proxy checks).

func (*Registry) Host

func (r *Registry) Host(name string) (*Host, bool)

Host returns the configured host by name.

func (*Registry) MergeSessions

func (r *Registry) MergeSessions(ctx context.Context, local []tmuxctl.Session) []Session

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

func (r *Registry) Resolve(target string) (h *Host, session string, remote bool)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL