feat(webhook): per-project and per-site webhook URLs
Build / build (push) Successful in 10m25s

Replace the single global webhook secret with entity-scoped secrets stored
on each project and static site. Webhook-driven project autocreate is
removed — projects must exist before their URL can trigger deploys.

Also wires static-site webhooks (sync_trigger=push|tag), turning the
previously inert "push" trigger into a functional one: POST the site's
webhook URL from a Git provider and Tinyforge re-syncs on matching refs.

- Adds webhook_secret columns + unique indexes to projects and static_sites
- Per-entity GET/regenerate endpoints under /api/projects/{id}/webhook
  and /api/sites/{id}/webhook (admin-only)
- Removes /api/settings/webhook-url and the global webhook panel
- Reusable WebhookPanel Svelte component on both detail pages, i18n in en/ru
- Tests for matcher (siteRefMatches, ParseImageRef) and handler (project
  match/mismatch/404 and site push/manual/branch-skip)
This commit is contained in:
2026-04-23 15:18:19 +03:00
parent e08acf5c0e
commit 0632f512e6
21 changed files with 1119 additions and 363 deletions
+20 -1
View File
@@ -74,6 +74,8 @@
"noMatchingProjects": "No projects match your search."
},
"projectDetail": {
"webhookTitle": "Project webhook",
"webhookDesc": "POST an image reference to this URL from your CI pipeline to trigger a deploy. Stage routing uses each stage's tag pattern.",
"deleteProject": "Delete Project",
"envVars": "Environment Variables",
"volumes": "Volume Mounts",
@@ -570,6 +572,8 @@
"lastChecked": "Last checked"
},
"sites": {
"webhookTitle": "Site webhook",
"webhookDesc": "Point your Git provider's push webhook at this URL. Tinyforge will re-sync the site on matching refs (branch for push trigger, tag pattern for tag trigger). Send an empty body for an unconditional sync.",
"title": "Static Sites",
"addSite": "New Site",
"newSite": "New Static Site",
@@ -1099,7 +1103,22 @@
"title": "Integrations",
"outgoing": "Outgoing notifications",
"outgoingDesc": "Where Tinyforge posts deploy and alert events. Paste a webhook URL (Apprise, Discord, Slack, your own handler).",
"incoming": "Incoming webhook"
"incoming": "Incoming webhooks",
"incomingMovedDesc": "Inbound webhooks are now scoped per entity. Open a project or static site to view and rotate its webhook URL."
},
"webhookPanel": {
"copy": "Copy",
"copied": "Webhook URL copied to clipboard",
"copyFailed": "Failed to copy to clipboard",
"noUrl": "No webhook URL configured",
"loadFailed": "Failed to load webhook URL",
"regenerate": "Regenerate URL",
"regenerated": "Webhook URL regenerated",
"regenerateFailed": "Failed to regenerate webhook URL",
"regenerateWarning": "Regenerating invalidates the current URL. Update any CI pipeline or Git webhook that uses it.",
"confirmRegenerate": "Replace the current URL?",
"confirmYes": "Regenerate",
"confirmNo": "Cancel"
},
"settingsMaintenance": {
"title": "Maintenance",