On this page
Web hosting
Stado takes a Node web product from its repository to a public hostname: the release pipeline builds it on a fleet host, the registry runs it as a managed unit with its environment delivered from Skarbiec, and the selected public connection publishes it. DNS, TLS and forwarding belong to the declared edge; no tunnel vendor is a requirement of the release or object client.
This page describes the product declarations, supported edge adapters, diagnostics and command sequence.
What the fleet already had
Three pieces existed before web hosting and are reused unchanged.
The release pipeline. .wisent-release.json in a product repository
declares platforms, quality gates, a build command, and the files to stage —
the recipe is stado-rs/src/release_pipeline/contract/recipe.rs and the
platforms map is stado-rs/src/release_pipeline/contract/manifest.rs.
stado release submit snapshots the
source, picks a builder whose registry release_platform matches the recipe's
runner_platform, runs the gates and the build there, publishes the staged
bytes into a channel, and promotes them. Platform keys are free names; only
runner_platform is constrained, to darwin-arm64 or linux-amd64.
The service registry. A unit is a ServiceDeclaration — an immutable
source (artifact, sha256) and a run spec (program, args, env) — held
in the canonical registry beside its verification descriptor and its consumers
(stado-rs/src/declaration.rs, stado-rs/src/service_resolution/).
stado service deploy renders it to a launchd plist or a systemd user unit and
bootstraps it; stado service secret sync puts one field of one Skarbiec item
into one environment variable in the unit's runtime env file, over the host
channel, never on a command line; stado service grant sync reconciles the
unit's Skarbiec consumer grant.
The database plane. stado database resolve <db> --consumer <consumer>
answers with the Skarbiec item that carries the credential, and refuses a
consumer the declaration does not list
(stado-rs/src/cli/database/reads.rs). The value never leaves Skarbiec
through this path; the item name does.
Public connection selection
A public hostname needs a reachable endpoint and a certificate for that name. The deployment chooses how to supply them. A current host address, tunnel process, provider login or DNS answer does not make that choice for the product.
stado web declare selects the web product's edge. The implemented adapters
are stado and cloudflare. A Stado edge can be an existing host recorded by
stado web edge declare; provisioning a new Azure host is one optional way
to obtain that host, not a prerequisite for release downloads. The Cloudflare
adapter uses the declared tunnel and its credential.
Moving a zone into Cloudflare
The Cloudflare adapter can publish only a hostname in a zone Cloudflare's
nameservers serve. stado dns delegate moves a zone the registrar serves to
the DNS host --provider names, and stado dns undelegate hands it back:
stado dns delegate example.com --provider cloudflare --credential <registrar item> --api-credential <Cloudflare item>
stado dns undelegate example.com --provider cloudflare --credential <registrar item> --api-credential <Cloudflare item>
--provider is required. cloudflare is the one DNS host with an adapter;
any other value is refused by the parser with exit 2 and the list of hosts.
undelegate refuses, changing nothing, while the host serves a record the
registrar's host list does not hold, and names those records.
delegate reads every record from Namecheap, creates the zone in the Cloudflare
account (or takes the one already there), writes each record Cloudflare does
not already hold, and only then points the registrar's nameservers at the
ones Cloudflare assigned and reads them back. MX records are copied like any
other, so mail keeps working. Namecheap's URL redirect records have no
Cloudflare equivalent; a zone holding one is refused before anything is
written, with <zone> was not moved: Cloudflare has no record type for URL <host> <target>, …. A Cloudflare write that fails stops the move with how
many records were already written, and the nameservers stay where they were.
The Cloudflare token is the Skarbiec item --api-credential names, field
api_key, and the registrar's is the item --credential names; no item is
built in, and a call without either is refused by the parser with exit 2.
Weles can acquire the Cloudflare token: its registered secret
cloudflare.api_token signs in to https://dash.cloudflare.com with the
account's console login, creates a token with Zone: Edit, DNS: Edit and
Cloudflare Tunnel: Edit, and writes it straight into an item, never into its
task result. The account is the one the token reaches: Cloudflare's
/accounts must answer exactly one, otherwise the move is refused with the token in <item> reaches <n> Cloudflare accounts; it must reach exactly one.
For edge: "stado", web_api.edge must name target, address and contact.
stado web status --json preserves a missing or invalid declaration as
edge-unconfigured, includes the exact edge_error, and exits non-zero.
It never treats an unknown expected address as proof that a hostname is
published. stado web edge status reads the same declaration.
The object and release HTTP origin is selected separately with STADO_API_URL
or api.url. Any connection that serves the documented HTTP contract can
carry it. stado repair stado --step object-api repairs only the local object
service; it does not run a tunnel program or replace public ingress.
Publication still has to read every written object through the selected
public origin and compare the bytes.
Desktop's Services → Web hosting shows the same web status report,
including edge_error, the source endpoint, complete output and exit code.
What was added
stado web — the product-level capability. It owns the shape of a web
product: which release artifact it runs, on which host and port, under which
Skarbiec consumer, with which environment, behind which hostname.
stado dns — the registrar. Namecheap's setHosts call replaces a whole
zone, so a record cannot be changed without re-sending every other record in
it. stado dns reads the zone with namecheap.domains.dns.getHosts, merges
one record, and writes the whole zone back, so the merge lives in Stado and
every product's DNS goes through one command. The credential is the Skarbiec
item --credential names (api_user, api_key, username, client_ip);
stado web route and stado web remove use the item the edge declaration
names in web_api.edge.registrar_credential
(stado web edge declare --registrar-credential <item>).
stado web build — the build a Node web product's release runs. Twenty-four
landing sites and ten applications do not need thirty-four build scripts, so the
recipe in .wisent-release.json calls one Stado command and the manifest stays
declarative.
stado web edge — the edge host: provision it, declare it, run the
reverse proxy as a role of that host's one Stado process, and reconcile the
set of hostnames it terminates.
The .wisent-release.json shape for a web product
What a web product's .wisent-release.json declares, a static site as a web product, and a web platform beside a binary platform: Web hosting: the release declaration.
The hostname and DNS model
How hostnames and DNS records are declared and served, a redirect that is a product with no unit, a hostname in front of an existing service, and a path under another product's hostname: Web hosting: hostnames and DNS.
Scheduled requests
A web product that a hosting platform used to call on a cron (a trading check every minute, a nightly snapshot) declares those calls beside its unit. Each one is a path and method on the unit, a 5-field cron, the IANA time zone it is read in, and optionally the header that proves the caller:
stado web schedule set app check-exits \
--path /api/cron/check-exits --method GET \
--cron '* * * * 1-5' --tz America/New_York \
--secret-header x-cron-secret --secret cron-secret#value
stado web schedule set app snapshot \
--path /api/cron/snapshot --method POST --cron '11 * * * *' --tz UTC \
--secret-header authorization --secret-scheme Bearer --secret cron-secret#value
stado web schedule list app
stado web schedule remove app snapshot
The declaration is web_api.products.<name>.schedules.<schedule> with
path, method, cron, tz and an optional secret of header, scheme
and value (a role#field reference). stado web declare keeps a product's
schedules when it rewrites the rest of the declaration.
Declaring a schedule sends nothing. stado web route creates the fleet
schedules after it has proved that the hostname answers from this fleet, so a
product still served by another platform keeps being called there until its
cutover and is never called twice. Each declared schedule becomes one
stado schedule with the id sch-web.<product>.<schedule>.<8 hex>, pinned
to the product's host, with overlap policy skip. Its job sends the request
to the unit's loopback port with curl; the secret reaches the job as
WEB_SCHEDULE_SECRET through its secret environment and reaches curl on
standard input, never in an argument list. An answer of 400 or more fails the
job with the body the route returned, which stado job watch shows. A route
pass keeps a schedule that already matches, replaces one whose cron, time
zone, command, host or secret changed, and deletes one no longer declared; the
route report lists each as unchanged, created, replaced or deleted.
stado web remove deletes all of the product's schedules before it retires
the unit.
stado web schedule list [<product>] [--json] prints each declared schedule
beside the fleet schedule sending it: its id, enabled or paused, next run and
last job, or not sent yet for a product that has not been routed.
Refusals, all from the configuration parser, leave the file unchanged: a
schedule on a redirect or on a hostname in front of an existing service
(runs no unit for a schedule to call); a path that does not start with /
or holds a quote, backslash, backtick, $ or whitespace; a method that is not
upper-case letters, digits and dashes; a header or scheme that is not letters,
digits and dashes; an invalid cron; a time zone that is not IANA; a secret
value that is not role#field; and any field the record does not have.
stado web schedule remove of a schedule the product does not declare is
refused with web product <name> declares no schedule "<schedule>". A secret
role the fleet's job grant does not list is refused by the route pass with
--secret-env reference <role>#<field> is not in agent.skarbiec.secret_fields.
Desktop's Services → Web hosting has a Scheduled requests section that
runs the same web schedule list, set and remove through the selected
endpoint's POST /api/operator/run: each declared schedule with its fleet
schedule or Not sent yet, a form for declaring or changing one, a
Withdraw button per row, and the command's exit code and complete output.
A refusal is shown as Stado wrote it.
The command sequence
The commands that take a web product from declaration to a served hostname, selecting an existing edge, and the edge that runs inside com.wisent.stado: Web hosting: the command sequence.
Source: this website