README
¶
Eventic
Eventic was born out of pure frustration. Every GitOps tool out there assumes you're running Kubernetes in the cloud with a fancy service mesh, an ingress controller, and a fleet of managed runners. But what if you're not? What if your builds run on bare-metal servers in a closet? What if your deployment target is a VM behind a firewall that GitHub Actions will never reach?
There is no simple, lightweight solution for triggering on-prem or local builds and arbitrary automation based on GitHub webhooks — so we built one.
Eventic is a minimal, two-component system that bridges GitHub webhooks to any machine that can make an outbound WebSocket connection. No inbound ports required on your local network. No complex infrastructure. Just a relay server and a lightweight client.
Architecture
GitHub ──webhook──▶ [Eventic Server] ◀──websocket──▶ [Eventic Client] ──▶ run hooks
(public / cloud) (on-prem / local)
- Server — A lightweight relay that receives GitHub webhooks, validates signatures, and fans out events to connected clients over WebSocket.
- Client — A small daemon that connects to the server, listens for events matching its subscriptions, checks out the relevant repo/ref, and executes hooks defined in
.eventic.yaml.
Server
The server is a single static binary packaged as a Docker container. It exposes three endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/webhook/github |
POST | Receives GitHub webhooks (HMAC-SHA256 validated) |
/ws |
GET | WebSocket endpoint for client connections |
/healthz |
GET | Health check |
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
EVENTIC_WEBHOOK_SECRET |
Yes | — | HMAC secret configured in your GitHub webhook |
EVENTIC_CLIENT_TOKENS |
Yes | — | Comma-separated list of tokens that clients use to authenticate |
EVENTIC_LISTEN_ADDR |
No | :8080 |
Address and port to listen on |
Running with Docker
docker run -d \
--name eventic-server \
-p 8080:8080 \
-e EVENTIC_WEBHOOK_SECRET="your-github-webhook-secret" \
-e EVENTIC_CLIENT_TOKENS="token1,token2" \
nitecon/eventic:latest
Docker Compose
services:
eventic:
image: nitecon/eventic:latest
restart: unless-stopped
ports:
- "8080:8080"
environment:
EVENTIC_WEBHOOK_SECRET: "your-github-webhook-secret"
EVENTIC_CLIENT_TOKENS: "token1,token2"
GitHub Webhook Setup
- Go to your repository (or organization) Settings > Webhooks > Add webhook
- Set Payload URL to
https://your-server:8080/webhook/github - Set Content type to
application/json - Set Secret to the same value as
EVENTIC_WEBHOOK_SECRET - Select the events you want to receive (or choose "Send me everything")
Client
The client is a small, self-contained binary that runs as a systemd service on any Linux machine. It maintains a persistent WebSocket connection to the server, automatically reconnects with exponential backoff, and executes hooks when events arrive.
Installation
Download the latest release for your platform from the Releases page:
# Download the binary
curl -L -o /usr/local/bin/eventic-client \
https://github.com/nitecon/eventic/releases/latest/download/eventic-client-linux-amd64
chmod +x /usr/local/bin/eventic-client
Configuration
Create /etc/eventic/config.yaml:
relay: "wss://your-server:8080/ws"
token: "your-auth-token"
client_id: "build-server-01"
repos_dir: "/opt/eventic/repos"
subscribe:
- "myorg/myrepo"
- "myorg/infra-*"
| Field | Description |
|---|---|
relay |
WebSocket URL of the Eventic server |
token |
Authentication token (must be listed in the server's EVENTIC_CLIENT_TOKENS) |
client_id |
Unique identifier for this client |
repos_dir |
Directory where repos will be cloned and managed |
subscribe |
List of glob patterns to filter which repositories trigger hooks |
Systemd Service
Create /etc/systemd/system/eventic-client.service:
[Unit]
Description=Eventic Client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/eventic-client /etc/eventic/config.yaml
Restart=always
RestartSec=5
User=eventic
Group=eventic
WorkingDirectory=/opt/eventic
[Install]
WantedBy=multi-user.target
Enable and start:
sudo useradd -r -s /bin/false eventic
sudo mkdir -p /opt/eventic/repos /etc/eventic
sudo systemctl daemon-reload
sudo systemctl enable --now eventic-client
Hook Configuration
Hooks are defined per-repository in .eventic.yaml at the repo root:
hooks:
pre: "echo preparing..."
post: "echo done"
events:
push:
post: "make deploy"
pull_request:
post: "make lint"
pull_request.opened:
post: "claude -p 'Review this PR' --headless"
pull_request.synchronize:
post: "make test"
release.published:
post: "/opt/scripts/notify-release.sh"
issues.opened:
post: "claude -p 'Triage this issue' --headless"
Execution Order
For each event the client receives:
- Global pre hook (
hooks.pre) — runs before anything else - Event-specific pre hook (
events.<event>.pre) — runs before checkout - Git checkout — switches to the correct ref/branch/PR
- Event-specific post hook (
events.<event>.post) — runs after checkout - Global post hook (
hooks.post) — runs after everything
Event Matching
Hooks are matched with action specificity: pull_request.opened takes precedence over pull_request. If no action-specific hook exists, the event-level hook is used as a fallback.
Environment Variables
Every hook receives these environment variables:
| Variable | Description |
|---|---|
EVENTIC_REPO |
Full repository name (e.g., org/repo) |
EVENTIC_REF |
Git ref (branch name, tag, or PR ref) |
EVENTIC_EVENT |
GitHub event type (e.g., push, pull_request) |
EVENTIC_ACTION |
Event action (e.g., opened, synchronize) |
EVENTIC_SENDER |
GitHub username that triggered the event |
EVENTIC_PR_NUMBER |
Pull request number (0 if not a PR event) |
EVENTIC_DELIVERY_ID |
Unique GitHub delivery ID for tracing |
Fallback: Bruce Manifests
If no .eventic.yaml is found, the client will look for .deploy/deploy.yml and use it as a Bruce manifest for push events.
Building from Source
# Server
CGO_ENABLED=0 go build -ldflags="-s -w" -o eventic-server ./server/cmd/
# Client
CGO_ENABLED=0 go build -ldflags="-s -w" -o eventic-client ./client/cmd/
License
Apache License 2.0 — see LICENSE for details.