relaydeploy

package
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package relaydeploy is the one implementation of "put flue's relay into a Cloudflare account": the Worker module, the web bundle, the Durable Object migration, the secret, the workers.dev host. Two callers share it — `flue relay setup`/`update` in cmd/flue, and the daemon's /api/relay endpoints behind the Remote screen — and sharing is the point: a UI deploy and a CLI deploy that drifted apart would leave users running different relays depending on which button they pressed.

The package deliberately does not read config, prompt, or print. Callers own the token's arrival, the account choice, relay.json and every word shown to a human; what lives here is only the Cloudflare choreography and the constants that define the deploy's shape. Those constants have twins in relay/wrangler.jsonc, which is how the same Worker runs under `pnpm dev` and the vitest pool — the two must agree or a developer and a user end up running different relays.

Index

Constants

View Source
const (
	// DefaultWorker is the script name `flue relay setup` deploys under when
	// --worker says nothing else. The script name is the unit of separation
	// between relays in one account: a dev relay under another name has its
	// own hostname, secret and hubs, and cannot touch this one.
	DefaultWorker     = "flue-relay"
	CompatibilityDate = "2026-08-01"
	DOClass           = "DaemonHub"
	DOBinding         = "HUB"
	AssetsBinding     = "ASSETS"

	// DirectoryClass and DirectoryBinding are the fleet directory: one Durable
	// Object for the whole relay rather than one per machine, because one relay
	// is one fleet (spec/fleet-trust.md, "The fleet directory"). Without the
	// binding the Worker answers /directory with 503 by design
	// (relay/src/index.ts) — a relay that serves every session and no
	// directory — which is exactly what `flue relay setup` deployed between
	// the Worker landing and this line.
	DirectoryClass   = "FleetDirectory"
	DirectoryBinding = "DIRECTORY"

	// SecretName is the Worker secret the daemon authenticates its outbound
	// leg with (relay/src/index.ts, authorizeDaemon).
	SecretName = "DAEMON_SECRET"

	// VersionVar is the plain-text binding the deploy stamps the deploying
	// flue's version into. The Worker reports it on /api/health, which is what
	// lets a daemon (and the Remote screen's "update relay" state) see that a
	// deployed relay is older than the binary looking at it.
	VersionVar = "FLUE_VERSION"

	// RateLimitBinding is the Cloudflare rate-limiting binding the Worker
	// checks on its credential-less routes (/client/*, POST /api/pair/*),
	// keyed by connecting IP (relay/src/index.ts, allowRate). The numbers are
	// the spec's "order of 100/min per IP" (spec/fleet-trust.md, "Rate
	// rule"): 300 requests per 60 s per IP per Cloudflare location is far
	// past a fleet of tabs — even a whole office reconnecting through one NAT
	// — while a quota-burning flood, or the 2^32 id-tag guessing walk, needs
	// a botnet's worth of addresses to get anywhere. All three values have
	// twins in relay/wrangler.jsonc (`ratelimits`); edit both or neither.
	RateLimitBinding = "CLIENT_RATE"

	// StepTimeout bounds one ordinary API call; DeployTimeout bounds the
	// deploy itself, which uploads the whole web bundle over whatever link the
	// user has. Each step gets its own deadline rather than the whole flow
	// sharing one, because callers put prompts between steps: a clock started
	// before an account question would be counting while the user reads it.
	StepTimeout   = time.Minute
	DeployTimeout = 10 * time.Minute
)

Variables

DOBindings is the Durable Object binding table every deploy sends: the name the Worker reads the namespace off `env` under, and the class behind it.

A package-level var rather than a literal inside Deploy so that the drift guard in deploy_test.go can compare *what is sent* against `durable_objects.bindings` in relay/wrangler.jsonc, rather than against a second hand-typed copy that would agree with the file and disagree with the deploy. Twinned with that file; edit both or neither.

