Three facts the client cannot produce alone, kept deliberately separate: mtu.pmtud_down (largest datagram that arrives unfragmented — meaningful only because the server sets DF), mtu.frag_delivery (whether larger ones arrive once fragmentation is allowed), and train.udp_downstream (loss, reordering and arrival spacing in the download direction, which a round trip cannot separate from upstream loss). ServerMeasurement now runs them on the same ProbeSession as the echo train. It had to: a fresh session restarts client-side sequence numbers and the server's anti-replay window discards the lot, so the re-primed source is never recorded and every granted send goes to a socket that has already closed. That produced four confidently-wrong FAILED tests and a RED verdict on a healthy network. Live against fmr: path MTU 1500, fragments to 4000, 100/100 downstream, GREEN. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3.6 KiB
Echolot website (web/)
Minimal single-page site for echo-lot.app, served from Cloudflare
Workers. Static files in public/ are served straight from the edge; the tiny Worker in
src/index.js only runs for paths that aren't files:
| Path | Behavior |
|---|---|
/apk |
302 → newest .apk asset of the latest Gitea release (QR-code friendly) |
/apk.sha256 |
302 → the matching .sha256 asset |
/api/latest |
JSON {version, published_at, apk, sha256} — the homepage's version readout |
/fdroid, /source |
302 → the URLs configured in wrangler.jsonc vars |
The latest release is resolved from the Gitea API at request time (edge-cached 5 min), so
publishing a release — git tag v0.2.0 && git push origin v0.2.0, which triggers
.gitea/workflows/release.yml — is the only release step. The site never needs a redeploy for
a new version, and empty/unreachable values fall back to the homepage instead of 404ing.
Light/dark follows the OS (prefers-color-scheme), no toggle, no JS required for it. Colors
come from the branding palette (teal = instrument, single amber point = finding).
public/assets/ (favicon, wordmark, social preview) are copies of ../assets/branding/ —
that directory is the source of truth; re-copy after any branding change.
Deploy
Everything is driven by wrangler, config
in wrangler.jsonc. No build step, no node_modules to commit.
One-time setup
- In the Cloudflare dashboard, add echo-lot.app as a zone (and point the domain's
nameservers at Cloudflare). The
routesinwrangler.jsoncusecustom_domain: true, so wrangler creates the DNS records forecho-lot.appandwwwautomatically on first deploy — the zone just has to exist in the same account. - Auth, either flavor:
-
Interactive:
npx wrangler login(opens the browser once, stores an OAuth token). -
API token (also what CI uses): dashboard → My Profile → API Tokens → create from the "Edit Cloudflare Workers" template. Then:
$env:CLOUDFLARE_API_TOKEN = "..." # PowerShell; export ... on POSIX $env:CLOUDFLARE_ACCOUNT_ID = "..." # dashboard → Workers & Pages, right sidebar
-
Deploy
cd web
npx wrangler@4 deploy
That's it — uploads src/index.js + the public/ assets, wires the custom domains. Useful
extras: npx wrangler dev (local preview at localhost:8787), npx wrangler tail (live logs),
npx wrangler versions list.
CI deploy (Gitea Actions)
.gitea/workflows/deploy-site.yml runs wrangler deploy on every push to main/master that
touches web/. It stays inert until you add two repo secrets (Settings → Actions → Secrets):
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID (same values as above).
Cloudflare's raw REST API (PUT /accounts/:id/workers/scripts/...) exists, but the assets
upload needs a manifest/session dance that wrangler already implements — use wrangler even in
automation.
Config knobs (wrangler.jsonc → vars)
GITEA_REPO_API— Gitea repo API base; releases must be publicly readable.DOWNLOAD_URL— manual/apkfallback while Gitea is unreachable.FDROID_URL— set when the F-Droid listing exists; until then/fdroidloops home.SOURCE_URL— public source mirror for the footer +/source.
Vars are plain (non-secret) config; change + wrangler deploy to apply.