Skip to content

Webhook Automation

Griffen Fargo edited this page Jul 10, 2026 · 1 revision

Webhook Automation (Push-to-Deploy)

Automatically deploy a stack when you push to its branch — either by polling origin on an interval, or by running an HTTP receiver that GitHub/GitLab calls directly.

Since: v0.32.0

Quick Reference

strut webhook poll --once                              # single check, good for cron
strut webhook poll --interval 30                        # long-running poll loop
strut webhook serve --port 9876 --secret "$WEBHOOK_SECRET"   # HTTP receiver
strut webhook install --mode poll                        # generate a systemd unit

Pick poll if you don't want to open an inbound port on the VPS. Pick serve if you want deploys to happen the moment you push, not on the next poll tick.

Poll Mode

strut webhook poll [--interval <sec>] [--branch <name>] [--stack <name>] [--once]
Flag Default Description
--interval <sec> 60 Seconds between checks (ignored with --once)
--branch <name> DEFAULT_BRANCH env var, else main Only deploy on pushes to this branch
--stack <name> all stacks Restrict to a single stack
--once Run a single check-and-deploy cycle, then exit — for cron instead of a long-running loop

Each cycle: fetches origin, diffs the new commits' changed files against the last-deployed SHA (tracked in .webhook-last-sha), maps those file paths to affected stacks, pulls the changes locally, then runs the normal release pipeline for each affected stack.

# Cron: check every 5 minutes
*/5 * * * * cd /path/to/project && strut webhook poll --once >> webhook.log 2>&1

Serve Mode

strut webhook serve --port <N> --secret <hmac-secret> [--branch <name>]
Flag Default Description
--port <N> 9876 HTTP listen port
--secret <hmac> Required. HMAC secret for signature validation
--branch <name> DEFAULT_BRANCH env var, else main Only deploy on pushes to this branch

Runs an HTTP receiver (via socat, forking a handler per connection) that validates GitHub's X-Hub-Signature-256 header — HMAC-SHA256(request body, secret), formatted as sha256=<hex-digest> — before doing anything. Requests with a missing or invalid signature get 401 Unauthorized and never reach the deploy path. A validated push to a matching branch triggers a poll cycle in the background.

Point your GitHub webhook (repo → Settings → Webhooks) at http://<vps-host>:<port>/, content type application/json, with the same secret you passed to --secret.

Running Persistently

strut webhook install generates a systemd service unit — it prints to stdout, you install it:

strut webhook install --mode poll --interval 60 > /tmp/strut-webhook.service
sudo mv /tmp/strut-webhook.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now strut-webhook
strut webhook install --mode serve --port 9876    # serve-mode unit instead

The generated serve-mode unit reads WEBHOOK_SECRET from .webhook.env at the project root — create that file before starting the service:

echo "WEBHOOK_SECRET=$(openssl rand -hex 32)" > .webhook.env
chmod 600 .webhook.env

Choosing Poll vs Serve

Poll Serve
Inbound port required No Yes
Deploy latency Up to one interval Seconds
Setup complexity Lower (no GitHub webhook config) Higher (webhook URL + secret in GitHub)
Good fit VPS behind a firewall/NAT, infrequent pushes Public-facing VPS, want fast turnaround

Both ultimately call the same underlying poll-and-deploy logic — serve just triggers it immediately instead of waiting for the next tick.

Related

Clone this wiki locally