Thanks to visit codestin.com
Credit goes to Github.com

Skip to content
/ githook Public

Githooks routes GitHub/GitLab/Bitbucket webhooks through rules to Watermill brokers and provides a worker SDK for provider-aware event handling.

Notifications You must be signed in to change notification settings

yindia/githook

Repository files navigation

Githooks ⚡

Githooks is an event automation layer for GitHub, GitLab, and Bitbucket. It receives webhooks, evaluates configurable rules, and publishes matching events to your message broker via Watermill. The Worker SDK then consumes those events with provider-aware clients, so your business logic stays focused on outcomes, not plumbing.

Warning: This project is for research and development only and is not production-ready. Do not deploy it in production environments.

Why Githooks ✨

  • Unify SCM events without writing three webhook stacks. 🔗
  • Route events by rules (JSONPath + boolean logic) instead of hardcoding. 🧠
  • Use any broker supported by Watermill, with optional fan-out per rule. 📬
  • Act with real clients (GitHub App, GitLab, Bitbucket) inside workers. 🔐

Features ✅

  • Multi-Provider Webhooks: GitHub, GitLab, and Bitbucket. 🌍
  • Rule Engine: JSONPath + boolean rules with multi-match support. 🧩
  • Protobuf Event Envelope: Broker payloads use cloud.v1.EventPayload, with raw JSON preserved. 📦
  • Flexible Publishing: AMQP, NATS Streaming, Kafka, HTTP, SQL, GoChannel, RiverQueue. 🚚
  • Multi-Driver Fan-Out: Publish to all drivers by default or target per rule. 🧯
  • Worker SDK: Concurrency, middleware, topics, and graceful shutdown. 🧰
  • SCM Auth Resolution: GitHub App (JWT → installation token), GitLab/Bitbucket OAuth tokens stored on install. 🔑
  • Observability: Request IDs and structured logs. 🔎
  • Ship-Ready Assets: Docker Compose, examples, boilerplate, Helm charts. 📚

Common Use Cases 🚀

  • Release orchestration: Trigger CI/CD or internal workflows from PR merges.
  • Preview automation: Post preview links on PR/MR events across providers.
  • Compliance hooks: Enforce policy when branch protection or approvals change.

Table of Contents

Getting Started (Local)

  1. Start dependencies:

    docker compose up -d
  2. Run the server:

    Set the secret for validating GitHub webhooks and run the server with the local Docker config.

    export GITHUB_WEBHOOK_SECRET=devsecret
    go run ./main.go serve --config app.docker.yaml
  3. Run a worker:

    In another terminal, run an example worker that listens for events.

    go run ./example/github/worker/main.go --config app.docker.yaml
  4. Send a test webhook:

    Use the provided script to simulate a GitHub pull_request event.

    ./scripts/send_webhook.sh github pull_request example/github/pull_request.json

    You should see the server log the event and the worker log its "pr.opened.ready" message.

Single‑binary (in‑process)

Use GoChannel to run the server and multiple workers in one process:

go run ./example/inprocess/main.go --config app.docker.yaml

Configuration

Docs:

Githooks is configured using a single YAML file. Environment variables like ${VAR} are automatically expanded. Requests use or generate X-Request-Id, which is echoed back in responses and included in logs.

Providers

The providers section configures webhook endpoints and SCM auth for each Git provider. providers.*.key sets the provider instance key (default: default). Use different keys to run multiple app installs per provider. If webhook.path is omitted, defaults are used: /webhooks/github, /webhooks/gitlab, /webhooks/bitbucket. Set server.public_base_url when running behind ngrok or a reverse proxy so OAuth callbacks resolve to your public domain. providers.*.oauth is reserved for OAuth2 expansion in future releases.

