Skip to content

Build Automation

Sia edited this page May 31, 2026 · 3 revisions

Build Automation (Cron + Webhook)

Two ways to fire builds without clicking "build" in the UI:

Mechanism Trigger
Cron schedule Background scheduler, ticks every 60 s
External webhook POST /api/webhooks/build/{projectId} from another system

Both are managed at /projects/{id}/automation (project detail → "⏰ Automation").

Looking for console prompt automation (repeat/sequence prompts on every turn completion)? See Prompt Automation.

Cron schedule

Supported expressions

The cron is intentionally minimal — no day-of-week, month, or day-of-month. Only:

Pattern Meaning
HH:MM Fire daily at this UTC-local time (e.g. 02:00)
*:MM Fire hourly at this minute (e.g. *:30)
*:* Fire every minute — test only

Full vixie-cron is on the roadmap; for now, more complex schedules can be synthesized by adding multiple entries (e.g. 3 rows for "Mon/Wed/Fri 02:00" isn't possible yet, but "02:00 daily" is one row).

Behavior

BuildScheduler ticks every 60 s. For each enabled schedule whose expression matches the current hour/minute, it:

  1. Calls BuildService.enqueueDebug(projectId, hub).
  2. Writes lastFiredAt (ISO instant).
  3. Records audit_log row: schedule.create / schedule.delete for lifecycle; the actual build firing shows up as a normal build.enqueue row.

Per-minute dedupe: if lastFiredAt starts with the current yyyy-MM-dd'T'HH:mm, the tick is skipped. So even if the scheduler ticks twice in the same minute (unlikely but possible), only one build fires.

Disabling

Each row has a "■ 비활성" toggle. Disabled rows are kept in the table for later re-enablement without losing the description.

External webhook

Setup

  1. On /projects/{id}/automation, click + 새 secret.
  2. The page reloads with a one-time yellow card showing the secret (32-byte URL-safe random, e.g. Vk-jWcFx_h2…). Copy it now — this is the only time it's shown.
  3. The DB stores only SHA-256(secret) hex, not the secret itself.

Calling the webhook

SECRET='Vk-jWcFx_h2_e1F9...'                # value you copied at step 2
SECRET_ID='abc123...'                       # from the "secret id" column
BODY='{"trigger":"github-actions","sha":"deadbeef"}'

SIGNATURE=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

curl -X POST http://<host>:17880/api/webhooks/build/my-app \
  -H "X-Vibe-Secret-Id: $SECRET_ID" \
  -H "X-Vibe-Secret: $SECRET" \
  -H "X-Vibe-Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  --data "$BODY"

Response on success:

{ "projectId": "my-app", "triggered": true }

Auth model

  • X-Vibe-Secret-Id — selects which DB row to verify against.
  • X-Vibe-Secretplaintext (TLS expected). Server computes SHA-256(plaintext) and compares to the stored hash with constant-time equality.
  • X-Vibe-Signature — optional HMAC-SHA256(secret, body) hex. If present and the body is non-empty, server verifies. Provides body integrity even if TLS is broken downstream.

Why both Secret and Signature

The design splits the two so:

  • A misconfigured reverse-proxy that strips bodies (some legacy gateways) still lets the trigger fire — the Secret header is enough.
  • A body-aware integration (GitHub-style) can add Signature for defense-in-depth without breaking the simpler case.

A pure HMAC-only design (sender knows secret, server only stores hash) is prevented by storing only SHA-256(secret) — verifying HMAC would require the plaintext. Storing plaintext is worse, so the X-Vibe-Secret transit is the compromise. HTTPS reverse proxy mandatory when exposing this endpoint outside LAN.

Multiple secrets per project

Add as many as you need — one per integration is the recommended pattern (e.g. github-actions, gitlab-ci, monitoring). Each has its own secret id and last-used timestamp, so revoking a single integration doesn't break the others.

Rate limiting

There is no built-in rate limit on the webhook. The underlying BuildService.enqueueDebug will queue every call; a runaway caller can fill the build queue. Mitigation:

  • Put the webhook endpoint behind a reverse proxy that does IP-based rate limiting.
  • Add a one-shot secret-rotation if you suspect leak.

Audit log

Action Audited as
Schedule create schedule.create (detail: cron)
Schedule delete schedule.delete
Webhook secret create webhook.secret.create (detail: name)
Webhook secret delete webhook.secret.delete
External trigger fires build webhook.build.trigger (detail: secretName)

Sees the Audit Log page for the schema.

Roadmap

  • Full vixie-cron expressions (0 2 * * 1-5 etc.) — drop-in library evaluation under consideration.
  • Per-secret rate limiting (e.g. max 10 builds/min/secret).
  • Per-schedule variant override (currently always debug).
  • HMAC-only mode (server stores plaintext encrypted with auth.pepper).

Clone this wiki locally