Skip to content

Northlight Content — Hosting Go-Live Runbook

SUPERSEDED (2026-07-10): the deployed chain is Gitea brightworks-sites → generated GitHub mirror (lusoris/thedesknook) → GitHub Pages, with Cloudflare as DNS/proxy only — see apps/ai/paperclip-dispatcher/files/northlight-org.json (production) and the northlight-publish skill, which are authoritative. The Cloudflare Pages plan below was not adopted; its AdSense/analytics/GSC sections still apply. Terraform Pages/web-analytics resources from this plan are orphans to repoint or remove.

Operator runbook to take Northlight Content (SFW tech/lifestyle/hobby network) from empty repo to a live, monetized, indexed site on Cloudflare Pages.

  • Host: Cloudflare Pages (free global CDN, best Core-Web-Vitals, already Cloudflare-managed). Git-integration builds from GitHub (github.com/lusoris/northlight).
  • Framework: Astro static build (Task A/B template).
  • IaC: the Pages project is Terraform (terraform/ in the k8s repo, provider cloudflare/cloudflare ~> 5.0 — 5.x = attributes, not blocks; account_id = var.cloudflare_account_id, zone via data.cloudflare_zone.main). No Cloudflare click-ops except the one-time GitHub OAuth authorization (§2), which cannot be done via API.
  • Secrets/tokens: Pages build env vars. PUBLIC_* values are non-secret (they ship to the browser). Nothing here is a SealedSecret — these live in the Pages project config, set once per go-live.

Legend for each step: [you] = manual operator action · [auto] = handled by terraform apply / Pages build · = where the value/secret goes.


