GCP project consolidation runbook — xlab-co as canonical umbrella

Date: 2026-05-20 · Owner: Dan (human-UI work) + Claude (toolkit/code) · Estimated effort: ~45-60 min when ready · Status: Runbook drafted; execute on a future deliberate session

Dan, 2026-05-20:

“i think we are drifting by adding a new one, we need to prune GCP for dare to one account, having multiple seems like it’s duplicating our service token surface” “it should really be xlab-co as the umbrella, unless you name it differently, or dare?” “easy path — draft the consolidation runbook”

This runbook is the deliberate-session playbook for consolidating all portfolio-internal Google Cloud work into ONE canonical project: xlab-co. The current state has 5+ projects with overlapping purposes — each carrying its own API keys, OAuth client IDs, service accounts, quota counters, and billing linkage. The destination is one project, scope-aligned with the existing GitHub umbrella org.


Current state — what we’re consolidating

Active GCP projects under the dare.co.uk GCP organisation as of 2026-05-20:

Project ID Type Likely role (audit confirms) Disposition
dare-seo-audit Project Newer dare-SEO container. PSI + CrUX enabled today. No API keys yet (wizard cancelled). Migrate to xlab-co → retire
seo-darecouk Project Older dare-SEO container, starred = previously the go-to. GSC API + OAuth from this morning likely live here. Migrate to xlab-co → retire
audrey-experiments Project Audrey-side experiments. May hold API keys / OAuth. Migrate to xlab-co → retire
CloudFlare-Admin-Login Project CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF-related early experiment. Probably empty/dormant. Audit → retire if empty
API-darecouk-YouTube Project YouTube Data API + a key (likely powering dare_video_audit.py etc) Migrate to xlab-co → retire
dare.co.uk Organization GCP org that contains all the above. Not a project; keep. Keep

Target state: ONE project, xlab-co, holding every Google API and credential the portfolio needs. The other five projects retired over the 30-day soft-delete window.


The naming decision (locked 2026-05-20)

Canonical project: dare-seo-audit — for now. Long-term: rename/align with xlab-co once the broader xlab-co naming has finished cascading through everything (GitHub org, repos, identity surfaces).

Dan’s framing:

“going with dare-seo-audit as the carrier of traffic, the one we retain and build upon…” “dare-seo-audit as the MASTER (for now, until we can align it with xlab-co naming that is cascading through everything)”

This is a deliberately staged decision:

This staging is pragmatic: - dare-seo-audit already has PSI + CrUX enabled today (less immediate rework) - Avoids migrate-everything-twice (now → xlab-co AND legacy → xlab-co) - The principle (consolidate to ONE canonical) is preserved today - The xlab-co rename becomes a SINGLE Phase-2 motion when the broader xlab-co naming alignment is happening anyway

Scope of dare-seo-audit (the MASTER, for now): - All dare.co.uk Google API work (PSI, CrUX, Search Console, YouTube Data, Indexing, etc.) - Portfolio-internal cross-domain work without a dedicated home (e.g. weather APIs for pa.gf.cx) - NOT audrey-specific work — that stays in audrey-experiments-496912 - NOT client engagements — those get their own per-client GCP

Per feedback_gcp_one_project_per_surface.md (banked today).

Two-phase rename plan

Phase 1 — TODAY’s decision (executing): - dare-seo-audit = MASTER - Retire 8 legacy projects INTO it (per runbook below) - All new Google API work lands here

Phase 2 — when xlab-co naming cascade completes (parked): - Rename or migrate dare-seo-auditxlab-co (or xlab-co-portfolio) - Triggered by: GitHub org rename if any, or when 3+ surfaces explicitly reference xlab-co as the canonical brand - One-time migration ceremony (Phase 1 already consolidated to ONE source, so Phase 2 is only ONE project to rename, not nine) - Update all op:// references in scripts (sed-replace pass + sibling sync)

The point of the staging: do the high-leverage consolidation now; defer the cosmetic rename until the naming alignment is happening anyway.


Pre-flight — survey the legacy state

Before any migration, enumerate what’s in each legacy project. This identifies what’ll need re-minting + what can be safely retired.

Option A — Web UI (slow but clickable):