View Source
var Migrations = []cloudflare.Migration{
	{Tag: "v1", NewSQLiteClasses: []string{DOClass}},
	{Tag: "v2", NewSQLiteClasses: []string{DirectoryClass}},
}

Migrations is the Durable Object migration history this relay carries, and the twin of `migrations` in relay/wrangler.jsonc — tags included, because the tag is what Cloudflare records against the deployed script and a wrangler deploy and a `flue relay setup` of the same Worker have to agree on it.

v2 is its own step rather than an extra class on v1, and that is not a style choice: v1 is already applied on every relay deployed before this build, and a migration's contents cannot be edited after the fact — Cloudflare records that the script is at v1 and would apply nothing further. A new class needs a new tag. The whole list is sent on every deploy and the client applies only the part the account has not reached (internal/cloudflare, pendingMigrations), so a fresh account runs both steps and an existing relay runs only v2.

View Source
var RunWorkerFirst = []string{"/daemon", "/daemon/*", "/client", "/client/*", "/api/*", "/directory", "/directory/*"}

RunWorkerFirst are the paths the Worker handles itself rather than letting the asset router answer from the bundle: the two WebSocket legs, the pairing API and the fleet directory. The bare entries matter alongside the globs — "/daemon/*" alone would let the asset router answer a bare /daemon with the SPA before the Worker's "no such machine" could, and on /directory the bare path *is* the route while the starred form is the Worker's own 404.

Twinned with `assets.run_worker_first` in relay/wrangler.jsonc; edit both or neither.

Functions

func Deploy

func Deploy(in Input) error

Deploy uploads the Worker and the web bundle, and changes nothing else: no secret is minted or bound (the script upload preserves the existing secret_text binding — keptBindingTypes in internal/cloudflare), and no subdomain is touched. It is the whole of `flue relay update`, and the first half of Provision.

func Provision

func Provision(in Input) (host, secret string, err error)

Provision is a first deploy: Deploy, then the workers.dev host, then a fresh secret bound to the Worker. It returns the host the relay answers on and the secret the fleet shares — the caller writes relay.json and prints the join line, because both of those are its business, not Cloudflare's.

The secret is fresh on every call, never reused: Provision is also the recovery path for a leaked or half-configured relay, and one that preserved the old secret would be unable to rotate the one credential the relay has.

func ValidWorkerName

func ValidWorkerName(name string) error

ValidWorkerName refuses a name that cannot be a Cloudflare script name reachable on workers.dev: the script name becomes the hostname's first label, so the grammar is lowercase letters, digits and inner dashes, at most 63 characters. Callers wrap the error with where the name came from (a --worker flag, a form field).

Types

type Input

type Input struct {
	// API carries the user's token. It is used for the calls below and goes
	// no further; nothing in this package stores or logs it.
	API       *cloudflare.Client
	AccountID string
	Worker    string

	// Module and Assets are the embedded relay Worker and web bundle. Callers
	// check them before asking a human for a credential — a dev build carries
	// neither, and that refusal belongs before the token prompt.
	Module []byte
	Assets []cloudflare.Asset
	// AssetHeaders is the `_headers` document the relay serves its assets
	// with. It is an input, not a constant here, because its content names
	// the daemon package's CSP and this package must not import the daemon.
	AssetHeaders string

	// Version is stamped into the Worker as VersionVar. "dev" is an honest
	// value for a from-source build.
	Version string

	// OnStep, when set, hears one line per completed step — "worker deployed:
	// x" — in the order they happen. Callers own the formatting.
	OnStep func(line string)

	// OnNote, when set, hears the conditions a deploy can detect and cannot
	// fix. Separate from OnStep because it is not progress and should not be
	// dressed as it: a caller printing "  ✓ %s" for every step would put a tick
	// on a warning. Unset falls back to OnStep, so a caller that has not
	// thought about it still shows the line rather than swallowing it.
	OnNote func(line string)
}

Input is one deploy's worth of decisions, all made by the caller.

Jump to

Keyboard shortcuts

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