(motir-core) The Fly certificates adapter — request, check and remove a hostname's certificate on the motir-marketing app, behind a port beside flyMachines
Opened by Zhu Yue ·
The one module in motir-core that asks Fly for a customer's certificate — behind a port, with a dependency guard, reading its token at call time. The shape is lib/orchestrator/adapters/fly/flyMachines.ts's, copied deliberately: that file is "the only module in the repository that speaks Fly" for the fleet, and this is its twin for certificates on a DIFFERENT app with a DIFFERENT token.
What ships
- The port —
lib/publicAddresses/certificateProvider.ts:interface CertificateProvider { request(hostname): Promise<CertificateState>; check(hostname): Promise<CertificateState>; remove(hostname): Promise<void> }and the normalisedCertificateState—{ hostname, configured: boolean, issued: boolean, dnsRequirements: { type: 'A' | 'AAAA' | 'CNAME' | 'TXT', name, value }[], checkedAt }. The lifecycle service depends on the port only. - The adapter —
lib/publicAddresses/adapters/fly/flyCertificates.ts:fetchagainst Fly's certificates resource on the app named by config —POST /v1/apps/{app}/certificates/acmewith{ hostname }to request,POST /v1/apps/{app}/certificates/{hostname}/checkto read state anddns_requirements, and the documented remove call. Verify each path and response field againsthttps://fly.io/docs/networking/custom-domain-api/at build time and quote the doc's field names in the mapping's comments — the planner read the resource's names (dns_requirements,configured), not its full schema, so the mapping is written from the doc, not from this card. - Config at CALL time, never module load (the
appAuth.tscontractflyMachines.tscites):FLY_CERTS_TOKENandFLY_CERTS_APP(the ADR names them), a typedCertificateProviderNotConfiguredErrorwhen either is unset — a self-hosted build never reaches this path and must not crash on boot. No fallback to any other Fly token: the token is scoped to themotir-marketingapp and to nothing else, for the same reasonFLY_FLEET_API_TOKENis its own variable. - Typed errors, one per outcome the service must branch on: not configured · Fly refused (4xx with the body's message) · Fly unavailable (5xx / network /
ORCHESTRATOR_REQUEST_TIMEOUT_MS-style timeout) · hostname unknown on remove. - The dependency guard — a test in the shape of
tests/ciFleet/orchestratorPortBoundary.test.tsasserting thatapi.machines.devand the Fly paths appear in this adapter file and nowhere else outsidelib/orchestrator/adapters/fly/. - Egress: confirm
lib/legal/egress-manifest.jsonalready recordsapi.machines.devas an egress host (it lists it for the fleet); add this caller to the entry's evidence if the manifest is per-caller — read the file before asserting either. - Unit tests over a fake
fetch: every port method's happy path, every typed error, the not-configured branch, and that no method is called at import.
Boundary
No persistence and no scheduling — the store holds state, the lifecycle decides when to call, the status job polls. The wildcard for the base domain is NOT requested here — a human ran fly certs add for it (the domain card); this adapter is per-customer-hostname only.
Acceptance criteria
CertificateProviderandCertificateStateare exported fromlib/publicAddresses/certificateProvider.ts, andflyCertificates.tsis the only implementation; no file underlib/services/imports the adapter directly.- Requesting, checking and removing a hostname each issue exactly the documented Fly call, with the token as
Authorization: Bearerand the app fromFLY_CERTS_APP; the mapping's comments quote the doc's field names beside each mapped field. - With
FLY_CERTS_TOKENorFLY_CERTS_APPunset, every method throwsCertificateProviderNotConfiguredErrorand importing the module throws nothing. - Each of the four typed errors has a unit test, and a request that exceeds the timeout resolves to the unavailable error rather than hanging.
- A dependency-guard test fails when a Fly host or path string appears in any file outside the two Fly adapter directories.
- No file outside
motir-coreis touched.
Context refs
- the decision — Q5 (Fly per hostname, on the marketing app) and the variable names
motir-core/lib/orchestrator/adapters/fly/flyMachines.ts— the boundary shape, the call-time config accessor, the token rule;lib/orchestrator/errors.ts— the typed-error vocabulary to mirrormotir-core/tests/ciFleet/orchestratorPortBoundary.test.ts— the dependency guard to copymotir-core/lib/legal/egress-manifest.json— theapi.machines.deventry- Fly —
https://fly.io/docs/networking/custom-domain-api/(the certificates resource) ·https://fly.io/docs/networking/custom-domain/
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.