For each legacy project: 1. Switch to the project in the picker 2. Visit APIs & Services → Enabled APIs & services → screenshot or list 3. Visit APIs & Services → Credentials → list each API key + OAuth client + service account 4. Visit IAM & Admin → Service Accounts → confirm 5. Visit Billing → note billing-account linkage

Option B — gcloud CLI (fast, ~5 min total):

# Install gcloud once if not present
brew install --cask google-cloud-sdk
gcloud auth login                    # opens browser, sign in as dan@dare.co.uk

# Enumerate every project's enabled APIs + credentials
for p in dare-seo-audit seo-darecouk audrey-experiments-496912 cloudflare-admin-login api-darecouk-youtube; do
  echo "═══════════════════════════════════════════════════════════════"
  echo "  $p"
  echo "═══════════════════════════════════════════════════════════════"
  echo "  Enabled APIs:"
  gcloud services list --enabled --project=$p 2>/dev/null | tail -n +2 | awk '{print "    " $1}'
  echo "  Service accounts:"
  gcloud iam service-accounts list --project=$p 2>/dev/null | tail -n +2 | awk '{print "    " $2}'
  echo "  API keys (need API Keys API enabled to list):"
  gcloud alpha services api-keys list --project=$p 2>/dev/null | tail -n +2 | head -10
done > ~/Downloads/gcp_legacy_audit_$(date +%F).md

Output lands at ~/Downloads/gcp_legacy_audit_<date>.md — read through, identify what we keep / re-mint / retire.


Step-by-step migration

Phase 1 — Create canonical xlab-co

  1. Cloud Console → project picker → New Project
  2. Project name: xlab-co (fallback: xlab-co-portfolio)
  3. Organization: keep dare.co.uk (matches existing org)
  4. Click Create, wait ~10s, switch to it

Phase 2 — Enable the API set

Left sidebar → APIs & Services → Library, enable each:

Any others surfaced by the pre-flight audit get enabled too.

Phase 3 — Mint the canonical API key

APIs & Services → Credentials → + Create credentials → API key

When the success modal appears, immediately click Edit API key:

Show key → copy the AIza... value.

Phase 4 — Drop in 1Password

New 1Password item in Code Shared:

Reference shape: op://Code Shared/xlab-co Google API/api_key

Some APIs (Search Console, YouTube Data) need OAuth 2.0 client credentials, not just an API key. Re-create in xlab-co:

  1. APIs & Services → Credentials → + Create credentials → OAuth client ID
  2. Application type: Desktop app
  3. Name: xlab-co-portfolio-oauth-client
  4. Download the resulting client-secret JSON
  5. Move to a safe location (NOT a tracked repo) — recommended: ~/.config/google/xlab-co-client-secret.json
  6. Update ~/bin/gsc_oauth_setup.py to point at the new client-secret path
  7. Re-run the OAuth dance: bash gsc_oauth_setup.py --client-secret ~/.config/google/xlab-co-client-secret.json
  8. Capture the fresh refresh token → store in 1Password: - Title: xlab-co Google OAuth - Custom concealed field: refresh_token = the new token - Custom concealed field: client_id = from the JSON - Custom concealed field: client_secret = from the JSON

Phase 6 — Update toolkit-script references

For each script that reads Google credentials, update the op:// reference:

Script Old reference New reference
dare_perf_trend.py op://Code Shared/Google PSI API/api_key op://Code Shared/xlab-co Google API/api_key
gsc_pull.py (whatever it currently reads) op://Code Shared/xlab-co Google OAuth/refresh_token etc.
gsc_oauth_setup.py (legacy client-secret path) ~/.config/google/xlab-co-client-secret.json
dare_video_audit.py (if it uses YouTube Data) new reference

Single sed-replace pass once we know all the script names; sibling sync to dare-pipeline/scripts/ as usual.

Phase 7 — Verify

For each script touched in Phase 6:

  1. Run with --dry-run or equivalent to confirm it can read the new credentials
  2. Run a real pull (low-cost / read-only) to confirm the API call succeeds with the new key
  3. Diff the output against the most recent legacy-key output to confirm parity

