Commit36ec667
feat(vapron-deploy): match the platform deploy-agent's webhook contract
feat(vapron-deploy): match the platform deploy-agent's webhook contract Reconciling Gluecron's deploy-webhook SENDER with Vapron's actual receiver so pushing ccantynz/Vapron to Gluecron can deploy Vapron itself. Read Vapron's receiver code (services/deploy-agent/src/webhook.ts): the platform self-deploy agent — reached at https://vapron.ai/__deploy_webhook — expects the signature in the `X-Gluecron-Signature-256` header (sha256=<hex> HMAC of the raw body with its DEPLOY_AGENT_HMAC secret), a payload carrying `ref` + `after` (it ignores the rest), and only deploys `refs/heads/Main`. It's deliberately GitHub-webhook-compatible. Gluecron already sends a matching payload and signature FORMAT, but only under the `X-Gluecron-Signature` header name (which the *tenant* receiver at /api/hooks/gluecron/push reads). So `triggerVapronDeploy` now sends BOTH header names with the same value — one sender works for both Vapron receivers, no per-endpoint shim. Also fixed the .env.example VAPRON_* docs: the old VAPRON_DEPLOY_URL (/api/webhooks/gluecron-push) is a 404; corrected to /__deploy_webhook, and VAPRON_REPO corrected to the case-sensitive ccantynz/Vapron. This is the Gluecron-side code half. Remaining (pending SSH access to Vapron's app box at 149.28.119.158): generate one shared secret and set VAPRON_HMAC_SECRET (Gluecron) = DEPLOY_AGENT_HMAC (Vapron), set VAPRON_REPO + VAPRON_DEPLOY_URL in the box .env (now reaches the container via the env_file fix), point Vapron's deploy-agent poll remote at Gluecron + neutralize the GitHub drift, then a live end-to-end test push. typecheck 0 errors; vapron-deploy tests 24 pass; full suite 3110 pass, same 4 pre-existing unrelated fails.
3 files changed+32−1236ec667ca17f68380a2f7134a8190e4dcfd728b3
3 changed files+32−12
Modified.env.example+15−10View fileUnifiedSplit
@@ -31,16 +31,21 @@ GATETEST_HMAC_SECRET=
3131# ─────────────────────────────────────────────────────────────────────────
3232# Vapron (formerly Crontech) deploy integration. Legacy CRONTECH_* names
3333# are still honored as fallbacks for already-configured deployments.
34VAPRON_DEPLOY_URL=https://vapron.ai/api/webhooks/gluecron-push
35# BLK-016 — only fire the Vapron deploy webhook for pushes to this
36# `<owner>/<name>` (default `ccantynz-alt/vapron`). All other repo pushes
37# are ignored.
38VAPRON_REPO=ccantynz-alt/vapron
39# Shared HMAC secret used to sign the outbound Vapron deploy webhook.
40# Sent as `X-Gluecron-Signature: sha256=<hex>` of the JSON body. Must match
41# the secret configured on the Vapron deploy-agent. Unset → header omitted
42# and Vapron rejects with 401. (Legacy fallbacks: CRONTECH_HMAC_SECRET,
43# GLUECRON_WEBHOOK_SECRET.) Prefer setting this on /admin/integrations.
34#
35# To deploy VAPRON ITSELF from Gluecron (push to ccantynz/Vapron → Vapron
36# rebuilds), point this at the deploy-agent's Caddy-proxied webhook. The
37# OLD value below (/api/webhooks/gluecron-push) is a 404 — do NOT use it.
38VAPRON_DEPLOY_URL=https://vapron.ai/__deploy_webhook
39# Only fire the Vapron deploy webhook for pushes to this exact,
40# CASE-SENSITIVE `<owner>/<name>`. Self-hosted Vapron on Gluecron lives at
41# `ccantynz/Vapron` (capital V). Default `ccantynz-alt/vapron` will NOT match.
42VAPRON_REPO=ccantynz/Vapron
43# Shared HMAC secret used to sign the outbound Vapron deploy webhook. Sent
44# as BOTH `X-Gluecron-Signature` and `X-Gluecron-Signature-256` = sha256=<hex>
45# of the JSON body. Must equal the Vapron deploy-agent's `DEPLOY_AGENT_HMAC`.
46# Unset → header omitted and Vapron rejects with 401. (Legacy fallbacks:
47# CRONTECH_HMAC_SECRET, GLUECRON_WEBHOOK_SECRET.) Generate one strong value
48# and set it identically on both boxes; prefer /admin/integrations here.
4449VAPRON_HMAC_SECRET=
4550# Inbound bearer token Vapron MUST present on deploy.succeeded /
4651# deploy.failed callbacks to POST /api/events/deploy (Signal Bus P1 — E3/E4).
Modifiedsrc/__tests__/vapron-deploy.test.ts+6−1View fileUnifiedSplit
@@ -196,16 +196,21 @@ describe("hooks/post-receive — triggerVapronDeploy (BLK-016 sender)", () => {
196196 .update(sentBody)
197197 .digest("hex");
198198 expect(headers["X-Gluecron-Signature"]).toBe(expected);
199 // Also sent as X-Gluecron-Signature-256 (same value) so the platform
200 // deploy-agent (/__deploy_webhook), which reads the -256 header, accepts
201 // the exact same request as the tenant receiver.
202 expect(headers["X-Gluecron-Signature-256"]).toBe(expected);
199203 expect(headers["Content-Type"]).toBe("application/json");
200204 });
201205
202 it("omits X-Gluecron-Signature when no secret is configured", async () => {
206 it("omits both signature headers when no secret is configured", async () => {
203207 const { calls, fn } = captureFetch();
204208
205209 await triggerVapronDeploy(makeArgs(), { fetchImpl: fn, sleep: noSleep });
206210
207211 const headers = calls[0]!.init.headers as Record<string, string>;
208212 expect(headers["X-Gluecron-Signature"]).toBeUndefined();
213 expect(headers["X-Gluecron-Signature-256"]).toBeUndefined();
209214 });
210215
211216 it("sends Authorization: Bearer <VAPRON_API_KEY> when the tenant key is set", async () => {
Modifiedsrc/hooks/post-receive.ts+11−1View fileUnifiedSplit
@@ -584,7 +584,17 @@ async function triggerVapronDeploy(
584584 "X-Gluecron-Event": "push",
585585 "X-Gluecron-Delivery": cryptoRandomId(),
586586 };
587 if (signature) headers["X-Gluecron-Signature"] = signature;
587 if (signature) {
588 // Send BOTH signature header names so one sender works for both Vapron
589 // receivers: the tenant/customer webhook (/api/hooks/gluecron/push) reads
590 // `X-Gluecron-Signature`, while the platform self-deploy agent
591 // (/__deploy_webhook, services/deploy-agent) reads
592 // `X-Gluecron-Signature-256`. Same `sha256=<hex>` value; the deploy-agent
593 // is intentionally GitHub-webhook-compatible (see its webhook.ts), so no
594 // per-endpoint shim is needed beyond matching the header name.
595 headers["X-Gluecron-Signature"] = signature;
596 headers["X-Gluecron-Signature-256"] = signature;
597 }
588598 // Vapron tenant auth: API key rides as a bearer alongside the HMAC
589599 // signature (key = who is calling, signature = payload integrity).
590600 if (config.vapronApiKey)
591601