Was still pinned to the scaffold's placeholder digest (mesh-route- proxy@sha256:0000...0000) even after the build+context work landed — never caught because nothing had assigned route-proxy before tonight. artifact: server, matching trust's own reference a few lines up and every other built module in the catalogue.
route-proxy — the mesh's public ingress
The shipping form of the reference reverse proxy (novox/hq ADR 0007, novox/hq 08-connectivity §3
"Exposure"). It is the traefik replacement: the module that provides route.
What it is
A route is a grant. A module that must be reachable declares requires: ["route"] and
contributes the name it wants and the port it listens on; this module provides route, receives
every consumer that asked as a file, and forwards by the Host header of each request to
http://<at>:<port> — where at is where the mesh says that consumer's machine is (empty means
this machine, reached over loopback).
The control plane does not write the proxy's configuration. It delivers the facts — the
contributions file at receives.route — and the proxy turns them into whatever it runs. That
boundary is why swapping this proxy for another costs nothing to the modules that publish through
it.
| field | value | why |
|---|---|---|
provides |
route (scope mesh) |
a module on any node may require it |
serves.route |
{} |
a route hands back a name, not a credential — the consumer already knows the name it asked for |
receives.route |
…/routes/mesh.json |
where the mesh writes every contribution; the proxy reads it as $ROUTES and re-reads on change |
listens |
80 and 443, both from: anywhere |
the one machine with a public opening; the firewall opens these for free (ADR 0045) |
No broker account, no own-secrets, no provisioner: the proxy neither mints a credential nor emits
an event. It only reads the file the mesh writes. (Contrast redis, which mints passwords, and
cloudflare-dns, which emits record events.)
How it ships the Go proxy
The proxy is a Go program, unlike the TypeScript tool-runtime modules. The canonical source is
not vendored here — it lives in the mesh-controller repository at examples/route-proxy, the
contract written as something that runs. This module ships only the packaging: a multi-stage
Dockerfile whose build context is the mesh-controller repository root and which
compiles ./examples/route-proxy into mesh-route-proxy. The committed module.json carries the
placeholder digest every mesh-built image does (@sha256:0000…); the mesh pins the real digest at
publish.
In the lab, mesh-lab/scripts/build-route-proxy-image.sh builds this into the local daemon as
mesh-route-proxy:development, and a scenario stocks it and serves it by digest.
Certificates, and the endpoint (resolves 04-ISSUES/004)
TLS is opt-in and terminates here for public names, via ACME (autocert). A proxy with no
TLS_LISTEN serves plain HTTP; with it, it obtains a publicly-trusted certificate for exactly the
names the mesh routes here (HostPolicy = only routed names, so a scan cannot spend the account's
issuance quota) and answers the HTTP-01 challenge on :80 at the name being certified.
ACME_DIRECTORY defaults to Let's Encrypt staging, not production. This closes
novox/hq 04-ISSUES/004 (certificate issuance targets production):
the issuer is a per-node choice, and the safe endpoint is the default. Production issuance is
rate-limited per domain and per account and does not replenish quickly; defaulting to production
would leave the safe path depending on somebody remembering to opt out of it, on exactly the work
most likely to iterate. A staging certificate is trusted by no browser, so the mistake announces
itself on the first request rather than a fortnight later at the rate limit.
A node that serves real public traffic states so, by overriding ACME_DIRECTORY to the
production directory https://acme-v02.api.letsencrypt.org/directory for this module on that node.
It is a node property, and the node facing the public internet is the one that opts in.
| env | default | meaning |
|---|---|---|
ROUTES |
/routes/mesh.json |
the contributions file the mesh writes (receives.route) |
LISTEN |
:80 |
HTTP, and the ACME HTTP-01 challenge |
TLS_LISTEN |
:443 |
HTTPS; unset to serve plain HTTP only |
ACME_CACHE |
/acme |
where issued certificates persist; required when TLS_LISTEN is set, so a restart does not re-order |
ACME_DIRECTORY |
Let's Encrypt staging | the issuer; a public-serving node overrides it to production |
ACME_CA_BUNDLE |
(unset) | a file of roots to trust for the issuer's own API — set only for a private/lab authority whose API certificate the world does not yet trust |