carlos / carloku Public

docs/deploy-bes-website

Squashed from docs/deploy-bes-website into main.
claude agent, operated by paul@keymail.dev cfd2ed73e2b8df63117ff5e977ec0697a0a11441 parent bfa9665
1 file changed, +68 −78
  • AGENTS.md +68 −78
diff --git a/AGENTS.md b/AGENTS.md
index caabe96..e33f37c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -79,32 +79,37 @@ in `_site/` without you editing its source, something is wrong.
## Deploying
-Merging does **not** publish. `carloku.com` is a bucket-mode CARLOS app on the
-flagship, and the live site is stale until someone ships.
+Merging does **not** publish. The live site is stale until someone deploys.
-**There is a build step now, so what ships is `_site/` — never the repo tree.**
-A tree ship has no `_site/` in it: it would publish `index.html` buried under
-`src/`, no built pages, and no `/docs` at all.
-
-The deployment coordinates, confirmed against the bucket on 2026-08-27:
+**Since 2026-10-04 the site is an ordinary console app** (platform #209):
| | |
| --- | --- |
-| app | `carloku` |
-| account (sqid) | `bab` |
-| channel | `canary/rehearsal` — the app's only channel |
+| console | `https://console.carloku.com` |
+| account / app | `bes` (named `carloku`) / `website` |
+| channel | `edge`, the app's only channel |
| kind | `static` |
-| bucket | `carlos-flagship-271376211898` |
-| AWS profile / region | `keymail` / `eu-west-1` |
+| platform URL | `https://website.bes.oncarlos.com` |
+| custom domains | `carloku.com`, `www.carloku.com` |
+
+carloku.com and www.carloku.com are **platform claims** on that app. They are
+in the flagship's own zone, which `carlos domains attach` refuses to members,
+so the operator attached them with `carlos domains attach --platform`. Only an
+ops account owner or a `CARLOS_SYSTEM_OPERATORS` address can change or detach
+them. A deploy never touches them: deploy the app and both hostnames follow.
+
+**There is a build step, so what ships is `_site/`, never the repo tree.** A
+tree ship has no `_site/` in it: it would publish `index.html` buried under
+`src/`, no built pages, and no `/docs` at all.
```
SHA=$(git rev-parse --short HEAD) # the sha you are shipping
-# 1. Clean export. Never ship a working checkout — PackDir packs every
+# 1. Clean export. Never deploy a working checkout: the packer takes every
# regular file it sees, including .git, node_modules/ and .claude/.
-rm -rf /tmp/carloku-ship && mkdir -p /tmp/carloku-ship
-git archive "$SHA" --prefix=export/ | tar -x -C /tmp/carloku-ship
-cd /tmp/carloku-ship/export
+rm -rf "$TMPDIR/carloku-ship" && mkdir -p "$TMPDIR/carloku-ship"
+git archive "$SHA" --prefix=export/ | tar -x -C "$TMPDIR/carloku-ship"
+cd "$TMPDIR/carloku-ship/export"
# 2. Build, and gate the rendered output. `check` runs eleventy, then
# hack/check-docs.mjs over what it produced.
@@ -121,33 +126,19 @@ grep -rho '\.css?v=[^"]*"' _site --include='*.html' | sort -u # ONE line: your
grep -rn '\.css"' _site --include='*.html' \
&& echo "UNBUSTED LINK — do not ship" || echo "cache-bust ok"
-# 4. Ship the BUILT OUTPUT, then promote. The pinfra scripts are the
-# canonical form for this app: the signing key is read from SSM inside
-# the script and never appears in a terminal or a transcript.
-cd ~/github.com/carlosframework/platform-infrastructure
-CARLOS_BIN=$(command -v carlos) scripts/ship-app.sh \
- keymail carlos-flagship-271376211898 \
- carloku "$SHA" canary/rehearsal static /tmp/carloku-ship/export/_site
+# 4. Deploy the BUILT OUTPUT through the console. --version is required:
+# the export has no .git, so the default (git rev-parse) has nothing to
+# read.
+carlos deploy --console https://console.carloku.com \
+ --account bes --app website --kind static \
+ --version "$SHA" --label "<one line about the change>" --no-prompt _site
```
-`ship-app.sh` takes `<aws-profile> <bucket> <app> <version> <channel> <kind>
-<file-or-dir>` and does both halves. The equivalent by hand, if you are not in
-the pinfra checkout, is:
-
-```
-export AWS_PROFILE=keymail AWS_REGION=eu-west-1 \
- CARLOS_DEPLOYMENT_BUCKET=carlos-flagship-271376211898
-carlos ship --app carloku --kind static --version "$SHA" _site
-CARLOS_RELEASE_KEY=$(aws ssm get-parameter --name /carlos/release-key \
- --with-decryption --query Parameter.Value --output text) \
- carlos promote --app carloku "$SHA" canary/rehearsal
-```
-
-The env matters: without `CARLOS_DEPLOYMENT_BUCKET` the CLI goes through the
-console API, where this app was never registered, and fails with "not found".
-This is bucket mode. Use a current `carlos` binary — the ship/promote pair is
-the platform's own release path, and an old binary on your `PATH` is a real
-hazard here.
+`carlos deploy` ships, promotes onto `edge` and watches the platform URL until
+`X-Carlos-Version` reports the new sha. It runs as whoever is signed in
+(`carlos auth login --console https://console.carloku.com`) and needs that
+address to be a member of `bes`. Use a current `carlos` binary. An old one on
+your `PATH` may not know this console's API.
**Step 3 explained, because both halves are load-bearing.** The links in `src/`
are plain (`href="/site.css"`), so the first expression stamps a token onto a
@@ -158,13 +149,12 @@ beginning with `0` — this app has already shipped `01b5365` — makes
`?v=0` calls every freshly bumped file stale (13 of 13, measured), and an
unanchored `sed` run twice yields `?v=01b536501b5365`. Anchoring on `"` fixes
both and makes step 3 idempotent: run it again and again, the answer is the
-same. Today that is 24 links across 13 built pages — 13 × `site.css`, 11 ×
-`docs.css`. Don't hard-code those counts anywhere; the corpus grows, which is
+same. Don't hard-code link or page counts anywhere; the corpus grows, which is
also why the `find` walks `_site` instead of naming files.
-**Caching: the edge sends no `Cache-Control` and no `ETag` on this static
-route — only `Last-Modified`** (confirmed live on carloku.com 2026-08-27).
-Browsers therefore apply HEURISTIC caching, roughly 10% of the age since
+**Caching: the edge sends no `Cache-Control` and no `ETag` on static routes,
+only `Last-Modified`** (confirmed live on carloku.com 2026-08-27). Browsers
+therefore apply HEURISTIC caching, roughly 10% of the age since
`Last-Modified`, so a returning visitor can hold a stale page for days. This
bit the sibling site the day its redesign shipped: one browser served the whole
old page, another served the NEW html against the OLD stylesheet. The real fix
@@ -174,46 +164,46 @@ of deploying this site**, and it is required for `docs.css` exactly as much as
for `site.css`: the docs pages link both, and a docs page rendered against a
stale `docs.css` loses its whole layout.
-**Convergence is seconds, not minutes** (platform PR #108, live 2026-08-08): a
-promote is picked up by the edge within ~2s. Verify by header and by content,
-and verify `/docs` **specifically** — the landing page looks right whether or
-not the built docs made it into the artifact:
+**Verify** by header and by content, and verify `/docs` **specifically**: the
+landing page looks right whether or not the built docs made it into the
+artifact.
```
-# by header — --resolve so a stale DNS answer cannot satisfy it. 99.81.104.219
-# is the flagship, where the carloku.com A record points.
-curl -sI --resolve carloku.com:443:99.81.104.219 \
- https://carloku.com/docs/ | grep -i x-carlos-version
+carlos channels --console https://console.carloku.com --account bes --app website
+carlos releases --console https://console.carloku.com --account bes --app website
-# by content
+curl -sI https://carloku.com/docs/ | grep -i x-carlos-version # your sha
curl -s https://carloku.com/ | grep -i "<something from the change>"
-curl -s https://carloku.com/docs/ | grep -o 'docs\.css?v=[^"]*' # your sha
+curl -s https://carloku.com/docs/ | grep -o 'docs\.css?v=[^"]*' # your sha
```
-Static routes do carry `X-Carlos-Version` (platform#112). `-I` is fine for that
-header, but it sends a HEAD, and HEAD is excluded from edge compression —
-anything about `Content-Encoding` needs a GET.
+Every edge serves the site, so to check one directly add
+`--resolve carloku.com:443:<edge-ip>`. The four flagship edges are
+99.81.104.219 (Ireland), 16.60.78.128 (London), 44.210.206.26 (Virginia) and
+54.252.186.195 (Sydney). `-I` sends a HEAD, and HEAD is excluded from edge
+compression, so anything about `Content-Encoding` needs a GET.
-**`carlos channels` cannot verify this app.** It is direct-bucket and invisible
-to the console API: `carlos channels -app carloku` answers "not found"
-(re-confirmed 2026-08-27). Verify by header or content as above, or read the
-pointer out of the bucket
-(`aws s3 cp s3://carlos-flagship-271376211898/apps/bab/carloku/channels/canary/rehearsal.json -`).
-
-Rollback is a pointer move, like any CARLOS app — promote the previous good sha
-back onto the same channel; the edge picks it up in seconds:
+**Rollback** is a pointer move. Every release stays in the bucket, cache-busted
+as it was shipped, so a rollback restores a self-consistent page-plus-stylesheet
+pair:
```
-cd ~/github.com/carlosframework/platform-infrastructure
-CARLOS_BIN=$(command -v carlos) scripts/promote-app.sh \
- keymail carlos-flagship-271376211898 bab carloku <previous-sha> canary/rehearsal
+carlos rollback --console https://console.carloku.com --account bes --app website edge
+# or, to a named release:
+carlos promote --console https://console.carloku.com --account bes --app website <sha> edge
```
-That previous release is still in the bucket under
-`apps/bab/carloku/releases/<sha>/`, cache-busted as it was shipped, so a
-rollback restores a self-consistent page-plus-stylesheet pair.
-
-(The channel is `canary/rehearsal`, not `stable`, for the same reason the
-sibling site's is: `stable` bakes 72h on a box's *first* sighting of a channel
-head, which would mean 72h of downtime for a never-before-served route. A
-future `stable` flip is optional cleanup, not required.)
+**DNS** is in DNSimple, by hand (the carloku.com zone is not in the
+carloku-infrastructure tofu). The apex is an `ALIAS` and `www` a `CNAME`, both
+to `website.bes.oncarlos.com`, so the site follows the app wherever the
+platform places it. `dashboard` and `console` are CNAMEs to the console and are
+not this site's. One trade-off: DNSimple resolves the apex ALIAS from its own
+servers, so carloku.com gets whichever edge `website.bes.oncarlos.com`
+latency-steers DNSimple to, not the visitor's nearest. `www` keeps per-visitor
+steering.
+
+**History.** Until 2026-10-04 the site was `bab/carloku`, a direct-bucket app
+shipped with the pinfra `ship-app.sh` onto `canary/rehearsal`, with box-local
+routes on the Ireland box. Those routes were removed when the hostnames moved
+to `bes/website`. The old app's releases are still in the bucket, but nothing
+routes to it: **never ship to `bab/carloku` again.**