0. Prerequisites

  • Astro template built and committed locally (Task A/B output in /home/kilian/dev/northlight-site).
  • Terraform Pages resource added under terraform/ (Task B): a cloudflare_pages_project.northlight with build_config (build command npm run build, output dir dist), source pointing at the GitHub repo lusoris/northlight on branch main. The PUBLIC_* / CF_ANALYTICS_TOKEN app-config values are set later in the Pages dashboard (they don't exist at provision time). A var.northlight_domain variable (default "") gates the custom-domain resource.

1. Create the GitHub repo + push the template

  • [DONE] github.com/lusoris/northlight is already created (private) and the Astro template is pushed to main, verified building on node 22. To re-push or update from the local copy at /home/kilian/dev/northlight-site:
cd /home/kilian/dev/northlight-site
git add -A && git commit -m "content: <change>"
git push origin main
  • Note: gitea (git.dev.cauda.dev/lusoris) is dev-home, but Cloudflare Pages builds from GitHub — the production remote for this repo is GitHub. Content agents push here (see the northlight-publish skill).
  • Code lives in lusoris/northlight on GitHub, branch main.

2. Terraform the Pages project + authorize GitHub

  • [you] terraform plan then terraform apply in terraform/. This creates the cloudflare_pages_project.northlight under var.cloudflare_account_id.
  • [auto] The project, build config, and production env-var scaffold are created by Terraform.
  • [you] One-time, dashboard-only (cannot be API'd): in the Cloudflare dashboard → Workers & Pages → the northlight project → connect to Git → authorize the GitHub app on the lusoris/northlight repo. This grants CF the OAuth permission to pull + build on push. After authorizing, trigger (or push) once to confirm the first production deployment goes green.
  • Pages project bound to the GitHub repo; builds fire on every push to main. PR/branch pushes get preview URLs at <branch>.northlight.pages.dev.

3. Point a domain

  • Default (no domain yet): the site is live at https://northlight.pages.dev. Fine for the dark/pre-launch build.
  • [you] When ready for a real domain, set var.northlight_domain (in the tfvars) to the hostname and terraform apply.
  • If the domain is a cauda.dev subdomain, Terraform can add the cloudflare_pages_domain + the DNS record via data.cloudflare_zone.main.
  • If it's a new apex domain, register it (separate registrar per the anonymity/jurisdiction split), add the zone to Cloudflare, then set var.northlight_domain and apply.
  • [auto] Terraform attaches the custom domain to the Pages project and provisions the CF-managed TLS cert. HTTPS is enforced by Pages.
  • var.northlight_domain → the cloudflare_pages_domain resource. Prod URL becomes https://<northlight_domain>/.

4. Enable Cloudflare Web Analytics

  • [you] Dashboard → Analytics & Logs → Web Analytics → add a site for the Pages hostname. Copy the beacon token it issues. (Privacy-first, cookieless, no CMP needed — chosen over GA4.)
  • [you] Set the token as a CF_ANALYTICS_TOKEN production env var on the Pages project — Settings → Environment variables in the CF dashboard (these app-config values are set post-launch and don't exist at IaC time, so they live in Pages env, not the Terraform that provisions the project). The template's Analytics.astro injects the CF beacon when this var is present.
  • CF_ANALYTICS_TOKEN → Pages prod env → browser beacon. Non-secret.

5. Apply for Google AdSense (after real content + traffic)

  • Gate: AdSense approval needs a site with genuine, original content and some organic traffic. Do this after the launch-readiness gate is GO and pages are indexed — not at day zero.
  • [you] Apply at adsense.google.com with the production domain. Google reviews; on approval you receive a publisher ID ca-pub-XXXXXXXXXXXXXXXX.
  • [you] Set PUBLIC_ADSENSE_CLIENT=ca-pub-XXXX… as a Pages prod env var (dashboard → Settings → Environment variables). The AdSlot component and the AdSense loader read it; with it unset, no ad markup renders (safe pre-approval).
  • [you] Fill public/ads.txt with the AdSense line Google provides: google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0. Commit + push. A correct ads.txt is required for fill/RPM — a missing one silently drops eligible demand.
  • PUBLIC_ADSENSE_CLIENT → Pages prod env; publisher line → public/ads.txt in the repo. Both non-secret.

6. Join Amazon Associates

  • [you] Enroll at the Amazon Associates program (associates-amazon) for the target locale(s); provide the live site. You receive a store/associate tag like northlight-20.
  • [you] Set PUBLIC_AMAZON_TAG=northlight-20 as a Pages prod env var (dashboard → Settings → Environment variables). The AffiliateLink component appends this tag to any amazon. product URL automatically (it detects Amazon by hostname — no merchant prop) — agents never hand-write it.
  • Note: Associates requires qualifying sales within the first 180 days to keep the account; drive real affiliate content before enrolling.
  • PUBLIC_AMAZON_TAG → Pages prod env → affiliate URLs. Non-secret.

7. Submit sitemap to Google Search Console

  • [you] Add the property in Google Search Console for the production domain. Verify ownership — easiest is a DNS TXT record, which you add via Terraform on data.cloudflare_zone.main (keeps DNS in IaC, no click-ops).
  • [you] Once verified, submit the sitemap URL: https://<domain>/sitemap-index.xml (Astro's @astrojs/sitemap emits this on build; draft pages are excluded).
  • [you] This is the flip-to-index moment: only after the launch gate is GO and noindex is removed site-wide should the sitemap be submitted. Capture the pre-index baseline first.
  • GSC verification TXT → data.cloudflare_zone.main DNS record (Terraform). Sitemap URL → submitted in the GSC UI.

Value / secret placement summary

Value Type Where it goes
Astro template code code github.com/lusoris/northlight (branch main)
var.northlight_domain tfvar Terraform → cloudflare_pages_domain
CF_ANALYTICS_TOKEN public env Pages prod env (dashboard)
PUBLIC_ADSENSE_CLIENT (ca-pub-…) public env Pages prod env (dashboard)
AdSense publisher line file public/ads.txt in the repo
PUBLIC_AMAZON_TAG (…-20) public env Pages prod env (dashboard)
GSC verification TXT DNS data.cloudflare_zone.main record (Terraform)

All PUBLIC_* values are non-secret by design (they render into the client). None are SealedSecrets. The only non-IaC step is the one-time GitHub OAuth authorization in §2, which Cloudflare requires through the dashboard.

Ordering rule

§1–4 can be done during the dark build. §5–7 are post-flip: AdSense and Associates want real content + traffic, and the sitemap is submitted only after the launch-readiness gate is GO and indexing is opened. See docs/ai/launch-readiness-gate.md.