From 6a583a8572e0adc8d71fcc7e949544f882667a5d Mon Sep 17 00:00:00 2001 From: Kavi Date: Sat, 23 May 2026 04:29:26 -0400 Subject: [PATCH] fix(deploy): SHA normalization + kua-vault wrap in verifyStatelessRecreated; retire webhook mandatory check; add /api/v1/apps/registry endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - verifyStatelessRecreated(): strip sha256: prefix before comparing image SHAs (same fix already applied to runtime-status in 2551af4, completeSelfRecreate in 06852c2; now consistent across all three verify paths) - verifyStatelessRecreated(): add kua-vault wrap on compose images/ps calls (mirrors 2551af4 runtime-status fix; env interpolation was causing empty output for vault-injected apps, making the verify always fail with 'no running container') - start(): downgrade missing WEBHOOK_SECRET from fatal error to warning; the Forgejo webhook path is retired in favour of the admin API — handler stays but the startup guard no longer blocks kua-deploy from booting - Add GET /api/v1/apps/registry so kua-mcp-core can fetch the full app list over HTTP without depending on a filesystem path inside its container Co-Authored-By: Claude Sonnet 4.6 --- server.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/server.js b/server.js index 436bafa..1a15e49 100644 --- a/server.js +++ b/server.js @@ -636,8 +636,8 @@ ${detail}`); if (verifyMode === 'off') return { ok: true, results: [], skipped: true }; const results = []; for (const svc of services) { - const exp = await runOnServer(server, `cd ${deployDir} && docker compose images --quiet ${svc} 2>/dev/null | head -1`); - const cid = await runOnServer(server, `cd ${deployDir} && docker compose ps --quiet ${svc} 2>/dev/null | head -1`); + const exp = await runOnServer(server, `cd ${deployDir} && ${kvPrefix} docker compose images --quiet ${svc} 2>/dev/null | head -1`); + const cid = await runOnServer(server, `cd ${deployDir} && ${kvPrefix} docker compose ps --quiet ${svc} 2>/dev/null | head -1`); const expectedSha = (exp.stdout || '').trim(); const containerId = (cid.stdout || '').trim(); if (!containerId) { @@ -647,7 +647,8 @@ ${detail}`); const insp = await runOnServer(server, `docker inspect --format '{{.Image}}|{{.State.StartedAt}}' ${containerId}`); const [actualSha, startedAtStr] = (insp.stdout || '').trim().split('|'); const startedAt = new Date(startedAtStr || 0); - const imageMatch = !!expectedSha && actualSha === expectedSha; + const stripSha = (s) => (s || '').replace(/^sha256:/, ''); + const imageMatch = !!expectedSha && stripSha(actualSha) === stripSha(expectedSha); const freshlyStarted = !isNaN(startedAt) && startedAt >= deployStartTs; results.push({ service: svc, ok: imageMatch && freshlyStarted, @@ -1162,6 +1163,12 @@ fastify.post('/webhook/forgejo', async (request, reply) => { // --- Apps --- +// Registry dump — used by kua-mcp-core to discover all apps at startup +// without relying on a filesystem path that may not resolve inside its container. +fastify.get('/api/v1/apps/registry', async () => { + return registry; +}); + // List all apps fastify.get('/api/v1/apps', async () => { const results = []; @@ -1502,9 +1509,11 @@ async function completeSelfRecreate() { const start = async () => { try { - // Fail fast if webhook secret is missing in production + // WEBHOOK_SECRET is optional — the Forgejo webhook path is now retired in + // favour of the admin API (/api/v1/apps/:app/deploy). The handler remains + // but returns 503 when the secret is absent, which is safe. if (!DEV_MODE && !WEBHOOK_SECRET) { - throw new Error('KUA_DEPLOY_WEBHOOK_SECRET must be set in production — refusing to start'); + fastify.log.warn('KUA_DEPLOY_WEBHOOK_SECRET not set — /webhook/forgejo will return 503. Set the secret to re-enable Forgejo push triggers.'); } await loadRegistry(); await loadHistory();