Skip to content

feat: add create-harp CLI to provision GCP deployments - #176

Open
anishalle wants to merge 1 commit into
mainfrom
feat/create-harp-cli
Open

anishalle wants to merge 1 commit into
mainfrom
feat/create-harp-cli

Conversation

@anishalle

Copy link
Copy Markdown
Collaborator

What

A zero-dependency Node CLI at tools/create-harp/ that stands up a production HARP deployment on Google Cloud from a fork. It automates everything our harp-test audit and setup screenshots did by hand:

  • Project: creates the project and links billing.
  • Access: enables the APIs and sets up IAM.
  • Storage and secrets: a private resume bucket with CORS built from the service URL, SENDGRID_API_KEY in Secret Manager with runtime access, and an Artifact Registry repo with a cleanup policy (keeps the newest 5 images).
  • Cloud Run: the service with its full env contract.
  • Deploy: migrations (golang-migrate or Docker), then a deploy-on-push Cloud Build trigger and the first build.
  • Super admin: optional promotion after first sign-in.

It generates the values nobody should pick by hand (VAPID pair, AUTH_BASIC_PASS, PUBLIC_API_KEY) and reuses deployed values on re-runs, so nothing rotates silently.

It stops and walks the user through the three browser-only steps, offering to open each page:

  1. The Google OAuth client.
  2. Installing the Cloud Build GitHub App on the fork.
  3. Syncing the fork if it's behind upstream.
node tools/create-harp/bin/create-harp.js --dry-run   # read-only; prints every change
node tools/create-harp/bin/create-harp.js

Also changed:

  • Linked from ADOPTING.md and claude.md.
  • Adds harp.deploy.json (non-secret answers) to .gitignore.
  • Adds a create-harp-audit CI job.

Why

Adopting HARP meant repeating ~90 console clicks. That's where the original setup went wrong: a https://https:// CORS origin, and a first deploy that failed on a missing secretAccessor grant. The CLI makes the setup repeatable and idempotent.

Safety

  • Idempotent: every step checks existing state before changing anything. A re-run only fills gaps.
  • Account-pinned: every gcloud call carries --account. --account <email> aborts before any project call if the active account differs. It never changes the user's default gcloud project or config.
  • Secrets stay in memory: they're never printed or written to disk. The one exception is a 0600 env file in a private temp dir that lives only for the gcloud run deploy call.
  • Propagation retries: freshly enabled APIs and new IAM grants are retried.

Testing

  • Unit tests plus a full end-to-end run against a stateful fake gcloud (test/fake-gcloud.mjs). Covers the account guard, dry run making zero mutations, the GitHub-not-connected pause, a re-run changing nothing, IAM propagation, a taken bucket name, and the trigger-update rejection below. 14/14 passing, no network.
  • A real run on a fresh project (harp-cli-test-0926) with a temporary Neon DB and dummy SuperTokens/SendGrid values:
    • All 50 migrations applied.
    • The build succeeded in about 6.6 minutes and the login page rendered.
    • Three runs against the same project were idempotent.
    • The project was deleted afterwards.
  • Bugs the real run found, now fixed and covered by tests:
    • Artifact Registry returned IAM_PERMISSION_DENIED to the owner right after API enablement.
    • gcloud builds triggers update github rejects triggers with an inline build config (INVALID_ARGUMENT), so substitutions are now changed via describe + triggers import.

Reviewer notes

  • Deliberate differences from the audited project are listed in the README:
    • Only 10 directly-used APIs are enabled.
    • Public access prevention is enforced.
    • Service-agent bindings are not re-added.
    • VITE_GOOGLE_AUTH_ENABLED is passed as a Docker build arg; as a runtime env var it had no effect.
    • Migrations use Neon's direct endpoint instead of the pooler.
  • Not published to npm. create-harp is taken, so package.json uses @hackutd/create-harp. Publishing needs the npm org.
  • Out of scope: CLIENT_IP_HEADER defaults to CF-Connecting-IP. On bare Cloud Run without Cloudflare, clients can set that header themselves and get around the per-IP rate limit. This PR leaves it unchanged.

🤖 Generated with Claude Code

Zero-dependency Node CLI that stands up HARP on Google Cloud from a fork:
project, billing, APIs, IAM, GCS bucket with CORS, Secret Manager,
Artifact Registry, Cloud Run, migrations, and a deploy-on-push Cloud
Build trigger. Idempotent, with --dry-run and an --account guard.
Tested end to end against a real project and a fake gcloud in CI.
@anishalle
anishalle requested a review from balebbae September 26, 2026 23:12

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant