tunnel

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package tunnel runs the developer's cloudflared for orb dev (ADR-0086): a quick tunnel (a random *.trycloudflare.com URL, no account) or a named tunnel (the developer's hostname, with the token from .env), exposing the app, and only the app, on a public HTTPS address.

orb dev owns the cloudflared process: it starts in a process group of its own and is stopped with its group when orb dev stops, when the Dev Portal asks, or before a restart. The token travels to cloudflared in its environment (TUNNEL_TOKEN), never on its command line, and is removed from every line of cloudflared's output orb keeps or prints; no status or error this package returns contains it.

Index

Constants

View Source
const (
	// BinaryVar names the cloudflared program to run instead of the one on
	// PATH.
	BinaryVar = "ORB_CLOUDFLARED"
	// TokenVar holds a named tunnel's token (a secret).
	TokenVar = "CLOUDFLARE_TUNNEL_TOKEN" //nolint:gosec // the variable's name, not a credential
	// TokenFileVar names a file holding the token instead.
	TokenFileVar = TokenVar + "_FILE"
	// HostnameVar is a named tunnel's public hostname.
	HostnameVar = "ORB_TUNNEL_HOSTNAME"
)

Environment variables the tunnel reads, from orb dev's environment or the app's .env (the environment wins).

View Source
const (
	CodeCloudflaredMissing = "cloudflared_missing"
	CodeNotDevelopment     = "not_development"
	CodeTokenMissing       = "tunnel_token_missing"
	CodeTokenInvalid       = "tunnel_token_invalid"
	CodeHostnameMissing    = "tunnel_hostname_missing"
	CodeHostnameInvalid    = "tunnel_hostname_invalid"
	CodeInvalidMode        = "invalid_tunnel_mode"
	CodeNoTarget           = "no_app_address"
	CodeNotConnected       = "tunnel_not_connected"
)

Codes of Error.

View Source
const ProcessFile = ".orb/portal/cloudflared.json"

ProcessFile records the cloudflared this orb dev is running, inside the app directory (git ignores .orb). It exists only while cloudflared runs: orb writes it when the process starts and removes it when the process ends, so a file left behind names a process an orb dev that was killed never got to stop. It never holds the token, which travels to cloudflared in its environment and never on its command line.

View Source
const SettingsFile = ".orb/portal/tunnel.json"

SettingsFile keeps the hostname entered in the Dev Portal, inside the app directory (git ignores .orb). It never holds the token.

Variables

This section is empty.

Functions

func CheckToken

func CheckToken(token string) error

CheckToken reports whether token looks like a Cloudflare tunnel token: base64 of a JSON object with the tunnel's ID ("t") and secret ("s"). The error never includes the token.

func InstallText

func InstallText() string

InstallText is InstallHelp for a terminal, for the current OS first.

func LoadToken

func LoadToken(env []string) (token, source string, err error)

LoadToken reads a named tunnel's token from env: CLOUDFLARE_TUNNEL_TOKEN or the file CLOUDFLARE_TUNNEL_TOKEN_FILE names. It returns the token and the variable it came from. Errors never include the token.

func NormalizeHostname

func NormalizeHostname(raw string) (string, error)