If anything errors: check API restrictions on the new key (did we miss enabling one of the APIs?), check 1Password field IDs match the op:// reference syntax exactly.

Phase 8 — Retire legacy projects

For each legacy project, in this order (lowest-risk first):

  1. CloudFlare-Admin-Login — almost certainly empty; delete via Cloud Console → IAM & Admin → Manage Resources → select project → Delete
  2. API-darecouk-YouTube — confirm YouTube Data work has moved to xlab-co + scripts updated → delete
  3. audrey-experiments — confirm any audrey-specific OAuth/API work has moved → delete (note: project ID is audrey-experiments-496912 per the picker)
  4. dare-seo-audit — confirm PSI/CrUX work has moved → delete
  5. seo-darecouk — LAST, since this is the starred/older one. Confirm GSC API + OAuth have moved + scripts work → delete

Google holds deleted projects for 30 days before final purge — recoverable if we missed something. Cloud Console will show them in “Recently deleted” under IAM & Admin → Manage Resources.

Phase 9 — Documentation

  1. Update ~/Code/dare-co-uk/CLAUDE.md with a note: “All Google Cloud APIs for portfolio-internal work live in xlab-co. Project ID: xlab-co. Key reference: op://Code Shared/xlab-co Google API/api_key. OAuth reference: op://Code Shared/xlab-co Google OAuth/refresh_token.”
  2. Commit + push the CLAUDE.md update
  3. Add reference to the feedback_gcp_one_project_per_surface.md memo so future-Claude doesn’t re-derive

Risk profile

Risk Severity Mitigation
Legacy project deleted before all consumers migrated High if missed Pre-flight audit (Phase 0) lists everything; verify each script in Phase 7 BEFORE Phase 8 deletion; 30-day soft-delete is the safety net
OAuth refresh token lost during re-creation Medium Capture immediately on Phase 5 step 8; keep legacy refresh tokens in 1Password until verification complete
API restrictions miss an API the scripts need Low Phase 7 verification catches this; just edit the key + add the missing API
Project ID xlab-co already taken globally Low Try xlab-co-portfolio or xlab-co-toolkit as fallback (still mirrors org)
Billing-account linkage accidentally severs working service Low We’re free-tier — none of these projects should be on billing. Pre-flight confirms.

Rollback plan

Within 30 days of deletion:

  1. Cloud Console → IAM & Admin → Manage Resources → tab “Pending deletion”
  2. Find the deleted project → click → Restore project
  3. All APIs / keys / OAuth clients / service accounts restored
  4. Update scripts back to the restored project’s references

After 30 days: legacy projects are permanently purged. Anything not migrated by then is lost.


Estimated time + sequence

Phase Effort Can run in parallel?
0. Pre-flight audit (gcloud CLI) 10 min
1. Create xlab-co 2 min No
2. Enable APIs 5 min (5 APIs × ~1 min each) No
3. Mint API key + restrict 5 min No
4. Drop in 1Password 2 min No
5. Re-create OAuth + re-run dance 15 min No
6. Update toolkit scripts 10 min No (but sibling sync at end)
7. Verify each script 10 min Can parallelise script-by-script
8. Retire legacy projects 5 min (5 × 1 min, mostly clicking “Delete”) Can parallelise once Phase 7 is done
9. Documentation 5 min Can be done while verifications run

Total active work: ~60 min in a single deliberate session, or ~30-45 if some phases parallelise. Phase 0 audit can happen any time; it’s the only step that doesn’t make changes.


When to do this

Resume conditions:

  1. A new Google API gets enabled anywhere — that’s the moment to ask “does this go in xlab-co?” rather than reflexively creating a new project
  2. A 30-60 min slot opens with focus to do it deliberately
  3. A script needs a Google API touch (key rotation, new feature) — fold the consolidation in

Don’t rush this. Legacy projects keep working; the consolidation is hygiene, not crisis-response. Pick a calm session.


Sibling memories + cross-references


The aphorism

One umbrella per identity, one project per umbrella, one canonical key per project. Sprawl is a tax that compounds; consolidation is a session that buys back every hour of “which one was that in?” — forever.

Source: parked_sketch_gcp_consolidation_xlab_co_2026-05-20.md · Rendered 2026-05-20 16:21