Blame · Line-by-line history
DEPLOY.md
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 988380a | 1 | # Deploying Gluecron |
| 8ade77b | 2 | |
| 988380a | 3 | ## The green ecosystem |
| 8ade77b | 4 | |
| 988380a | 5 | Gluecron is part of a self-hosting triangle. Each service deploys or observes the others: |
| 8ade77b | 6 | |
| 988380a | 7 | - **Crontech** is the deployment platform for Gluecron (and everything else). |
| 8 | - **Gluecron** hosts the source code for Crontech, GateTest, and itself. | |
| 9 | - **GateTest** scans every push to Gluecron. | |
| 3644238 | 10 | |
| 988380a | 11 | Dogfood end-to-end. **Crontech is the deployment target.** Do not deploy Gluecron to Vercel or Hetzner — they are not part of this stack. |
| 3644238 | 12 | |
| 988380a | 13 | --- |
| 3644238 | 14 | |
| 988380a | 15 | ## 1. Prerequisites |
| 3644238 | 16 | |
| 988380a | 17 | 1. **Neon Postgres** — create a project at https://neon.tech and copy the pooled connection string. This becomes `DATABASE_URL`. |
| 18 | 2. **Crontech account** — tenant + service permissions for the repo. | |
| 19 | 3. **(Recommended) Anthropic API key** — https://console.anthropic.com. Everything AI-flavoured degrades to safe fallbacks without it, but you'll want this for the differentiator features. | |
| 3644238 | 20 | |
| 988380a | 21 | --- |
| 8ade77b | 22 | |
| 988380a | 23 | ## 2. Deploy via Crontech |
| 8ade77b | 24 | |
| 988380a | 25 | In Crontech, create a service pointing at this repo with: |
| 8ade77b | 26 | |
| 988380a | 27 | - **Build:** `bun install --production` |
| 28 | - **Release:** `bun run db:migrate` | |
| 29 | - **Start:** `bun run src/index.ts` | |
| 30 | - **Port:** `3000` | |
| 31 | - **Persistent volume:** mount `/data/repos` (bare git repos live on disk) | |
| 8ade77b | 32 | |
| 988380a | 33 | Route a subdomain (e.g. `gluecron.crontech.ai`) or a custom domain (e.g. `gluecron.com` via CNAME) to the service. |
| 8ade77b | 34 | |
| 988380a | 35 | Crontech handles TLS termination, rollouts, and restart policy. Treat it as the primary control plane — do not bolt on nginx, systemd, or Docker Compose orchestration. |
| 8ade77b | 36 | |
| 988380a | 37 | --- |
| 8ade77b | 38 | |
| 988380a | 39 | ## 3. Environment variables |
| 8ade77b | 40 | |
| 988380a | 41 | Full reference is in [`.env.example`](./.env.example); cross-reference BUILD_BIBLE §5.2 for anything not listed here. |
| 8ade77b | 42 | |
| 988380a | 43 | ### Required |
| 44 | | Variable | Purpose | | |
| 45 | |---|---| | |
| 46 | | `DATABASE_URL` | Neon Postgres connection string (pooled). | | |
| 47 | | `GIT_REPOS_PATH` | Where bare repos live on disk (e.g. `/data/repos`). | | |
| 48 | | `PORT` | HTTP port. Default `3000`. | | |
| 49 | | `APP_BASE_URL` | Canonical public URL, used when composing outbound emails and webhooks. | | |
| 8ade77b | 50 | |
| 988380a | 51 | ### Strongly recommended |
| 52 | | Variable | Purpose | | |
| 53 | |---|---| | |
| 54 | | `ANTHROPIC_API_KEY` | Unlocks AI review, triage, chat, incident responder, auto-repair, completions. | | |
| 55 | | `EMAIL_PROVIDER` | `log` (default, writes to stderr) or `resend` (production). | | |
| 56 | | `EMAIL_FROM` | Sender identity on outbound mail. | | |
| 57 | | `RESEND_API_KEY` | Required when `EMAIL_PROVIDER=resend`. | | |
| 58 | | `VOYAGE_API_KEY` | Upgrades semantic search to Voyage `voyage-code-3` embeddings; without it, a deterministic hashing embedder is used. | | |
| 59 | ||
| 60 | ### Integrations | |
| 61 | | Variable | Purpose | | |
| 62 | |---|---| | |
| 63 | | `GATETEST_URL` | Outbound push webhook. Default `https://gatetest.ai/api/events/push`. | | |
| 64 | | `GATETEST_API_KEY` | Bearer sent on outbound GateTest posts. | | |
| 65 | | `GATETEST_CALLBACK_SECRET` | Inbound bearer GateTest must present on result callbacks. See `GATETEST_HOOK.md`. | | |
| 66 | | `GATETEST_HMAC_SECRET` | HMAC secret for inbound GateTest callbacks (alternative to bearer). | | |
| 67 | | `CRONTECH_DEPLOY_URL` | Outbound deploy webhook. Default `https://crontech.ai/api/hooks/gluecron/push`. | | |
| 68 | | `GLUECRON_WEBHOOK_SECRET` | Bearer sent on the outbound Crontech deploy webhook. | | |
| 69 | | `CRONTECH_EVENT_TOKEN` | Bearer Crontech must present on `deploy.succeeded` / `deploy.failed` callbacks to `POST /api/events/deploy`. | | |
| 70 | | `CRONTECH_STATUS_URL` / `GLUECRON_STATUS_URL` / `GATETEST_STATUS_URL` | Platform-status endpoints surfaced in the `/admin/platform` widget. | | |
| 8ade77b | 71 | |
| 988380a | 72 | ### Operational flags |
| 73 | | Variable | Purpose | | |
| 74 | |---|---| | |
| 75 | | `AUTOPILOT_DISABLED=1` | Opt out of the 5-minute autopilot ticker (mirror sync, merge-queue processing, weekly digests, advisory rescans). Default: enabled. | | |
| 76 | | `DEMO_SEED_ON_BOOT=1` | Idempotently create a `demo` user plus three public sample repos (`hello-python`, `todo-api`, `design-docs`) on server start. Safe to leave enabled — a second run is a near-instant no-op. Site admins can also trigger a reseed manually from `/admin`. | | |
| 77 | ||
| 78 | --- | |
| 79 | ||
| 80 | ## 4. Database migrations | |
| 81 | ||
| 82 | Migrations are checked into `drizzle/` and run via: | |
| 8ade77b | 83 | |
| 84 | ```bash | |
| 988380a | 85 | bun run db:migrate |
| 8ade77b | 86 | ``` |
| 87 | ||
| 988380a | 88 | Hook this into Crontech's release phase so every deploy self-migrates. First-time bootstrap: the release command will pick up `0000_init.sql` through the latest migration in order. |
| 8ade77b | 89 | |
| 988380a | 90 | --- |
| 8ade77b | 91 | |
| 988380a | 92 | ## 5. Post-deploy checklist |
| 8ade77b | 93 | |
| 988380a | 94 | Verify these in order after the first deploy: |
| 8ade77b | 95 | |
| 988380a | 96 | - [ ] **Migrations ran** — `db:migrate` reported success in release logs. |
| 97 | - [ ] **`/healthz` is green** — returns `{"ok": true, ...}` with a 200. | |
| 98 | - [ ] **`/readyz` is green** — returns `{"ok": true}`, confirming DB connectivity. | |
| 99 | - [ ] **`/metrics` responds** — basic process snapshot is emitted. | |
| 100 | - [ ] **First admin bootstrap** — register the first account at `/register`. Per `src/lib/admin.ts`, while `site_admins` is empty the **oldest user** is treated as site admin. Register the intended admin account first so the bootstrap rule applies; subsequent admins can then be granted from `/admin/users`. | |
| 101 | - [ ] **Smart HTTP works** — create a repo at `/new`, then `git clone https://<your-host>/<user>/<repo>.git` round-trips. | |
| 102 | - [ ] **Push triggers the pipeline** — a `git push` fires GateTest, the secret scanner, and webhook fan-out (check `/:owner/:repo/gates` and `/:owner/:repo/settings/audit`). | |
| 103 | - [ ] **Custom domain TLS** — certificate issued and `APP_BASE_URL` reflects the canonical host. | |
| 104 | - [ ] **Autopilot state** — if you want the background ticker off, confirm `AUTOPILOT_DISABLED=1` is set; otherwise expect mirror syncs and weekly digests to fire on schedule. | |
| 8ade77b | 105 | |
| 988380a | 106 | --- |
| 8ade77b | 107 | |
| 988380a | 108 | ## 6. Operations |
| 8ade77b | 109 | |
| 988380a | 110 | ### Logs |
| 111 | Crontech streams stdout/stderr from `bun run src/index.ts`. Every request carries an `X-Request-Id` header; grep logs by that ID when tracing a report. | |
| 112 | ||
| 113 | ### Restarts | |
| 114 | Trigger from the Crontech dashboard. Rate-limit counters are in-memory and reset on restart — that is intentional. | |
| 115 | ||
| 116 | ### Rollbacks | |
| 117 | Roll back via Crontech's release history. Database migrations are additive; rolling back the service does not roll back the schema. If a migration needs reverting, write a new forward-migration. | |
| 118 | ||
| 119 | ### Backups | |
| 120 | Neon handles PITR + branch snapshots — configure retention in the Neon console. The bare repos on the persistent volume must be backed up separately (filesystem snapshot of `/data/repos`). See BUILD_BIBLE §2.6 for what still needs wiring on the observability side. | |
| 121 | ||
| 122 | --- | |
| 8ade77b | 123 | |
| 988380a | 124 | ## 7. Graceful-degradation matrix |
| 8ade77b | 125 | |
| 988380a | 126 | | Missing secret | Effect | |
| 127 | |---|---| | |
| 128 | | `DATABASE_URL` | App boots, `/healthz` returns 200, any DB-backed route returns 500. Do not deploy without it. | | |
| 129 | | `ANTHROPIC_API_KEY` | AI review, chat, triage, incident, completions, explain, test-gen, merge resolver all return deterministic fallback strings. Site remains fully usable as a plain git host. | | |
| 130 | | `GATETEST_API_KEY` | Outbound GateTest integration silently skipped. Local secret scanner + gate runner still execute. | | |
| 131 | | `RESEND_API_KEY` (with `EMAIL_PROVIDER=resend`) | `sendEmail()` still never throws; emails are logged instead of delivered. | | |
| 132 | | `VOYAGE_API_KEY` | Semantic search falls back to the deterministic hashing embedder. Quality drops; feature still works. | | |
| 8ade77b | 133 | |
| 988380a | 134 | Every missing integration follows the same rule: **never break the primary request path**. See BUILD_BIBLE §4.9 for the full invariant list. |