diff --git a/.env.example b/.env.example index b95474c..cedfd4f 100644 --- a/.env.example +++ b/.env.example @@ -6,8 +6,8 @@ # arbitrary image-generation requests against the endpoint (which would burn # CPU and bandwidth). # -# Generate with: npx nuxt-og-image generate-secret -# (or any 32-byte hex string — the CLI just calls randomBytes(32).toString('hex')) +# Generate with: pnpm gen:og-image-secret +# (or any 32-byte hex string) # --------------------------------------------------------------------------- NUXT_OG_IMAGE_SECRET=<32-byte hex string> @@ -26,6 +26,7 @@ NUXT_DATABASE_URL=postgres://user:password@host.neon.tech/dbname?sslmode=require # --------------------------------------------------------------------------- # AT Proto OAuth client signing key (ES256 private JWK). +# # Generate with: pnpm gen:jwk # Paste the full JSON object on a single line below. # --------------------------------------------------------------------------- @@ -34,6 +35,7 @@ NUXT_ATPROTO_PRIVATE_JWK={"kty":"EC","kid":"...","crv":"P-256","x":"...","y":".. # --------------------------------------------------------------------------- # Application encryption key (KEK) — wraps SSH private keys and AT Proto # session blobs at rest. Base64-encoded 32 bytes. +# # Generate with: pnpm gen:encryption-key # --------------------------------------------------------------------------- NUXT_ENCRYPTION_KEY= @@ -41,14 +43,16 @@ NUXT_ENCRYPTION_KEY= # --------------------------------------------------------------------------- # Dashboard session password. Used by h3's `useSession` to seal the # `synchub-session` cookie. 32+ characters of entropy. -# Generate with: pnpm gen:encryption-key (any sufficiently long random string -# works; the base64 output of 32 random bytes is convenient). +# +# Generate with: pnpm gen:encryption-key +# (any sufficiently long random string works; the base64 output is convenient). # --------------------------------------------------------------------------- NUXT_SESSION_PASSWORD=<32+ char random string> # --------------------------------------------------------------------------- -# GitHub App credentials. After creating the App at -# https://github.com/settings/apps/new, copy: +# GitHub App credentials. If you are unsure where these values are in GitHub, +# see "Run it locally" in README.md for the full GitHub App setup walkthrough. +# After creating the App at https://github.com/settings/apps/new, copy: # - The numeric App ID (top of the App settings page). # - The webhook secret you set during creation. # - A generated private key (.pem). On Vercel, store with literal "\n" in @@ -78,6 +82,7 @@ NUXT_GITHUB_APP_INSTALL_URL=https://github.com/apps/synchub-to/installations/new # unauthenticated callers. Vercel auto-injects this as the `Authorization: # Bearer` header on cron invocations, so the name must be exactly CRON_SECRET # (not NUXT_-prefixed). Locally, `pnpm jobs:tick` reads the same var. +# # Generate with: pnpm gen:cron-secret # --------------------------------------------------------------------------- CRON_SECRET= diff --git a/README.md b/README.md index 8c0044b..509a1b8 100644 --- a/README.md +++ b/README.md @@ -19,44 +19,75 @@ branch, and tag will be mirrored to tangled. ## Run it locally -You will need [Node 24+](https://nodejs.org), [pnpm 10+](https://pnpm.io) -(`corepack enable`), a [Neon](https://neon.tech) database (free tier is fine), -and the [Smee CLI](https://smee.io) for webhook proxying -(`pnpm add -g smee-client`). +You will need [Node 24+](https://nodejs.org) and [pnpm 10+](https://pnpm.io). + +Start by creating a [Neon](https://neon.tech) project, then copy the pooled Postgres +connection string from the Neon dashboard. Keep it ready for the `NUXT_DATABASE_URL` +value in the env setup step below. + +Next, open [smee.io](https://smee.io), create a new channel, and copy that URL. +This URL is the webhook destination GitHub should send to during local +development. If you do not have the CLI yet, install it with +`pnpm add -g smee-client`. + +Then create a new GitHub App at +[github.com/settings/apps/new](https://github.com/settings/apps/new). On the +creation form, set GitHub App name to any unique name, Homepage URL to your +local origin (`http://127.0.0.1:3000`), Callback URL to +`http://127.0.0.1:3000/api/github/oauth/callback`, Setup URL to +`http://127.0.0.1:3000/connect`, and enable "Redirect on update". + +Keep user OAuth toggles at their defaults unless you specifically need them, but +keep webhooks active and set Webhook URL to your Smee URL with a webhook secret +you choose. + +Set repository permissions to `contents:read` and `metadata:read`. For event +subscriptions, use `push`, `create`, `delete`, and `repository`. +If you do not see those event checkboxes yet, save the app after adding the +webhook URL, then return to the "Permissions & events" page and select them +there. + +After creation, copy App ID, Client ID, generate a client secret, and generate +a private key (`.pem`) for the `NUXT_GITHUB_APP_*` values. + +If you need to install this app in +organizations/accounts other than the owner account, go to Advanced > Danger +zone and make the app public. + +Now create your local env file and fill all variables: ```bash -corepack enable -pnpm install -cp .env.example .env # fill in the values, see below -pnpm db:migrate -pnpm dev +cp .env.example .env ``` -`.env.example` documents every variable. Generate the secrets with the bundled -helpers: +Use these helpers for generated values: ```bash -pnpm gen:jwk # NUXT_ATPROTO_PRIVATE_JWK -pnpm gen:encryption-key # NUXT_ENCRYPTION_KEY and NUXT_SESSION_PASSWORD -pnpm gen:cron-secret # CRON_SECRET +pnpm gen:og-image-secret # NUXT_OG_IMAGE_SECRET +pnpm gen:jwk # NUXT_ATPROTO_PRIVATE_JWK +pnpm gen:encryption-key # NUXT_ENCRYPTION_KEY and NUXT_SESSION_PASSWORD +pnpm gen:cron-secret # CRON_SECRET ``` -The rest (`NUXT_DATABASE_URL`, the `NUXT_GITHUB_APP_*` values) come from your -Neon dashboard and a [new GitHub App](https://github.com/settings/apps/new). -The App needs `contents:read` and `metadata:read` permissions plus the `push`, -`create`, `delete`, and `repository` events, with its webhook pointed at your -Smee URL. Set the **Setup URL** to `/connect` (tick *Redirect on -update*) and the **Callback URL** to `/api/github/oauth/callback`; the -latter drives the user-OAuth that verifies a connecting user administers the -installation before any handle is bound. Copy the App's **Client ID** and a -generated **client secret** into `NUXT_GITHUB_APP_CLIENT_ID` / -`NUXT_GITHUB_APP_CLIENT_SECRET`. +After `.env` is complete, install dependencies and run migrations: + +```bash +corepack enable +pnpm install +pnpm db:migrate +``` -In separate terminals, proxy webhooks and drain the job queue: +Local development should run in three terminals: ```bash +# terminal 1 smee --url --target http://127.0.0.1:3000/api/github/webhook -pnpm jobs:tick # run as needed; in production Vercel Cron does this + +# terminal 2 +pnpm dev + +# terminal 3 +pnpm jobs:tick # run this to sync queued jobs while developing; Vercel Cron runs it in production ``` ## Deploy to Vercel diff --git a/package.json b/package.json index 6e25989..103f3a6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:studio": "drizzle-kit studio", + "gen:og-image-secret": "node -e \"console.log(require('node:crypto').randomBytes(32).toString('hex'))\"", "gen:jwk": "node scripts/gen-jwk.ts", "gen:encryption-key": "node -e \"console.log(require('node:crypto').randomBytes(32).toString('base64'))\"", "gen:cron-secret": "node -e \"console.log(require('node:crypto').randomBytes(32).toString('base64url'))\"",