The operating manual for traveller.org — kept current with every publish that changes a process. Sits with the CMS portal behind the admin door.
A runbook, in the order you do it. Each step says where you are, what to do, and how you know it worked. Steps marked OWNER need you at a keyboard on someone else's website; CLAUDE steps are done in a session — ask, and they happen. A Done chip means it is finished, with the date on the step.
The cutover is finished: every numbered step below is done, the last (step 24, cancelling InMotion) on 2026-09-25. What is left sits here, yours first. The rest of the page is the historical checklist of how the move was made, kept in order and with its numbers, because the manual’s other pages point at those steps by number.
Five left, as of 11 November 2026. Numbered so you can name one in a session (“open item 2”); the numbers belong to this list and are not the cutover’s step numbers below. The fifth, the faster Publish, was added last so the other four keep their numbers; it is ready whenever you are.
door-check said ON, and the admin’s bar
named your address. The third, the phone page, answered not found; the fix went live with the
Publish of 9 October (build b6c1a86b), along with the smaller rail buttons. On the phone,
open www.traveller.org/admin/api/m/:
it should open straight on the albums, no passphrase screen, the gold bar reading signed in with
Cloudflare. If it does, add it to the home screen in place of the old icon and tell a session it
works. Stage two then follows, done by Claude: the passphrase comes off the web pages
altogether — the test site’s admin points you to www (your choice: live site only), the
old upload addresses forward to the new ones, and the Unlock the live site path retires. The
iPhone Shortcut keeps its passphrase (your choice); it never used these pages. Until then the
passphrase still works on the old addresses and on the test site.p=none — unblocked since step 24.
Cancelling InMotion removed the last sender that failed authentication; the
DMARC reports now decide when
p=quarantine is safe. Ask a session to read the reports; it says when, and then the
change is one TXT record in the Cloudflare zone (DNS
records, select the zone first).2016/cebu is picked up by the next redirect build on its own.brarob100.github.io to the Turnstile widget’s hostnames
(Turnstile → the widget
→ Hostname Management), only if you want the contact form’s robot check to draw on the
test site. The live site does not need it.production branch and Cloudflare clones the repository and builds it: a median of 4.4
minutes to the live site, sometimes twenty. The faster path builds on GitHub and hands Cloudflare the
finished site. Do the parts in order; do C and D one straight after the other.
traveller-worker-deploy, and its editor shows two policies, a Zone policy
(all zones, Workers Routes Write) and an Account policy (Workers Scripts Write).
On its row press ⋯ and then Edit. Press the pencil on the Account
policy and add Cloudflare Pages set to Write (Cloudflare now says
Write where older screens said Edit; Add new policy → Account policy
does the same job). Save the policy, then save the token.fast-publish-trial.traveller-org.pages.dev. The live site is not touched.
It takes a few minutes.2770e8e, the commit www was serving, in 17 seconds; all nine pages came out
same; the upload was accepted on the first try (of 3,635 files Cloudflare already held
3,634 byte for byte, which is the same finding seen from Cloudflare’s side). The preview
address answers with that commit at /build.json, and its /admin/ sends a
stranger to sign in.traveller-org). Cloudflare’s own
instructions, as of 11 November 2026, read: Build → edit Branch control → turn off
“Enable automatic production branch deployments”. Save. Leave the preview-branch
setting as it is.production from starting a build.production, the box “Enable automatic production branch deployments”
unticked, preview branch left on “All non-Production branches”.FAST_PUBLISH. Value: on. Add it.FAST_PUBLISH with the value on.on: that is the proof it took./build.json; that an unlisted album
still sends a stranger to the friends’ sign-in; that /admin/ still asks you to
sign in; and that the contact form still draws its check.7539cd8, so this one sent the commit already live. The whole run took 80 seconds:
the branch moved, the build matched www on all nine pages, Cloudflare already held all 3,635 files,
and www answered with the commit at once. Then, on www: /build.json names
7539cd8; the unlisted album /countries/unitedkingdom/1991/ and the
friends’ shelf both send a stranger to the friends’ sign-in; a wrong friends’
password is refused (bad=1), which it can only be if the server has the password, so the
live settings apply; /admin/ sends a stranger to the Cloudflare Access sign-in; the
contact page carries the Turnstile widget with its live site key. The session could not watch the
check draw, because its sandbox cannot reach Cloudflare’s challenge server; the next
time you open the contact page is the last word on that. The Publish page’s Live
is yours to see too: it is behind your sign-in.FAST_PUBLISH variable; turn automatic production branch deployments back on. Then
tell the session what you saw. Publish is then exactly what it was this morning.Claude’s when you ask, or deliberately waiting.
/build.json). A session rewords them with the page’s test when you ask.docs/2026-11-10-review.md in the repository. It is being worked through in six batches, each published when tested: (1) the faults — done; (2) phone and iPad polish — done; (3) a “done” signal for background runs — done; (4) public-site speed, touch and navigation — done; the 1200px photograph size for phones was built on 11 November at your word (addendum 437), and its one-off fill for photographs already on the site was run the same day: 30,846 of 30,932 have their copy (the numbers). The owner’s photo controls came off the album pages the same day (addendum 439). Still open: moving the admin’s styles out of the public stylesheet. It was measured and tried on 11 November and not done: 244 rules, about 26 KB of the stylesheet’s 142 KB, match nothing on a public page and could move, but the check built to prove the move changes nothing on any admin page gave different answers for the same build twice, so nothing was moved (addendum 442); (5) pipeline efficiency — done; the faster Publish was built off and switched on by you on 11 November (the steps, all done). Built since, on 11 November: caption saves leave in batches; Album cleanup reads captions from the repository without waiting for a rebuild; a covers or journal-photo save that GitHub drops sends itself again (addenda 435, 436). The build’s Node version is pinned to 22 in .nvmrc (addendum 440). Album cleanup’s order and friends-only marks are read from the repository too, and the test runner times every suite (addendum 441; a full run is about 700 seconds, and running 52 suites side by side saves about 31 of them). Still open from this batch: Cards & strips, Journal photos and Banknote pages reading saved changes the same way; sharing builds between the browser suites, which is where the suite’s time goes; the IndexNow run’s wait after a Publish, which should now be short and has not been looked at since the faster Publish went on; the guide-sync runs that fire for nothing (the cause is in the app repositories); (6) Delete for films and letters, and one vocabulary — done. The eight controls left from batch 6 were built on 11 November (addenda 434 and 435): captions in Album cleanup; Unlisted and “Publish to www too” when the phone uploads into an existing album; old albums in Album cleanup’s phone list, with Delete; a Friends button that says which way it goes; a card to name banknote photographs the reader could not; Bring them back for photographs held as near-duplicates; one rule for confirming what cannot be undone. One is yours to decide: the Remember-passphrase default, which is off at a desk and on on the phone; say which way both should go. Caption saves now leave in batches too. A session picking this up reads that file and the decision log from addendum 426 on.images.traveller.org answers 404 for the full-size file, for the thumbnail, and for every rotation name of it. 72 are in Turkey 2022, 13 in Siem Reap 2009 and 1 in Belgium 2016. The albums’ records still name them, so each album page draws a tile whose picture cannot load, on the test site and on www. Nothing done on 11 November could have removed them: that work wrote and deleted files under mid/ only. Not known: when they went, or why. The names are in docs/2026-11-11-missing-photographs.txt. Yours to decide: a sync run on each of the three albums removes the 86 empty tiles (the records then match storage); getting the photographs back means uploading them again. Say “sync the three albums” in a session, or upload the photographs to those albums first.ThrottleHost), so 6 of 18 is the last count a session has. What is left for you is Bing’s own IndexNow page: send its address in a session and it goes into the stack table, and this item closes.http://www.traveller.org/sitemap.xml (submitted 2009; the new one kept the filename), last downloaded 2026-10-04 — nine days before the Publish. So Google had not yet read the new sitemap, let alone the pages. The session doing the comparison should read that date first: if it has still not passed 2026-10-13, there is nothing to compare yet, whatever the calendar says.docs-guard.yml — run it on the trunk only, and check out without the
photographs (filter: blob:none, sparse to .github); it only compares two
lists of file names, yet was the largest single consumer (661 minutes in September, 2,797 in
August), about 500 minutes a month saved.
(2) cutover-checks.yml — run it on the trunk only, not on every session branch
as well; roughly 100 minutes a month.
Already done: the retired-file sweep runs daily rather than hourly (about 720 minutes a month
avoided). Keep the $35 Actions budget as an alert without Stop usage: a stopping budget
would halt photo imports, the test site and Publish until the month resets.migration/journal-missing-images.csv
in case a scan turns up.direction: replace, which the fetch honours by
overwriting a taken name); the phone reuses its existing duplicates sheet. Tests pin the
classification’s parity with the Python, both pages’ POSTs, and the replace
flag. About half a day of session time. Reuse points: icloudGuid and
the destination handling on /dispatch, the /photos listing,
master_name / plan_names in icloud_fetch.py, the
#dupsheet on /m. Nothing is built until you say so.production, with five Pages Functions) and the Spec Intake form (two
functions, on spec-intake.pages.dev). The four Workers — upload,
contact, email and the recipe pipeline — are already Workers and are unaffected, as
are Netlify (Expense Tracker) and Railway (the Amapas 353 ChatGPT app).
_redirects, _headers, custom
domains, environment variables and secrets all carry over, and Git builds become Workers
Builds — but the functions/ folder is not supported natively and
must be compiled or rewritten. On this site that folder is the 301 layer answering every
old address plus the friends door, which is the machinery least safe to disturb while the
Search Console reports are still draining.
docs/guides-site/; they are not on the shelf. The reason is worth
keeping: the guide went stale between being drafted and being pushed — the
Rates screen was redesigned, promo codes went from two kinds to three, and a Reviews
screen appeared, all in the hours it took to write. To bring it back: delete the
hold line from that app’s row in src/data/guides.json,
then run the Sync app guides workflow. Before you do, have that repository’s
session re-read the six pages against the finished site — the sentence most likely
to be wrong by then is the one saying the permanent web address is not switched on
yet./admin is not built. Editing is by session or by
editing JSON on GitHub. The Journal editor (2026-08-18) now covers the letters
— prose, inline photographs, the pull-quote — which was the largest piece of
what a CMS was for.Every step here is finished. Read top to bottom, it is the order the move had to happen in, and each step records when it was done and anything that surprised us.
The order is a dependency chain, not a suggestion. Every step can be started the moment the one above it is finished, and none can be started before — so working top to bottom never leaves you stuck. If a step ever tells you it must wait for a later one, that is a bug in this page: say so and it gets moved.
images.traveller.org needs no record from you anywhere. R2 creates it
itself when you add the custom domain in step 14, and it can only do that once the
zone is on Cloudflare — which is why that step sits immediately after the nameserver
change and not before it. Adding a CNAME for it at Porkbun would not work and is not a
shortcut: R2 serves a bucket under a hostname only for domains attached through its own
Custom Domains panel, and nothing else would issue the certificate. So there is no
subdomain DNS to do — not before step 13, and not after.Nothing here touches the live site. Do it whenever; it can all sit finished for weeks.
brarob100/traveller.org → Begin setup.traveller-org — keep exactly this, it is the name
step 2's redirect table was built against; Production branch production — the one field that must not be left at its default;
Framework preset None; Build command npm run build;
Build output directory dist. Preset None leaves the last two
blank — type them, rather than picking the Astro preset and trusting what it fills
in.npx wrangler deploy.
wrangler.toml, which belongs to the upload Worker, and
deploy that instead of the site — a second deploy path fighting the one GitHub
already runs. Worse, the Workers form pins no branch, so it would build the working
branch: every commit straight to the live site, which is the one thing this project exists
to prevent. It also never asks for the output directory or the five variables below, both
of which Pages does.PUBLIC_R2_BASE and check it ends .r2.dev):
SITE_URL = https://www.traveller.orgSITE_BASE = a single forward slashPUBLIC_R2_BASE =
https://pub-d86b555b7dd7462abd871c743192bcd6.r2.dev — the bucket's own
address, for now. It becomes https://images.traveller.org at
step 14, which cannot happen until the nameservers move at step 13. Setting the
custom name before it resolves builds a site on which every photograph is broken.PUBLIC_LEGACY_IMAGES = offPUBLIC_UPLOAD_URL = https://traveller-upload.brarob.workers.devtraveller-org.pages.dev shows the
site, and a session confirms the build is correctly shaped — green is not the
finish line, because it is green either way. Write that address down — step 2 needs
it.traveller-org.pages.dev, production branch
production, all five variables verified from a session against the deployed
site — see the checklist below for what that means and how to ask for it again after
a redeploy.production
branch, which moves only when you press Publish — so this first build shows
whatever was last published, not the newest staging. A missing recent change here is the
gate working, not a broken build: open
the Publish page, check the waiting list, and publish
once to bring the two level.traveller-org.pages.dev was added to the sandbox's allowlist and
a session verified all five from this side on 2026-08-18, so the check is one
sentence to Claude ("verify the deployed Pages site") rather than five things to find in a
browser. What it looks at, and which variable each proves:
pub-….r2.dev and the objects fetch 200 image/jpeg —
PUBLIC_R2_BASE reached the build, and its truncated ending survived the
paste/robots.txt does not say Disallow: / and names a
sitemap — SITE_URL reached itrel="canonical" on the home page says www.traveller.org with no
sub-path — SITE_URL and SITE_BASE both landed, so the build
is production-shaped/sitemap.xml exists, every URL in it names www.traveller.org, and
none is under /admin/PUBLIC_LEGACY_IMAGES=off reached itPUBLIC_UPLOAD_URL reached itproduction and not the working branch: every commit
reaches staging (the test site) by itself, but the live site must only change when you
press Publish on the Publish page —
that button is what moves the production branch, and this Pages project
rebuilds from it. Connect the working branch instead and every half-reviewed commit goes
straight to traveller.org.robots.txt, the sitemap and the llms.txt files all say
www.traveller.org instead of the test address; PUBLIC_R2_BASE points every
photograph at the image host; PUBLIC_LEGACY_IMAGES=off withholds the
0 albums whose photographs are not migrated yet
(), so the site never shows a
broken frame.NEW_HOST=https://traveller-org.pages.dev python3 migration/build_redirects.pymigration/htaccess-redirects.txt was generated against a guessed
address, traveller-org.pages.dev. If Cloudflare gives the project any other
name, every one of its 327 redirects points at a host that does not exist — and it
is pasted into the live server at step 6.migration/htaccess-redirects.txt names your Pages address,
and both checks pass:
python3 migration/verify_redirects.py and
python3 migration/test_htaccess.py.
traveller-org.pages.dev — the very address the committed table had been
generated against from a guess — so regenerating produced a byte-identical file
and git diff came back empty. That empty diff is the whole result: it proves
the 393 rules about to be pasted into the live server point at a host that exists. Had
Cloudflare named the project anything else, the diff would have been the work.SITE_URL=https://www.traveller.org SITE_BASE=/ PUBLIC_LEGACY_IMAGES=off
PUBLIC_R2_BASE=https://images.traveller.org npx astro build
then python3 migration/verify_cutover.py.SITE_URL and SITE_BASE
off and the build still calls itself production — but its canonical tags name the
staging address, so the redirect check reports STAGING and refuses to make the strong
claim this step exists to obtain, and the audit's own headline passes for the wrong reason
(see the note below).python3 migration/verify_redirects.py.dist/, and at step 2 that is
normally a staging build. Production sets PUBLIC_LEGACY_IMAGES=off and
withholds the legacy albums, so a redirect aimed at one would pass at step 2 and 404 on the
live site. The check now prints which shape it read and says so when the weaker claim is
all it can make.PUBLIC_LEGACY_IMAGES and PUBLIC_R2_BASE set, the audit printed
self-references the new site serves: 0 and then nothing on the public site
addresses www.traveller.org — safe to cut over. Both lines were true and together
they meant nothing: with SITE_URL unset there were no www.traveller.org
references in the build at all, so the audit passed by having nothing to look at.
Adding the two missing variables gives 1130 self-references and the same pass —
which is the pass that means something. That is `tests/README.md`'s second trap ("a passing
test can be measuring nothing") landing on the runbook for the second time in one day.@astrojs/sitemap, /admin/ excluded, named by production
robots.txt), canonical tags and og:url/og:image on every page,
and schema.org JSON-LD (WebSite + Person on the home page, ImageGallery on galleries,
BlogPosting on journal entries). tests/seo.test.mjs holds it all green../tests/run.sh passes — the SEO suite asserts the
sitemap exists, leaks no admin page, and robots.txt names it.run.sh builds a staging-shaped site, so for the production
claim run the step-4 build command above and then node tests/seo.test.mjs
against it. Done 2026-08-18 that way: 21 assertions green, 563 sitemap URLs, no
/admin/ page listed — and the same three facts confirmed independently
against the deployed site at traveller-org.pages.dev, which is the stronger
check because it reads what Cloudflare actually served.
This is the only stage that edits the old server. It sends visitors to the new site before DNS moves, so no old link is dead in the meantime. The old site keeps working for anyone typing the address.
traveller-org.pages.dev carries
rel="canonical" naming www.traveller.org — so the new host
disclaims itself, and search engines keep the www URLs in the index rather than replacing
them with pages.dev ones that would all have to be replaced again at
cutover. Nothing transfers early because nothing needs to: the addresses that are indexed
today are the addresses that serve the site after step 13..htaccess.public_html → Settings
→ tick Show Hidden Files → select .htaccess →
Download.migration/test_htaccess.py, which is the next best thing: the file can be
reconstructed from the repo if the download is ever lost.traveller-org.pages.dev, not at www.traveller.org, and
that host has been live since step 1. This is the whole reason Stage 2 comes before the DNS
flip: visitors and search engines start reaching the new site now, and nothing about it
waits for step 13. Verified 2026-08-18: all three of step 8's targets answer 200 on the
Pages site. If you would rather see that for yourself before editing anything, open
https://traveller-org.pages.dev/currency/thailand-20/ in a browser —
that is exactly where the third test URL will land..htaccess → Edit. Open
migration/htaccess-redirects.txt
on GitHub, click Raw, copy everything, and paste it in at the bottom, below
everything already there. Save.#spamming site lines and ends
with the redirect block.301 → traveller-org.pages.dev on an invented URL, which
it can only do if the rules are being read.content/ layout, which the generator had missed. Every URL they cover
already falls through your uploaded catch-all to the new host, where the new machinery
catches it — verified live on /luxembourg/1998/. Re-pasting would only
shorten a two-hop redirect to one, and the whole file stops mattering at step 13, when
this server stops seeing traffic for the domain.
migration/test_htaccess.py. It
holds only two things, a user-agent block and a PHP handler, and neither one catches URLs,
so the redirects can safely run last. Pasting below them means the user-agent block keeps
first refusal on the agent it bans; everyone else falls through to the 301s.[L], so the banned agent escapes the
block entirely and gets a 301 like everyone else. Bottom of the file, every time.RewriteCond pattern as a regular
expression — so the parentheses in (Windows NT 10.0; WOW64) and
(KHTML, like Gecko) are capture groups, not literal brackets. The rule
therefore hunts for a version of the user-agent with its brackets stripped, which no
browser sends. If you want the block to work, replace its RewriteCond line
with:RewriteCond %{HTTP_USER_AGENT} "Chrome/51\.0\.2704\.103" [NC]503 from the live server — the first time this rule has ever fired. A
normal browser is unaffected, and the redirects below it are untouched.
AddHandler line alone while you are tidying. The old
WordPress site still runs on it until InMotion is cancelled, and the 301s living in this
file have to keep working until then. (Its comment says PHP53 and the directive says
php56 — the comment is what is wrong, and neither is worth touching.)traveller-org.pages.dev, which is live, so they work the moment step 6 is
saved — the old site being still on InMotion changes nothing.www.traveller.org/journal/?p=15 → Climbing the Pyramidswww.traveller.org/cuba/1996/varadero/ → the Cuba gallerywww.traveller.org/currency/thai-notes-20.html → the Thai 20 note200 on the Pages site. Then
14 more were sampled, two from each of the seven redirect categories (banknote,
banknote-without-a-record, country, gallery, split-or-retired gallery, journal post, root
page): every Location header matched the redirect map exactly, and every
target answered 200 on the deployed site rather than merely existing in a
local build.
.htaccess (it must
be the one in public_html, the same file step 5 downloaded).www.traveller.org/definitely-not-a-real-page.html. With the block live, the
catch-all rule 301s anything unmatched to the new site. If instead you get a 302 to
missing.shtml, that is the old site's own 404 handler answering, which means
no rewrite rule ran — the block is not active. Confirmed as the pre-paste signature
on 2026-08-18, when the live server was probed from a session and answered exactly
that.Last look. Still nothing irreversible. Start with the TTL, because it is the only step here with a wait attached — set it first and the other three happen while the clock runs.
traveller-org.pages.dev — one of every kind
of page the site builds:
ERR_CONNECTION_RESET — which is why the committed browser suites run
against a local server with photographs stubbed. Corrected 2026-08-18, after trying it.200 image/jpeg from the bucket, including the four whose filenames carry
spaces and brackets. So the addresses are right and the objects exist.luxembourg/1998, was moved onto the new site the same day: its 23
photographs now ship with the build under /legacy-media/, so it is published
like any other album and is not marked deprecated — nothing supersedes it, it
is simply the best copy that exists.www.traveller.org, and step 6's redirects made every such URL 301 to the new
site, where they did not exist. The moment the redirect block went live, the album's images
broke. Shipping them with the site is what makes the old server switchable-off, which is
the whole point of stage 5.verify_redirects.py and verify_cutover.py both
pass against a fresh production build.luxembourg/1998 moved onto the site:
verify_cutover.py — 574 pages serve photographs from the image host,
1,133 self-references to www.traveller.org, and 0 references to the moving
hostname the new site does not serve. Nothing on the public site addresses
www.traveller.org — safe to cut over.verify_redirects.py — checked against a PRODUCTION build,
327 redirects to 278 distinct targets, 278/278 present.verify_redirects.py says STAGING and exits 0 —
so the job greps for the word PRODUCTION and fails without it. And the audit's headline
passes trivially on a build containing no www.traveller.org references at all, which is what
happens when SITE_URL is unset — so the job also fails if the
self-reference count comes back near zero, where a real build has over a thousand. Both
failure modes were reproduced deliberately before the job was trusted.amy.ns.cloudflare.com and
bob.ns.cloudflare.com but the two words are yours alone. Copying a pair from
any documentation, including this page, sends the domain to nameservers that are not
authoritative for it and the site goes dark. Step 13a is where yours appears; use those
two and nothing else.production branch, and the
post-cutover redirect machinery (step 18) only exists on it after a publish — the
100-rule discovery of 2026-08-18 was made on the deployed site, and the fix reaches
it the same way. Then ask a session to re-verify the three legacy URLs on
traveller-org.pages.dev before 13c; they must 301 or forward, not 404.traveller.org → choose the Free plan.
Cloudflare then scans Porkbun's existing DNS and imports what it finds. This changes
nothing yet — the domain keeps resolving through Porkbun until 13c.traveller.org → Authoritative
Nameservers (some accounts label it NS) → replace both existing
entries with Cloudflare's two → Save. This is the moment the switch is
thrown.traveller-org →
Custom domains → add www.traveller.org, then add
traveller.org. Cloudflare creates the records and issues the certificates
itself.traveller-org.pages.dev answers on
172.66.44.108 and 172.66.47.148, and those are Cloudflare's shared
edge, not this site's address. The edge decides what to serve from the hostname, not the
IP — so an A record pointing at it serves your visitors nothing until Cloudflare
has been told that hostname belongs to this project, and the only place to tell it is the
Custom domains panel above, which needs the zone in the account.
traveller-org.pages.dev → 200, the sitewww.traveller.org → 403 Forbidden, from
server: cloudflaretraveller.org by flattening it,
which requires hosting the zone); no certificate — Cloudflare issues the
padlock for a custom domain once it is attached, so an A record alone fails TLS before it
ever reaches the 403; and those addresses are Cloudflare's to change, so a hand-copied A
record is a bomb on a timer.
https://www.traveller.org shows the new site with a valid padlock.
Certificates can take a few minutes.www.traveller.org and reads the response headers. Server: cloudflare
with a CF-RAY means the request is being answered by Cloudflare;
Server: Apache means it is still InMotion and the change has not landed yet.
That is exactly how the pre-cutover state was established on 2026-08-18. What a session
cannot do is read the nameserver records themselves: cloudflare-dns.com
and dns.google both answer 403 on CONNECT from the sandbox, so the
authoritative check on the NS pair is Cloudflare's own Active badge.
www and the apex back at InMotion's address
(216.194.165.246, the value they resolve to today) takes effect within the
record TTL — minutes: Cloudflare serves proxied records with a ~5-minute TTL of its
own, and the records its scan imported carry the 600 seconds set in step 9. Reverting the nameservers at Porkbun instead means waiting on
the .org registry's delegation TTL, which is not yours to set and is
commonly a day: resolvers that have cached the delegation keep asking Cloudflare regardless
of what Porkbun now says. Same intent, two very different clocks./admin/ (20) all require it.server: cloudflare with a CF-RAY, serving the new site, and the
live home page counted the same 60 countries as the data. One thing broke, and it was
not the website.traveller.org MX 0 traveller.org — aimed at the
apex, which is proxied — and the orange cloud carries HTTP and HTTPS only. A
proxied name resolves to Cloudflare's edge, which does not speak SMTP, so inbound mail had
nowhere to land. Nothing in the dashboard says so; the record looks fine.mail →
216.194.165.246 set DNS only (grey cloud), the apex MX re-pointed at
mail.traveller.org, and cpanel and ftp greyed for
the same reason. Confirmed from a session: mail.traveller.org answers
server: Apache with no CF-RAY — the grey cloud is
working — while www answers as Cloudflare.images.traveller.org is attached to the bucket and works, and the owner reports
PUBLIC_R2_BASE changed — but the live build still asks for every
photograph from pub-….r2.dev. A variable is read at build time
only, so the site keeps the old value until a build runs having seen the new one. The
same symptom sits on step 18's Turnstile keys, from the same cause — which is what
makes this one build problem rather than two variable problems. Nothing is broken meanwhile:
both addresses serve the same bucket.pub-….r2.dev address set in step 1. If mail needs
attention first, do step 15 and come back — the two do not depend on each other.traveller-images →
Settings → Custom Domains → + Add →
images.traveller.org. It is the first panel on the Settings page,
above the fold, and while it is empty it reads "There is no custom domain assigned to
this bucket."https://pub-d86b555b7dd7462abd871c743192bcd6.r2.dev, which is the value
PUBLIC_R2_BASE is set to right now, so disabling it breaks every photograph on
the site the moment it is clicked. Searching the page for "public" lands on that panel and
not on the one you want. Cloudflare's own warning under it — rate-limited, not
recommended for production, Access and Caching unavailable — is the whole reason
this step exists. It stays enabled until PUBLIC_R2_BASE has been switched over
and a redeploy has proved the new name works.https://images.traveller.org/thumbs/vietnam/2009/halong-bay/…any file
listed in the bucket — and Pages → traveller-org → Settings
→ Environment variables has PUBLIC_R2_BASE changed from the
pub-….r2.dev address to https://images.traveller.org,
with a redeploy so the build picks it up.
PUBLIC_R2_BASE after the custom domain resolves,
never in the same sitting as adding it — between the two, every photograph on the
site is being served from a name that may not have propagated, and the Pages build cannot
tell you that. Add the domain, open a photograph over it in a browser, and only then edit
the variable and redeploy.send.traveller.org — a different hostname, which
Email Routing neither reads nor cares about. Do not delete it: it is Resend's
bounce and complaint return path, and removing it breaks sending, which is the half you are
keeping.MX 0 mail.traveller.org — the one
written at step 13 to repair inbound mail while it was pointed at a proxied name. That was
right then and it is exactly what Email Routing replaces now. Delete the apex MX, then
press Activate; Cloudflare writes its own three
route1/2/3.mx.cloudflare.net. Leave the mail A record
alone — it is not an MX, and it is how you reach cPanel and webmail afterwards.
v=spf1 include:_spf.mx.cloudflare.net ~all for the apex. You already have an
apex SPF covering InMotion's sending; accept the offer as written and InMotion's mail stops
passing SPF. Merge the two into one apex TXT — every include you have plus
Cloudflare's, one ~all at the end — and edit the record by hand
afterwards if the wizard has overwritten it. The InMotion include comes back out at step
24, when that plan is cancelled.v=DMARC1 TXT (DMARC is only ever read at _dmarc.traveller.org)
and a v=DKIM1 TXT (DKIM is only ever read at
<selector>._domainkey). Neither does anything where it sits. That the
DMARC one is inert is lucky rather than fine: it asks for strict alignment, which
Resend's send. return path would fail if anything ever read it.@traveller.org addresses to Gmail. The moment
the nameservers change, mail follows DNS — so this step happens the same day, right
after step 13. Cloudflare → the traveller.org zone → Email
→ Email Routing → Get started:
brarob@gmail.com and
reynaldh@gmail.com. Cloudflare emails each one a verification link; both must
be clicked before any rule can point at them.brarob,
brobinson, dns, hiole, privacy,
support, theboys → brarob@gmail.com, and
reynald → reynaldh@gmail.com. Add dmarc →
brarob@gmail.com alongside them — it is not one of InMotion's eight, but step 17's
reports are sent to it and bounce without it.theboys@ goes to both inboxes today, and a Cloudflare rule forwards
to one destination only — so its rule is different: set the action to Send to a
Worker → traveller-email. That Email Worker is already written and
deploys itself (cloudflare/email-worker.js); it hands the same message to both
Gmail inboxes. It still needs both destinations verified first.traveller-email”; the Worker decides
where it goes, from a list in its own code:
traveller-email → the code view. The line reads
const INBOXES = […], near the top.cloudflare/email-worker.js in the repository
and push — it redeploys itself. Not in the dashboard editor: that works until
the next auto-deploy overwrites it. Asking in a session is the short way.forward() refuses an
unverified destination, so an unverified inbox means theboys@ quietly reaches
one person instead of two. Check it here before activating.theboys@ arrives in both.
brobinson@traveller.org (or any address on the domain) must not
depend on InMotion's mail server. Resend takes over as the outgoing relay — the domain
is already verified there, because the import emails send from
pipeline@traveller.org:
gmail-smtp. Do not reuse the
pipeline's key — separate keys mean rolling one never breaks the other.brobinson@traveller.org. SMTP server smtp.resend.com, port
587, username resend, password = the new API key, TLS.brobinson@traveller.org — it
arrives in brarob@gmail.com through the forwarding set up in step 15, which is why this
step comes after it.brarob@traveller.org, support@…) — the same SMTP
credential serves them all.brobinson@traveller.org arrives showing that address — not “via
gmail.com” — and lands in the inbox, not spam.
d=gmail.com and its bounce address is your gmail.com account — so
SPF and DKIM both authenticate gmail.com, while the From line says
traveller.org. DMARC's whole test is whether those two agree, and here nothing
authenticates the domain in the From line. Through Resend the message is DKIM-signed
d=traveller.org, which is the same thing said properly.pipeline@traveller.org. Free tier: 100 emails a day, shared with the import
notices — plenty.theboys@ and hiole@
worked, brobinson@ did not) — Gmail stores the SMTP server per
address, not once per account. The screen that lists them shows only the address, so
three entries can look identical and be pointed at three different servers. An address you
set up years ago against InMotion keeps InMotion's server and password until you change it,
and mail.traveller.org still resolves, so the failure is a credential error
rather than an obvious one.smtp.resend.com on port 587 with username
resend is the bug; retype it and re-send. If it already says Resend, the next
suspects are an address showing (unverified) in that list — Gmail will not send
as an address whose confirmation code was never clicked — and an API key created with
sending access scoped to one domain.| Type | Name | Content | TTL |
|---|---|---|---|
| TXT | _dmarc | v=DMARC1; p=none; rua=mailto:dmarc@traveller.org; fo=1 | Auto |
p=none is deliberate. It changes what happens to nobody's mail; it
only asks receivers to report what they saw. Read a few weeks of reports, confirm everything
legitimate passes, and only then consider p=quarantine. Starting at enforcement
is how a forgotten sender — a booking site, an old form, a mailing list —
disappears into spam with no warning.send.traveller.org, a
subdomain, and relaxed alignment accepts that while strict would fail it. The old
apex record carried adkim=s; aspf=s — do not copy them across.v=DMARC1 TXT at the same time. DMARC is only ever
read at _dmarc, so the apex copy has never done anything; leaving it there
invites a future tidy-up that copies its strict flags onto the real record.dmarc@traveller.org has to exist, or the reports bounce. Add it as a
custom address in step 15's list, forwarding to brarob@gmail.com. Keep the address on
traveller.org — pointing rua at a gmail.com address instead requires
Gmail to publish an authorisation record for this domain, which it does not, so most
senders would silently refuse to report at all._dmarc.traveller.org answers with the record (any DNS lookup
tool), the apex copy is gone, and within a few days a report has arrived at
dmarc@.
data-sitekey, the Turnstile script and the live endpoint, and the contact
Worker answers past its own secret check. It came right a day after it looked broken, and
the reason is worth keeping: the page the owner was testing was staging, whose build
carried neither key and therefore drew the display-only form — a grey
“Test build” line and a picture of a checkbox — while the live site had
been sending all along. Staging now builds with both keys too, so the two agree.https://www.traveller.org/contact/, view source, search for
data-sitekey. Empty means the build had no key, whatever the dashboard shows.
Then, in order:
PUBLIC_TURNSTILE_KEY? Case-sensitive, no trailing
space. Astro only substitutes a variable whose name starts with PUBLIC_, and an
unrecognised name is not an error — it is an empty string.PUBLIC_R2_BASE, which is the useful clue: two
unrelated variables both missing points at the build, not at either variable.traveller.org contact, hostnames traveller.org and
www.traveller.org, widget mode Managed. It gives you two keys.
brarob100.github.io to that list as well (the
widget → Settings → Hostname Management → Add). The staging site
builds with the same public key since 2026-08-20, so the form works there too — but
Turnstile renders only on hostnames the widget names, and on any other it draws nothing at
all. That empty space is indistinguishable from a broken key, and it is neither.PUBLIC_TURNSTILE_KEY, and alongside it PUBLIC_CONTACT_URL
= https://traveller-contact.brarob.workers.dev. Then redeploy — a variable
on its own changes nothing until a build reads it.
traveller-org is the entry tagged Pages, not the
traveller- Workers beside it.PUBLIC_R2_BASE from the pub-….r2.dev address to
https://images.traveller.org and one redeploy finishes both.traveller-contact → Settings → Variables → add
TURNSTILE_SECRET (encrypted), and RESEND_API_KEY — a
third Resend key, its own, so rolling it touches neither the pipeline nor Gmail.https://www.traveller.org/contact/ and it arrives in your inbox — and
hitting Reply addresses it back to the address you typed, not to the form.
www.traveller.org or
brarob100.github.io, not traveller-org.pages.dev. The
Turnstile widget is bound to the hostnames in its Hostname Management. On any other name it
refuses to render — which looks exactly like a broken key, and is not.brarob100.github.io and reasonably read it as a broken CAPTCHA on a form that
was, on the live domain, already sending./journal/?p=15 — the journal index resolves the WordPress post id
with a script, because Pages redirects cannot see a query string./cuba/1996/varadero/ — a pre-rendered forwarding page at the
old address, one of 172 the build writes for every exact old URL./currency/thai-notes-20.html — a forwarding page too; the wildcard
rules in _redirects cover only the ~4,600 photo wrapper pages, whose filenames
cannot be enumerated._redirects file
(2026-08-18): the deployed Pages host was observed honoring exactly the first 100
rules of that file and silently dropping the other 125 — rule 100 fired, rule 101
did not, bisected live against traveller-org.pages.dev, with no bad byte at
the boundary. Every plain gallery URL was in the dropped half, so they would have 404ed the
moment DNS moved, with every build green. Cloudflare's documentation permits far more; the
site is built to the limit that was observed, with a guard in
build_redirects.py that refuses to emit more than 100 rules and a suite
(tests/redirect-stubs.test.mjs, also in the Cutover checks job) holding
every exact old URL to having its forwarding page.https://www.traveller.org/robots.txt, then
https://www.traveller.org/llms.txt.Disallow: /, and every link
in llms.txt names www.traveller.org, not the old test address. The staging
site blocks indexing and writes test-host links; a production build drops both
automatically. If either survives, the build did not pick up SITE_URL.
llms.txt / llms-full.txt are what AI
answer engines read. AI crawlers are deliberately not blocked — the
Pipeline page has the whole
LLM-and-search inventory, including what is deliberately not built.www in Subdomain, choose
traveller.org as the Domain, and type admin in Path.
traveller.org/admin and not www.traveller.org/admin
— and www is where the site actually lives, so the door would look configured and guard
an address nobody uses. Add a second public hostname with the Subdomain empty if you
want the apex covered too; it costs nothing and closes the back way in.Owner only → Action
Allow → under Include choose the selector Emails → save. Back
on the application, the policy now appears in the list.
/admin/,
including Publish and the upload pages. Anyone you add
can put the site live. Keep the list to people you would give the repository to.traveller.org;
change it to something you will recognise in a list, like traveller.org admin.
Session Duration 24 hours is fine. Then Create.https://www.traveller.org/admin/docs/ asks for an email and a code before
showing this manual — and your own browser, already signed in, still opens it normally.
Both held on 2026-08-20: an anonymous request is 302'd to the one-time-PIN login, the JSON
feeds the pages read stay public, and the owner signed in with the code the same day.
traveller-upload.brarob.workers.dev — nor
www.traveller.org/api/*, where the same Worker is routed (since 2026-08-20)
so the admin pages can call it on their own domain. Putting Access in front of either breaks every interactive
admin page, which is the fault this route was added to end (2026-08-20). Its door is the
passphrase —
which is why that gate rate-limits failed attempts (about ten wrong tries a minute, then
a one-minute wait; the Pipeline page has the
details). Remember that robots.txt and noindex keep pages out
of searches, not away from visitors — they are politeness, not locks.https://www.traveller.org as a property if it is not
already → submit https://www.traveller.org/sitemap.xml.mail and cpanel are
grey-clouded A records in the Cloudflare zone aimed at InMotion's server, and they are the
reason those two names still hold a certificate. Once the plan ends they aim at nothing..htaccess. That is the whole of what “the redirect overlap” means;
decide it is over before pressing cancel, not after.mail, cpanel and ftp — so delete all three, and edit the apex SPF TXT to drop only the InMotion
include, keeping Cloudflare’s. This session cannot read public DNS, so it could not check
the live records for you; the zone’s own list is the one to believe. Say when it is done
and the step, the table and DMARC’s next move get updated together.p=none, which
is why the two are written down together.traveller.org zone
→ DNS → Records. (Records left Proxied show TTL as
Auto and need no change; the setting only applies to unproxied records.)traveller.org zone first (the controls are
per-domain; the account page does not have them) →
Security →
Settings, the page Cloudflare’s 2026-09-15 email calls Security Settings.
The page is headed Security — Manage how AI crawlers access your content,
and there are two layers on it, both of which have to say Allow (you walked this on
2026-09-16):200 on every row. It runs itself every
Monday morning after that, and goes red if the setting drifts again.
200 on both paths, with robots.txt the site’s own. That is this
step’s whole test, so whatever switches that table held are no longer refusing anyone.
Where Cloudflare now keeps the table, this session cannot say — it cannot see the
dashboard, and the page has changed since you walked it on 2026-09-16. If the Monday probe
ever goes red, that is the moment to look, starting from the same
zone Security
settings page.albums.traveller.org. Cloudflare creates the DNS record and issues the certificate
itself, exactly as it did for www and the apex at 13e; you type nothing at Porkbun and add no
record by hand. No Cloudflare Access application is needed — there is no admin on this
host, and the friends door is the Pages Function, which is already in front of it.https://albums.traveller.org/ answers with the friends sign-in
(a 302 to /friends/, server: cloudflare); signed in as a friend, an
unlisted album opens there by both spellings — /countries/<country>/<album>
and with the trailing slash — and its country page and the home list it; and
https://www.traveller.org/albums/ forwards to the albums host. Nothing changes
before this step: the site’s pages carry the nav item and the tree from the first
Publish after 2026-09-22, and the hostname simply does not answer until the domain is
attached.
/albums/ served on a second hostname by the same
Function that guards www, with the door in front of everything on that host. A second
Pages project (two deploys, two Publish flows, one set of data) was the alternative, and
the reason it lost is in Addendum 364.albums → traveller-org.pages.dev at “your DNS provider”, which read as
a to-do. It was not one: the zone is at Cloudflare, so Cloudflare had already written the
record itself — the certificate for albums.traveller.org was issued in the same
minute and the edge answered on the hostname before the sheet closed. The sheet is what the panel
shows for the minute or two before its own check catches up; nothing is typed at Porkbun, ever.
Then you published, and the live site did not change for 27 minutes: the branch moved in
seconds, Cloudflare’s build of it took the rest. That is the same publish that always took
“about two minutes” before — so a Publish that has not landed after five minutes
is slow, not broken, until the deployment list at
Workers & Pages
→ traveller-org says otherwise. Verified from outside once it landed: / and an
album path both 302 to /friends/, robots.txt is the tree’s
(Disallow: /), www.traveller.org/albums/ 301s to the host, the friends’
data answers 401 without a cookie, and www’s nav carries the item.FRIEND_KEY you set at the door’s launch — add
two, both encrypted:RESEND_API_KEY — a new key made at
resend.com → API Keys, the fourth on the domain
(pipeline, contact Worker, DMARC Worker, and now the door), so this one can be rolled or revoked
without touching the others.TURNSTILE_SECRET — the same secret the contact form uses
(the traveller-contact Worker’s copy): the door’s card draws the contact
form’s widget, whose site key already rides in the build as
PUBLIC_TURNSTILE_KEY. Copy it from
Cloudflare → Turnstile →
the widget → its secret key (the Worker’s settings page does not show a secret back).
This is not a second Turnstile key on the list: PUBLIC_TURNSTILE_KEY, already
there, is the widget’s public half (the page draws the check with it) and stays as it
is; TURNSTILE_SECRET is the same widget’s secret half (the Function
verifies the check with it). One widget, two halves, both on the list — you asked exactly
this on 2026-09-22 with both already added, so (a) was done.albums.traveller.org. The widget draws nothing on a hostname it was not told about,
which reads as a broken card rather than a missing hostname; www.traveller.org is
already listed from the contact form.albums.traveller.org on the widget beside www and the apex (three
hostnames, ten allowed). (c) is the next Publish; until it lands the card answers not switched
on yet.www.traveller.org/friends/ and
albums.traveller.org/friends/ both show the Need access? Ask Bradley line under
the password form, which opens to a short form with the robot check drawn inside it; asking from it with your own name lands an email in your inbox
headed … is asking for friends access; its button opens a page naming you; pressing
Approve sends a second email whose button opens the site signed in. Until (a) is done the
card answers “Asking for access is not switched on yet” — it never
pretends to have sent.
FRIEND_KEY, which only the Pages
Function has. Two secrets on the project is the whole cost; the alternatives are in Addendum
365.cpanel@traveller.org, that could prove neither DKIM nor
SPF. Gmail refused each one (550-5.7.26 … sender is unauthenticated), the
bounces piled into your spam folder, and the DMARC Worker raised the alarm on each day's
report. Nothing was spoofing the domain — it was our own half-retired host.DNS DCV: No local authority: “traveller.org”; HTTP DCV:
The system queried for a temporary file at
“http://www.traveller.org/.well-known/acme-challenge/…”, … responded
with the following error: 404 (Not Found). … The domain “traveller.org”
resolved to an IP address “104.21.2.180” that does not exist on this
server.biz292.inmotionhosting.com/cpanel — mail.traveller.org is no
longer a working way in, even though the name still resolves and still holds a certificate).
Tick the eight failing rows and press Exclude Domains from AutoSSL:
traveller.org, www, webmail, webdisk,
cpcontacts, cpcalendars, autodiscover,
autoconfig. Leave mail and cpanel included
— they still resolve to InMotion, so they still validate, and they are the names the
certificate is actually for. Then press Run AutoSSL once, rather than waiting for the
next scheduled run to notice.mail/cpanel show a new expiry date — a reissued
certificate, not a silenced warning. Then: no new bounces in spam within a day, and the
DMARC alerts stop within two (the reports cover a window that has already closed, so one
more alert after the fix is expected and is not a failure).
webmail, webdisk, cpcontacts,
cpcalendars, autodiscover and autoconfig do not
resolve at all — those records were never recreated in the Cloudflare zone.
Excluding them costs nothing, because no traffic for any of the eight has reached this
server since 19 August.traveller.org that proves nothing; any other cPanel notification would
fail identically. Only step 24 ends that — and until it does, the DMARC policy stays
at p=none, because enforcement would fall on our own sender first. The
reasoning is on the Pipeline page.Work that finished outside the numbered steps, kept as the record.
brarob@gmail.com and
reynaldh@gmail.com). Done 9 October: you confirmed reynaldh@gmail.com is in the traveller admin access group the admin application’s policy includes, added ACCESS_AUD_ADMIN and UPLOAD_KEY, and the Publish of 9 October (build 1acfa470) put the site’s half live. door-check said ON and the admin’s bar named your address. The phone page answered not found at first; that was fixed and published the same day (build b6c1a86b) and is open item 1’s check. Stage two is open item 1 too. What follows is the item as you worked it. On
www.traveller.org, every admin page, the upload pages and the photo controls ask
Cloudflare who you are instead of asking for the passphrase. Three settings turn it on, all in
Cloudflare, and until they are in nothing changes — the passphrase keeps working everywhere,
so there is no order in which this can lock you out.
www.traveller.org/admin, not the guides one) and
its policy. If reynaldh@gmail.com is not under Include already, add it there the way
you added your own address at step 21 (Include rows are ORed — any address anywhere in Include
gets in). The guides application already lists him (14 Sept); this repository does not record
whether the admin one does. Remember what the list opens: the whole admin, Publish included.ACCESS_AUD_ADMIN, type Text, value the tag. This also switches on
the admin’s second lock, which the guides have had since 3 Oct.UPLOAD_KEY, type Secret, value the upload passphrase you use today —
the same value the Worker holds. The site adds it to each call on your behalf; no page ever sees it
again.www.traveller.org/admin/.
The bar at the foot of the menu reads Signed in with Cloudflare as your address, and no page
asks for the passphrase. The upload pages have new addresses behind the same sign-in:
www.traveller.org/admin/api/ on a
computer and www.traveller.org/admin/api/m/
on the phone — add the phone one to the home screen in place of the old icon.
www.traveller.org/admin/door-check/
says ON and whose pass it checked. Tell a session what you see.ACCESS_AUD_ADMIN on the same settings page and Retry
deployment, as for the guides. Until stage two, the test site’s admin still takes the
passphrase, so Publish is reachable there throughout.
Stage two, once you say it works: the passphrase comes off the web pages altogether —
the test site’s admin points you to www (your choice: live site only), the old upload addresses
forward to the new ones, and the Unlock the live site path retires. The iPhone Shortcut keeps
its passphrase (your choice); it never used these pages.FRIEND_KEY as a secret (your report, 2026-10-09). It had been typed
Text on the Pages project’s Variables and secrets, so its value showed on screen;
you re-added it as Secret with the same value, so no friend was signed out and no link sent
was voided.delete-zips run emptied zips/ in the bucket
and the old zip addresses answer 404. Published 9 October (build b6c1a86b). How it works
is on the Pipeline page.migration/backup-pages.txt; how it was made and how the rules work are
on the Pipeline page. Folders that were never
the travel site (portfolio, hiole, winequest,
ebay, insurance, staging and a few small ones) stay 404, as
decided. Live: it was on the site by the Publish of 1 October.trave127_wrdp1) and its old staging copy (trave127_wp14)
— hold 161 published letters, and every one is on the site, matched by id, each within
15% of its original length, with no longer earlier revision anywhere; the two letters this page once
said had resisted migration are not missing. Also there: a 2024 draft page for an Almar Resort studio
rental, two 2012 reader comments, and 24 media uploads the letters never used. The file list
(184,383 entries) holds the old website and 124,340 files of old mailboxes — keep the
backup for those alone. What it found is in the open list above.images.traveller.org, and only then switch the r2.dev address
off. The custom domain is attached and Active (your screenshot, 2026-09-24), but the live
site still asks the bucket’s pub-…r2.dev address for every photograph,
because PUBLIC_R2_BASE on the Pages project still names it — step 14’s
unfinished half. Do not press Disable on the Public Development URL yet: it would break
every photograph on the live site, and five admin pages, the staging workflow and the pipeline
also have that address written in. The order: (1) a session moves those hard-coded addresses to
images.traveller.org (ask for it); (2) you change PUBLIC_R2_BASE to
https://images.traveller.org at
Workers
& Pages → traveller-org → Settings → Variables and secrets,
Production; (3) Publish; (4) a session checks that the live pages ask for
images.traveller.org and nothing asks for r2.dev; (5) then Disable, at
R2
→ traveller-images → Settings → Public Development URL.images.traveller.org, checked first to serve the same bytes, the
same etag and the same year-long cache rule as r2.dev. (2) and (3) done by you, 2026-09-25 —
PUBLIC_R2_BASE changed and published. (4) done the same night, once
Cloudflare’s build landed (00:42 UTC): the home page, an album, the country index, a country,
the journal, the banknotes, the films and both friends doors ask images.traveller.org
and nothing asks r2.dev; a sample photograph loads from the new host. (5) done by you, 2026-09-25:
the Public Development URL is off — the old address now answers 401 while
images.traveller.org serves photographs and thumbnails 200, checked the same
hour. Step 14 is finished at last./build.json, without
which the new retired-file sweep deletes nothing (Addendum 375). From now on a Remove or Rotate
leaves the live album as it was until you publish — never broken.upload.traveller.org (your call, 2026-09-14). Declared as a Worker
custom domain in wrangler.toml and created by the deploy that already
runs on every push — no dashboard, no hand-written DNS record, no credential in the
conversation. Nothing was clicked, which is the point: the dashboard cannot do this.
Adding it there puts you in Cloudflare’s Connect your domain flow —
add-a-new-zone onboarding, which will not register a subdomain of a zone it already
hosts — and it spins on “processing” and gives up silently, which is
exactly what happened on 2026-08-20.
/ and
/m both serve the uploader carrying that deploy’s own build stamp,
/galleries answers 403 with no passphrase (the Worker itself
talking), and the certificate verifies. The run was green on the first push, so nothing had
to be added to the token. (Corrected 2026-11-11: this used to say the token “already
carried” a DNS · Edit row. It does not — read off the token’s own editor
that day, it holds Workers Routes and Workers Scripts only. The deploy created the custom domain
without DNS permission.) www.traveller.org/api/ is still what the admin login should read, because it is
the only same-origin one; traveller-upload.brarob.workers.dev still carries the
iPhone Shortcut, so the Shortcut needs no change.www.traveller.org/guides*,
allow-list of two (you and Reynald), one-time PIN, created by you on 2026-09-14 and
verified from outside the same day: the shelf and every page under it answer with the login,
the public site is unaffected. The app addresses went onto the cards in the same change,
which the door is what makes safe. The history, kept because the ordering mattered
(deferred by you on 2026-09-05: the shelf went live open, unlinked and unindexed but readable by anyone with
the address, and every guide is written as if public). The door is a Cloudflare Access
application, and it locks the shelf with no change to the site. In the Zero Trust dashboard,
Access → Applications → Add → Self-hosted: domain www.traveller.org, path
guides (covers /guides/ and everything under it), one policy
Allow with an email allow-list of just your address, one-time PIN as the login
method — a second application, not a new path on the admin one, so its list
can later take an app’s users without opening the admin to them (owner’s
choice: just you for now, widen later). Then check that a private window asking for
www.traveller.org/guides/ is answered with the PIN login, not the shelf.
That rule — nothing private on the shelf until the door exists — held until
2026-09-14, and is why the addresses waited.) Separately, and not
waiting on the door: the sync’s read token exists — you created it on
2026-09-05, under your own name for it, CENTRAL_DOCS_GIT_TOKEN (credentials
table on the pipeline page) — so the
nightly sync can read the app repositories.www.traveller.org, and the site also answers at its own Pages
address, traveller-org.pages.dev, where no door stands — on 2026-10-01
traveller-org.pages.dev/guides/ served the shelf to anyone, every app address on
it. Already closed, with nothing for you to do: from the next Publish, a request for
/guides or /admin on any address but www is sent to
www, where the door is. The second lock is the amapas353.com one: the site
itself checks the signed pass that Access hands every visitor it lets in, so even a request
that found another way round is refused. It switches on when the site is told the guides
door’s audience tag — one value, copied from Cloudflare.
www.traveller.org/guides, not the admin one) → Configure → the
Additional settings tab → copy Application Audience (AUD) Tag. This is the
path you walked for amapas353.com’s admin on 25 September (its Launch Book step 14).
It is a long string of letters and digits; it is not a secret.ACCESS_AUD_GUIDES, type Text, value the tag.www.traveller.org/guides/door-check/
and sign in if asked. It says ON and names the email whose pass it just checked, or
OFF if this deployment has no tag. Read it there and not on the shelf itself: the shelf looks
exactly the same either way, which is why this page exists (3 Oct — your “Behind
Cloudflare login” and the shelf appearing were both true and still could not tell on from off).
A page reading “This page is behind a sign-in” instead means the lock refused
you; its last line says why. Tell a session what it says.ACCESS_AUD_GUIDES on the same settings
page, then on the Pages project’s deployments
use Retry deployment on the newest one — it builds again and reads the variables;
Rollback does not. The shelf goes back to the edge door alone, which is where it is
today. Publish keeps working throughout: the admin has its own door and its own switch.
ACCESS_AUD_ADMIN, the admin application’s
tag, same steps). It is not part of this item because a mistake there locks the Publish
button too, and the way back is then only the dashboard’s Retry deployment. Worth
doing once the guides lock has run cleanly for a while, or not at all: the admin’s
pages are already visible on the test site, so its second lock protects less than the
shelf’s does.
<something>.traveller-org.pages.dev), serving the code
it was built with — so deployments from before this change still serve their shelf
openly to anyone who has that exact address. They are not listed anywhere public and the
names are random, but they exist. A session cannot list them (the Cloudflare token in
GitHub cannot read Pages). Settling it is a look at the
deployments
list: either delete the old preview deployments, or put an Access application over the
preview addresses. Not urgent; say which you prefer and a session will write the steps.FRIEND_KEY on the Pages project and deployed on 2026-08-28. Rotating the
password (which signs every friend out) is one edit to that same setting followed by a
deploy; the manual’s Friends
section says what to send people.luxembourg/1998, whose country has no high-res
batch yet.