providers:
  github:
    enabled: true
    key: default
    webhook:
      path: /webhooks/github
      secret: ${GITHUB_WEBHOOK_SECRET}
    app:
      app_id: ${GITHUB_APP_ID}
      private_key_path: ${GITHUB_PRIVATE_KEY_PATH}
      app_slug: ${GITHUB_APP_SLUG}
    api:
      base_url: https://api.github.com
      web_base_url: https://github.com
    oauth:
      client_id: ${GITHUB_OAUTH_CLIENT_ID}
      client_secret: ${GITHUB_OAUTH_CLIENT_SECRET}
      scopes: ["read:user"]
  gitlab:
    enabled: false
    key: default
    webhook:
      path: /webhooks/gitlab
      secret: ${GITLAB_WEBHOOK_SECRET} # Optional
    api:
      base_url: https://gitlab.com/api/v4
      web_base_url: https://gitlab.com
    oauth:
      client_id: ${GITLAB_OAUTH_CLIENT_ID}
      client_secret: ${GITLAB_OAUTH_CLIENT_SECRET}
      scopes: ["read_api"]
  bitbucket:
    enabled: false
    key: default
    webhook:
      path: /webhooks/bitbucket
      secret: ${BITBUCKET_WEBHOOK_SECRET} # Optional, for X-Hook-UUID
    api:
      base_url: https://api.bitbucket.org/2.0
      web_base_url: https://bitbucket.org
    oauth:
      client_id: ${BITBUCKET_OAUTH_CLIENT_ID}
      client_secret: ${BITBUCKET_OAUTH_CLIENT_SECRET}
      scopes: ["repository"]

SCM Authentication

providers:
  github:
    app:
      app_id: 123
      private_key_path: /secrets/github.pem
      app_slug: githooks
    api:
      base_url: https://api.github.com
      web_base_url: https://github.com
  gitlab:
    api:
      base_url: https://gitlab.com/api/v4
      web_base_url: https://gitlab.com
  bitbucket:
    api:
      base_url: https://api.bitbucket.org/2.0
      web_base_url: https://bitbucket.org

