tunnel

package
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Named-tunnel mode: a persistent Cloudflare *named* tunnel bound to a user-supplied hostname (e.g. sub.example.com), as opposed to the ephemeral quick tunnel in tunnel.go.

Why it exists: a quick tunnel's `*.trycloudflare.com` URL is random and dies on any cloudflared restart; a named tunnel keeps a fixed hostname the user configures once. The hostname is NEVER hardcoded — the host UI collects it and passes it to StartNamed.

Cloudflare state (login cert + tunnel credentials) lives under <dataDir>/cloudflared so each host (pro/terminal/teamworkbench) keeps an independent account — never the global ~/.cloudflared. All cloudflared invocations get TUNNEL_ORIGIN_CERT pointed at that per-dataDir cert.

Transport is forced to http2 (--protocol http2): the quick tunnel's QUIC/UDP path to the edge proved unreliable in the field ("no recent network activity" timeouts); http2 over TCP is the robust fallback and connected cleanly where QUIC did not.

Package tunnel manages a Cloudflare quick-tunnel (cloudflared) process: it lazily downloads the cloudflared binary, starts/stops a quick tunnel exposing an arbitrary local addr, and reports download progress + the public URL. It is generic infrastructure — exposing "any local server" to the internet — so it lives in the shared kit (github.com/brightman-ai/kit), consumed equally by deepwork-terminal and deepwork-pro. Neither owns it; the SSOT is here.

Lifecycle: the tunnel is DECOUPLED from the host server's process lifecycle. cloudflared is launched detached (own session via Setsid, stderr → a log file rather than a parent-owned pipe), so restarting/rebuilding the host server does NOT kill the tunnel and does NOT change the public URL. State (url+pid+localAddr) is persisted to <dataDir>/tunnel.json; on construction or Start the manager adopts an already-running cloudflared (matching pid alive + localAddr) instead of spawning a duplicate. Per-dataDir state keeps terminal and pro independent. Only an explicit Stop() (or a dead pid) tears the tunnel down.

Index

Constants

View Source
const SuperviseInterval = 15 * time.Second

SuperviseInterval is how often the watchdog probes liveness.

Variables

This section is empty.

Functions

This section is empty.

Types

type State

type State struct {
	Running         bool   `json:"running"`
	PublicURL       string `json:"publicURL,omitempty"`
	Downloading     bool   `json:"downloading"`
	DownloadedBytes int64  `json:"downloadedBytes,omitempty"`
	TotalBytes      int64  `json:"totalBytes,omitempty"` // -1 if unknown
	DownloadURL     string `json:"downloadURL,omitempty"`
	BinPath         string `json:"binPath,omitempty"`

	// Named-tunnel fields (empty/zero for a quick tunnel).
	Mode       string `json:"mode"`                 // "quick" | "named" | "" (off)
	Hostname   string `json:"hostname,omitempty"`   // named-mode target hostname
	Ready      int    `json:"ready,omitempty"`      // named: registered edge connections (0 = not up)
	Account    bool   `json:"account"`              // a Cloudflare cert.pem exists (logged in)
	LoginState string `json:"loginState,omitempty"` // "" | "pending" | "done" | "error"
	LoginURL   string `json:"loginURL,omitempty"`   // Cloudflare auth URL to display during login
}

State is the complete observable state of a Tunnel. Safe to read at any time — never blocks on a download in progress.

type Tunnel

type Tunnel struct {
	// contains filtered or unexported fields
}

Tunnel manages a cloudflared quick-tunnel process.

Locking discipline:

startMu — held for the full duration of Start/Stop; prevents concurrent starts.
          NOT held during status reads.
stateMu — RWMutex guarding running/publicURL/cmd/downloadURL.
          Never held while doing I/O (download, process wait).
downloadedBytes / totalBytes / downloading — atomic; zero-lock reads.

func New

func New(dataDir string) *Tunnel

New creates a tunnel manager. dataDir is where cloudflared is cached AND where the tunnel state/log live. If a previously-started tunnel is still alive (its PID responds), New adopts it so Status()/IsRunning() report the running tunnel immediately — without the host server having to re-Start it. A stale record (dead PID) is cleaned up.

func (*Tunnel) IsRunning

func (t *Tunnel) IsRunning() bool

IsRunning returns whether the tunnel is active (liveness-checked).

func (*Tunnel) Login added in v0.10.0

func (t *Tunnel) Login(ctx context.Context) error

Login runs `cloudflared tunnel login` (detached) and surfaces the Cloudflare authorization URL via Status().LoginURL / LoginState. It returns as soon as the URL is available (or an error); a background watcher flips LoginState to "done" once the user authorizes and cert.pem lands. Idempotent: a no-op ("done") if already logged in, and coalesces a concurrent pending login.

func (*Tunnel) PublicURL

func (t *Tunnel) PublicURL() string

PublicURL returns the current public tunnel URL.

func (*Tunnel) SetLogf added in v0.15.0

func (t *Tunnel) SetLogf(f func(format string, v ...any))

SetLogf installs a log sink for supervisor events (restart attempts, failures, recovery).

func (*Tunnel) Start

func (t *Tunnel) Start(ctx context.Context, localAddr string) (string, error)

Start ensures the cloudflared binary is present, then returns a quick-tunnel URL, adopting an already-running detached cloudflared when possible instead of spawning a duplicate. Resolution order:

  1. In-memory running tunnel that is alive and forwards to localAddr → reuse.
  2. Persisted tunnel that is alive and forwards to localAddr → adopt (covers a fresh host process that didn't adopt at New, or a tunnel started by a prior host).
  3. Otherwise spawn a NEW detached cloudflared, persist it, and return its URL.

A persisted tunnel that is dead (stale) or points at a different localAddr is cleaned up (and killed, if it points elsewhere) before spawning. Concurrent calls are serialized.

func (*Tunnel) StartNamed added in v0.10.0

func (t *Tunnel) StartNamed(ctx context.Context, hostname, localAddr string) (string, error)

StartNamed brings up a named tunnel for hostname, forwarding the edge to localAddr. It ensures the cloudflared tunnel exists (create if needed), (re)points the hostname's DNS at it, and runs it detached over http2. Returns https://<hostname> once an edge connection registers. Reuses / adopts an already-running named tunnel for the same hostname+addr. Requires a prior Login.

func (*Tunnel) Status

func (t *Tunnel) Status() State

Status returns a consistent snapshot of tunnel state without blocking. Running is liveness-checked: an adopted/spawned tunnel whose PID has since died is reported as not-running (a cheap signal-0 probe), so the UI never shows a dead URL.

func (*Tunnel) Stop

func (t *Tunnel) Stop()

Stop kills the tunnel process (spawned this run OR adopted/persisted) and clears all state. The tunnel only stops on an explicit Stop — never implicitly when the host server exits.

func (*Tunnel) Supervise added in v0.15.0

func (t *Tunnel) Supervise(ctx context.Context)

Supervise runs the self-healing watchdog until ctx is cancelled. It probes tunnel liveness every SuperviseInterval and, when the user has an enabled tunnel whose cloudflared is gone, restarts it (exponential backoff on repeated failure, reset on success). It never starts a tunnel the user never enabled, and never resurrects one the user Stopped — both are "no intent".

Call it once, in a goroutine, from the host server: go tun.Supervise(ctx).

func (*Tunnel) SuperviseEvery added in v0.15.0

func (t *Tunnel) SuperviseEvery(ctx context.Context, interval time.Duration)

SuperviseEvery is Supervise with an explicit probe interval (tests use a short one).

Jump to

Keyboard shortcuts

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