NormalizeHostname turns what a developer types (api.example.com, https://api.example.com/) into a hostname, or refuses it: it must be a DNS name with at least two labels, not an IP address, localhost or a quick tunnel's name.

func ParseQuickURL

func ParseQuickURL(line string) (string, bool)

ParseQuickURL finds the public URL of a quick tunnel in one line of cloudflared's output: https://<name>.trycloudflare.com, wherever it is on the line (cloudflared draws it inside a box). It depends on nothing else in the line's wording. The API host cloudflared asks for the tunnel, api.trycloudflare.com, is not a tunnel URL. The URL is returned in lower case, without a path.

func Version

func Version(ctx context.Context, path string) string

Version runs cloudflared --version and returns its first line, or "".

Types

type Binary

type Binary struct {
	Found bool `json:"found"`
	// Path is the program; FromEnv reports it came from ORB_CLOUDFLARED.
	Path    string `json:"path,omitempty"`
	FromEnv bool   `json:"from_env"`
	// Version is the first line of cloudflared --version.
	Version string `json:"version,omitempty"`
	// Problem says why it wasn't found.
	Problem string `json:"problem,omitempty"`
}

Binary describes the cloudflared orb would run.

func FindBinary

func FindBinary(env []string, lookPath func(string) (string, error), version func(path string) string) Binary

FindBinary looks for cloudflared: ORB_CLOUDFLARED when set (a path or a program on PATH), else cloudflared on PATH. It never downloads anything. version, when not nil, reports the version of the program found.

type Callback

type Callback struct {
	// Provider is google, apple, github or resend.
	Provider string `json:"provider"`
	// Label names the address, such as "Authorized redirect URI".
	Label string `json:"label"`
	// URL is the full address; Path the app's route it reaches.
	URL    string `json:"url"`
	Method string `json:"method"`
	Path   string `json:"path"`
	// Where is where to paste it in the provider's console.
	Where string `json:"where"`
	// Configured reports that the app's environment sets up the provider
	// (such as GOOGLE_CLIENT_ID).
	Configured bool `json:"configured"`
}

Callback is an address to register with a provider for the tunnel's hostname.

type Config

type Config struct {
	// Dir is the app directory (for SettingsFile and cloudflared's working
	// directory).
	Dir string
	// Env returns orb dev's view of the environment: the process
	// environment and .env, the environment winning. It is read on every
	// start, for APP_ENV, the token, the hostname and ORB_CLOUDFLARED.
	Env func() []string
	// Target returns the app's address as reachable from this machine,
	// such as http://127.0.0.1:8080.
	Target func() (string, error)
	// OnChange receives the status after every change; it must not block.
	OnChange func(Status)
	// Logf prints orb's own messages about the tunnel; nil discards them.
	Logf func(format string, args ...any)
	// LookPath finds programs; nil uses exec.LookPath.
	LookPath func(string) (string, error)
	// ProcessEnv is the environment cloudflared inherits (without any
	// tunnel token variables); nil uses os.Environ.
	ProcessEnv func() []string
	// StartTimeout bounds how long cloudflared may take to connect (and a
	// quick tunnel to report its URL); zero means 45 seconds.
	StartTimeout time.Duration
	// StopTimeout is how long cloudflared gets to exit after SIGTERM
	// before its group is killed; zero means 5 seconds.
	StopTimeout time.Duration
	// Client makes reachability checks; nil uses a client with a 10 second
	// timeout that resolves names with Go's own DNS client, so a lookup
	// made before a new quick tunnel's name existed isn't answered from the
	// system's negative cache. Redirects are never followed.
	Client *http.Client
	// CheckAttempts and CheckInterval shape the check after connecting,
	// which waits an interval first and retries while DNS and Cloudflare
	// catch up; zero means 12 and 5 seconds, and negative attempts turn the
	// automatic check off.
	CheckAttempts int
	CheckInterval time.Duration
}

Config configures a Manager.

type EnvChange

type EnvChange struct {
	Key string `json:"key"`
	// Current is the value .env (or the environment) has, "" when unset.
	Current string `json:"current"`
	// Proposed is the value the tunnel needs.
	Proposed string `json:"proposed"`
	Reason   string `json:"reason"`
	// Optional changes are proposed but not needed for sign-in to work
	// through the tunnel; the portal leaves them unticked.
	Optional bool `json:"optional"`
}

EnvChange is one proposed change to .env.

type Error

type Error struct {
	Code   string
	Detail string
}

Error is a refusal to start a tunnel, with a stable code the portal answers with.

func (*Error) Error

func (e *Error) Error() string

type Info

type Info struct {
	Status      Status        `json:"status"`
	Cloudflared Binary        `json:"cloudflared"`
	Install     []InstallStep `json:"install"`
	// OS is this machine's operating system, to pick install steps.
	OS string `json:"os"`
	// Hostname is a named tunnel's hostname as configured (normalized when
	// valid) and HostnameSource where it comes from: ORB_TUNNEL_HOSTNAME,
	// the portal's saved setting, or "".
	Hostname       string `json:"hostname,omitempty"`
	HostnameSource string `json:"hostname_source,omitempty"`
	// TokenSource is the variable a named tunnel's token comes from, or ""
	// when none is set; TokenProblem says what is wrong with it. The token
	// itself is never included.
	TokenSource  string `json:"token_source,omitempty"`
	TokenProblem string `json:"token_problem,omitempty"`
	// AppEnv is APP_ENV as orb dev runs the app; Allowed reports a tunnel
	// may start (development only).
	AppEnv  string `json:"app_env"`
	Allowed bool   `json:"allowed"`
	// Target is where a tunnel forwards: the app's address.
	Target string `json:"target,omitempty"`
}

Info is everything the portal's Tunnel screen shows besides the setup.

type InstallStep

type InstallStep struct {
	// OS is darwin, linux or windows (as Go names them).
	OS      string `json:"os"`
	Label   string `json:"label"`
	Command string `json:"command,omitempty"`
	URL     string `json:"url,omitempty"`
}

InstallStep is one way to install cloudflared.

func InstallHelp

func InstallHelp() []InstallStep

InstallHelp lists how to install cloudflared on each operating system; orb never installs it.

type LogLine

type LogLine struct {
	Time time.Time `json:"time"`
	// Level is cloudflared's: DBG, INF, WRN, ERR, FTL, or "".
	Level string `json:"level,omitempty"`
	Text  string `json:"text"`
}

LogLine is one line of cloudflared's output, with secrets removed.

type Manager

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

Manager starts, stops and watches cloudflared. It is safe for concurrent use.

func New

func New(cfg Config) *Manager

New returns a manager; nothing runs until Start.

func (*Manager) Check

func (m *Manager) Check(ctx context.Context) (Reachability, error)

Check requests the public URL's /livez and compares the answer with the app's own, so a hostname pointing at something else isn't reported as working. It refuses when no tunnel is connected.

func (*Manager) Close

func (m *Manager) Close()

Close stops the tunnel for good, for a stopping orb dev.

func (*Manager) Info

func (m *Manager) Info(ctx context.Context, goos string) Info

Info describes cloudflared, the configuration and the status.

func (*Manager) Restart

func (m *Manager) Restart(ctx context.Context) error

Restart starts the tunnel again with the options of the last start.

func (*Manager) SaveHostname

func (m *Manager) SaveHostname(raw string) (string, error)

SaveHostname validates and saves a named tunnel's hostname for later starts, and returns it normalized.

func (*Manager) Start

func (m *Manager) Start(ctx context.Context, opts StartOptions) error

Start starts a tunnel, stopping a running one first. It returns once cloudflared runs; its URL and connection arrive as status changes. It refuses, with an *Error, when cloudflared is missing, APP_ENV isn't development, the app's address is unknown, or a named tunnel lacks its hostname or a valid token.

func (*Manager) Status

func (m *Manager) Status() Status

Status returns the tunnel's status.

func (*Manager) Stop

func (m *Manager) Stop() error

Stop stops the tunnel and waits for cloudflared's group to exit.

func (*Manager) TargetChanged

func (m *Manager) TargetChanged(ctx context.Context)

TargetChanged tells the manager the app may listen elsewhere now. A running quick tunnel pointing at the old address restarts; a named tunnel's target is set in the Cloudflare dashboard, so orb says where it must point instead.

type Mode

type Mode string

Mode is how the tunnel gets its public address.

const (
	// ModeQuick is a quick tunnel: a random trycloudflare.com URL that
	// changes on every start, without a Cloudflare account.
	ModeQuick Mode = "quick"
	// ModeNamed is a tunnel created in the Cloudflare dashboard, with the
	// developer's hostname and token.
	ModeNamed Mode = "named"
)

Modes.

func ParseMode

func ParseMode(s string) (Mode, error)

ParseMode returns the mode named s.

type Reachability

type Reachability struct {
	URL       string    `json:"url"`
	OK        bool      `json:"ok"`
	Status    int       `json:"status,omitempty"`
	Detail    string    `json:"detail"`
	CheckedAt time.Time `json:"checked_at"`
	LatencyMS float64   `json:"latency_ms"`
}

Reachability is the result of requesting the public URL's /livez.

type Route

type Route struct {
	Method string
	Path   string
}

Route is a route of the app, as the portal lists them.

type Setup

type Setup struct {
	PublicURL string `json:"public_url"`
	Hostname  string `json:"hostname"`
	Stable    bool   `json:"stable"`
	// Changes are the .env changes for sign-in, passkeys and client
	// addresses through the tunnel; Set holds the required ones as the env
	// editor's PUT body takes them.
	Changes []EnvChange       `json:"changes"`
	Set     map[string]string `json:"set"`
	// Callbacks are the addresses to register with providers.
	Callbacks []Callback `json:"callbacks"`
	// RoutesKnown is false when the addresses are the defaults because the
	// app's routes couldn't be listed.
	RoutesKnown bool `json:"routes_known"`
	// Warnings say what to watch out for, such as a quick tunnel's URL
	// changing on the next run.
	Warnings []string `json:"warnings"`
}

Setup is the Tunnel screen's configuration help for a running tunnel.

func BuildSetup

func BuildSetup(in SetupInput) (Setup, bool)

BuildSetup proposes the .env changes and lists the provider addresses for a tunnel's public URL. It returns false when the tunnel has no URL yet.

type SetupInput

type SetupInput struct {
	// Status is the tunnel's; Setup needs its PublicURL.
	Status Status
	// Env is the environment as the app would read it (.env with the
	// process environment winning).
	Env []string
	// AppPort is the port the app listens on.
	AppPort string
	// Routes are the app's routes; RoutesKnown is false when they couldn't
	// be listed, and the provider addresses are then the defaults.
	Routes      []Route
	RoutesKnown bool
}

SetupInput is what Setup needs.

type StartOptions

type StartOptions struct {
	Mode Mode `json:"mode"`
	// Hostname is a named tunnel's public hostname; empty uses
	// ORB_TUNNEL_HOSTNAME, then the one saved from the portal. A hostname
	// given here is saved for the next run unless ORB_TUNNEL_HOSTNAME is set.
	Hostname string `json:"hostname,omitempty"`
}

StartOptions choose the tunnel to start.

type State

type State string

State is what the tunnel is doing.

const (
	StateOff       State = "off"
	StateStarting  State = "starting"
	StateConnected State = "connected"
	StateStopping  State = "stopping"
	StateFailed    State = "failed"
)

States.

type Status

type Status struct {
	State State `json:"state"`
	Mode  Mode  `json:"mode,omitempty"`
	// PublicURL is https://<hostname>: a quick tunnel's once cloudflared
	// reports it, a named tunnel's from the start.
	PublicURL string `json:"public_url,omitempty"`
	Hostname  string `json:"hostname,omitempty"`
	// Stable reports a URL that stays the same across runs (named
	// tunnels): only such a URL suits sign-in callbacks and passkeys.
	Stable bool `json:"stable"`
	// Target is the app's address cloudflared forwards to (a quick
	// tunnel's --url; for a named tunnel, where its public hostname
	// should point in the dashboard).
	Target      string     `json:"target,omitempty"`
	PID         int        `json:"pid,omitempty"`
	StartedAt   *time.Time `json:"started_at,omitempty"`
	ConnectedAt *time.Time `json:"connected_at,omitempty"`
	// Problem says why the tunnel failed, or what needs attention.
	Problem string `json:"problem,omitempty"`
	// Restarts counts starts after the first in this orb dev run.
	Restarts int `json:"restarts"`
	// Check is the last reachability check.
	Check *Reachability `json:"check,omitempty"`
	// Log holds cloudflared's most recent lines, oldest first.
	Log []LogLine `json:"log"`
}

Status describes the tunnel.

Jump to

Keyboard shortcuts

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