GitHub Enterprise: set providers.github.api.base_url to your API base (for example, https://ghe.example.com/api/v3). The SDK derives the upload URL automatically.

Server Limits

server:
  port: 8080
  public_base_url: https://app.example.com
  read_timeout_ms: 5000
  write_timeout_ms: 10000
  idle_timeout_ms: 60000
  read_header_timeout_ms: 5000
  max_body_bytes: 1048576
  debug_events: false

API Authentication

auth:
  oauth2:
    enabled: true
    issuer: https://<your-okta-domain>/oauth2/default
    audience: api://githooks

When enabled, all Connect RPC endpoints require a bearer token. Webhooks and /auth/* remain public. See docs/auth.md for client_credentials and human login flows.

Installation Storage

storage:
  driver: postgres
  dsn: postgres://githooks:githooks@localhost:5432/githooks?sslmode=disable
  dialect: postgres
  auto_migrate: true

OAuth Callbacks

oauth:
  redirect_base_url: https://app.example.com/oauth/complete

Callback endpoints:

  • /auth/github/callback
  • /auth/gitlab/callback
  • /auth/bitbucket/callback

GitHub App installs are initiated from the GitHub App installation page. The GitHub callback is only used when "Request user authorization" is enabled in the app settings.

API Endpoints (Connect/GRPC)

REST endpoints are replaced by Connect/GRPC handlers. Use the generated client from pkg/gen/cloud/v1/cloudv1connect or call the procedures directly.

/cloud.v1.InstallationsService/ListInstallations
/cloud.v1.InstallationsService/GetInstallationByID
/cloud.v1.NamespacesService/ListNamespaces
/cloud.v1.NamespacesService/SyncNamespaces
/cloud.v1.NamespacesService/GetNamespaceWebhook
/cloud.v1.NamespacesService/SetNamespaceWebhook
/cloud.v1.RulesService/MatchRules
/cloud.v1.DriversService/ListDrivers
/cloud.v1.DriversService/GetDriver
/cloud.v1.DriversService/UpsertDriver
/cloud.v1.DriversService/DeleteDriver
/cloud.v1.ProvidersService/ListProviders
/cloud.v1.ProvidersService/GetProvider
/cloud.v1.ProvidersService/UpsertProvider
/cloud.v1.ProvidersService/DeleteProvider

Use the Connect RPC to get the provider URL and state.

Notes:

  • GitHub webhooks are always enabled by the GitHub App and cannot be toggled.
  • GitLab/Bitbucket create/delete provider webhooks when toggled.

CLI shortcuts (via Connect RPC):

githooks --endpoint http://localhost:8080 installations list --state-id <state-id>
githooks --endpoint http://localhost:8080 installations get --provider github --installation-id <id>
githooks --endpoint http://localhost:8080 namespaces list --state-id <state-id>
githooks --endpoint http://localhost:8080 namespaces sync --state-id <state-id> --provider gitlab
githooks --endpoint http://localhost:8080 namespaces webhook get --state-id <state-id> --provider gitlab --repo-id <repo-id>
githooks --endpoint http://localhost:8080 namespaces webhook set --state-id <state-id> --provider gitlab --repo-id <repo-id> --enabled
githooks --endpoint http://localhost:8080 rules match --payload-file payload.json --rules-file rules.yaml
githooks --endpoint http://localhost:8080 providers list --provider github
githooks --endpoint http://localhost:8080 providers get --provider github --key default
githooks --endpoint http://localhost:8080 providers set --provider github --key acme-prod --config-file github.json
githooks --endpoint http://localhost:8080 providers delete --provider github --key default
githooks --endpoint http://localhost:8080 drivers list
githooks --endpoint http://localhost:8080 drivers get --name amqp
githooks --endpoint http://localhost:8080 drivers set --name amqp --config-file amqp.json
githooks --endpoint http://localhost:8080 drivers delete --name amqp

Install/Authorize Entry

Start an install/authorize flow by redirecting users to:

http://localhost:8080/?provider=github
http://localhost:8080/?provider=gitlab
http://localhost:8080/?provider=bitbucket

To target a specific provider instance, pass instance=<key>:

http://localhost:8080/?provider=github&instance=acme-prod

GitHub uses the App installation URL. GitLab/Bitbucket use OAuth authorize URLs built from providers.* config.

Watermill Drivers (Publishing)

The watermill section configures the message broker(s) to publish events to.

  • driver: (string) Default publisher driver.
  • drivers: (array) Fan-out to all listed drivers by default.

Single Driver (AMQP)

watermill:
  driver: amqp
  amqp:
    url: amqp://guest:guest@localhost:5672/
    mode: durable_queue # Or: nondurable_queue, durable_pubsub, nondurable_pubsub

Multiple Drivers (Fan-Out)

watermill:
  drivers: [amqp, http]
  amqp:
    url: amqp://guest:guest@localhost:5672/
  http:
    mode: base_url
    base_url: http://localhost:9000/hooks

RiverQueue (Postgres Job Queue)

watermill:
  driver: riverqueue
  riverqueue:
    driver: postgres
    dsn: postgres://user:pass@localhost:5432/dbname?sslmode=disable
    table: river_job # Optional, default is river_job
    queue: default   # Optional, default is default
    kind: githooks.event # The job type to insert

See the Watermill documentation for details on each driver's configuration.

Rules

The rules section defines which events to publish and where. Each rule has a when condition and an emit topic.

rules_strict: false # Optional: if true, rules with missing fields won't match
rules:
  # If a PR is opened and not a draft, emit to 'pr.opened.ready'
  - when: action == "opened" && pull_request.draft == false
    emit: pr.opened.ready

  # If a PR is merged, emit to 'pr.merged' on specific drivers
  - when: action == "closed" && pull_request.merged == true
    emit: pr.merged
    drivers: [amqp, http]

  # Fan-out to multiple topics
  - when: action == "closed" && pull_request.merged == true
    emit: [pr.merged, audit.pr.merged]
  • when: A boolean expression evaluated against the webhook payload.
    • Bare identifiers (e.g., action) are treated as JSONPath $.action.
    • You can use full JSONPath syntax (e.g., $.pull_request.head.ref).
    • Helper functions: contains(value, needle) and like(value, pattern) (% wildcard).
  • emit: The topic name to publish the event to if the when condition is true.
  • emit can also be a list to publish to multiple topics.
  • drivers: (Optional) A list of specific drivers to publish this event to. If omitted, the default driver or drivers from the Watermill config are used.

Worker SDK

The worker SDK provides a simple way to consume events from the message broker.

Minimal Example

package main

import (
    "context"
    "log"

    "githooks/sdk/go/worker"
)

func main() {
    // Load subscriber settings from the same config file the server uses.
    subCfg, err := worker.LoadSubscriberConfig("config.yaml")
    if err != nil {
        log.Fatalf("Failed to build subscriber: %v", err)
    }
    sub, err := worker.BuildSubscriber(subCfg)
    if err != nil {
        log.Fatalf("Failed to build subscriber: %v", err)
    }

    wk := worker.New(
        worker.WithSubscriber(sub),
        worker.WithTopics("pr.opened.ready"), // List of topics to subscribe to
        worker.WithConcurrency(10),
    )

    // Register a handler for a specific topic
    wk.HandleTopic("pr.opened.ready", func(ctx context.Context, evt *worker.Event) error {
        log.Printf("Received event: %s/%s", evt.Provider, evt.Type)
        // Do something with evt.Payload or evt.Normalized
        return nil
    })

    // Run the worker (blocking call)
    if err := wk.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

If you need provider‑aware clients inside handlers, set server.public_base_url in your worker config or export GITHOOKS_API_BASE_URL so the worker can resolve installation tokens.

Watermill Middleware

You can use any Watermill middleware with the provided adapter.

import wmmw "github.com/ThreeDotsLabs/watermill/message/router/middleware"

retryMiddleware := worker.MiddlewareFromWatermill(
    wmmw.Retry{MaxRetries: 3}.Middleware,
)

wk := worker.New(
  // ... other options
  worker.WithMiddleware(retryMiddleware),
)

Building Your Own Go App

If you like this model of Git provider webhook management, you can build your own Go app by reusing the same pattern: validate provider signatures, normalize payloads, evaluate rules, then publish to a broker and consume with workers. Use the SDK to wire provider clients into handlers and keep business logic isolated from transport.

Examples

The example/ directory contains several working examples:

  • example/github: A simple server and worker for handling GitHub webhooks.
  • example/realworld: A more complex setup with multiple workers consuming events from a single topic.
  • example/riverqueue: Demonstrates publishing events to a River job queue.
  • example/vercel: Production-style preview/production deploy hooks for Vercel.
  • example/gitlab: Sample setup for GitLab webhooks.
  • example/bitbucket: Sample setup for Bitbucket webhooks.
  • example/inprocess: Single-binary server + multiple workers using GoChannel.

Helm Charts

Helm charts for deploying the server and a generic worker are available in the charts/ directory.

Install from GitHub Pages

helm repo add githooks https://yindia.github.io/githooks
helm repo update
helm install my-githooks githooks/githooks
helm install my-worker githooks/githooks-worker

Releases

  • Code Releases: Tagging a commit with vX.Y.Z triggers a workflow that publishes a new Go module version and a container image to ghcr.io/yindia/githooks.
  • Chart Releases: Tagging a commit with chart-X.Y.Z publishes the Helm charts to the gh-pages branch. Ensure you update version and appVersion in charts/*/Chart.yaml first.

Development

Run Tests

go test ./...

Notes

  • When using the SQL publisher, you must blank-import a database driver (e.g., _ "github.com/lib/pq").
  • The default webhook secret for local testing is devsecret.
  • Rules are evaluated in the order they appear in the config file. Multiple rules can match a single event, causing multiple messages to be published.

About

Githooks routes GitHub/GitLab/Bitbucket webhooks through rules to Watermill brokers and provides a worker SDK for provider-aware event handling.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages