mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
initial commit
This commit is contained in:
commit
512d4b01d5
55 changed files with 6430 additions and 0 deletions
115
docs/guide/configuration.md
Normal file
115
docs/guide/configuration.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# Configuration
|
||||
|
||||
## Secrets
|
||||
|
||||
WebHooker requires several secrets to function. For local development, store them in `.dev.vars`. For production, use Cloudflare Worker Secrets.
|
||||
|
||||
### Required Secrets
|
||||
|
||||
| Variable | Description |
|
||||
| ----------------------- | ----------------------------------------------- |
|
||||
| `GITHUB_WEBHOOK_SECRET` | Webhook secret from your GitHub App settings |
|
||||
| `GITHUB_APP_ID` | Numeric ID of your GitHub App |
|
||||
| `GITHUB_PRIVATE_KEY` | App private key (PEM format, with `\n` escapes) |
|
||||
| `GITHUB_CLIENT_ID` | OAuth client ID from App settings |
|
||||
| `GITHUB_CLIENT_SECRET` | OAuth client secret from App settings |
|
||||
| `DISCORD_TOKEN` | Discord bot token |
|
||||
| `DISCORD_CHANNEL_ID` | Default Discord channel ID for messages |
|
||||
|
||||
### Optional Secrets
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ---------- | ------------------------------ | ----------------------- |
|
||||
| `BASE_URL` | Public URL for OAuth callbacks | `http://localhost:8787` |
|
||||
|
||||
## Routes
|
||||
|
||||
Routes define which events get forwarded to which Discord channels. They are stored in Cloudflare KV under the key `config:routes` as a JSON array.
|
||||
|
||||
On first boot, 7 default routes are used if no KV config exists.
|
||||
|
||||
### Route Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "unique-route-id",
|
||||
"name": "Human-readable name",
|
||||
"enabled": true,
|
||||
"filters": [
|
||||
{ "type": "event", "match": "push" },
|
||||
{ "type": "repo", "match": "org/repo", "exclude": false }
|
||||
],
|
||||
"target": {
|
||||
"channelId": "DISCORD_CHANNEL_ID",
|
||||
"threadId": "OPTIONAL_THREAD_ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Default Routes
|
||||
|
||||
| ID | Event(s) | Description |
|
||||
| ----------------- | ------------------ | -------------------------------- |
|
||||
| `all-push` | `push` | All push events |
|
||||
| `pull-requests` | `pull_request` | All PR activity |
|
||||
| `issues` | `issues` | Issue open/close/edit |
|
||||
| `issue-comments` | `issue_comment` | Issue and PR comments |
|
||||
| `workflow-runs` | `workflow_run` | CI/CD workflow completions |
|
||||
| `releases` | `release` | Release publish/edit |
|
||||
| `branch-activity` | `create`, `delete` | Branch/tag creation and deletion |
|
||||
|
||||
### Custom Route Example
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "backend-prs",
|
||||
"name": "Backend PRs",
|
||||
"enabled": true,
|
||||
"filters": [
|
||||
{ "type": "repo", "match": "myorg/backend" },
|
||||
{ "type": "event", "match": "pull_request" },
|
||||
{ "type": "actor", "match": "[bot]", "exclude": true }
|
||||
],
|
||||
"target": {
|
||||
"channelId": "1234567890",
|
||||
"threadId": "9876543210"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Filter Types
|
||||
|
||||
| Type | Matches | Example |
|
||||
| --------- | -------------------- | -------------------------------- |
|
||||
| `event` | GitHub event name | `push`, `pull_request`, `issues` |
|
||||
| `repo` | Repository full name | `org/repo` |
|
||||
| `actor` | Sender login | `username`, `[bot]` |
|
||||
| `action` | Event action | `opened`, `closed`, `published` |
|
||||
| `branch` | Branch name | `main`, `feature/*` |
|
||||
| `keyword` | Text in payload body | `deploy`, `/fix\s+\d+/` (regex) |
|
||||
|
||||
### Filter Behavior
|
||||
|
||||
- All filters in a route must match for the route to trigger (AND logic)
|
||||
- Set `"exclude": true` on any filter to invert it (NOT logic)
|
||||
- `keyword` filter supports regex patterns — falls back to substring match if regex is invalid
|
||||
- `branch` filter works for push, pull_request, create/delete, workflow_run, and code_scanning_alert events
|
||||
|
||||
### Match Values
|
||||
|
||||
Filters accept either a single string or an array of strings:
|
||||
|
||||
```json
|
||||
{ "type": "event", "match": "push" }
|
||||
{ "type": "event", "match": ["push", "pull_request"] }
|
||||
```
|
||||
|
||||
## KV Storage Layout
|
||||
|
||||
| Key Pattern | Value | TTL |
|
||||
| ---------------- | ---------------------------- | ------------ |
|
||||
| `config:routes` | JSON array of routes | Permanent |
|
||||
| `token:{userId}` | `{ accessToken, expiresAt }` | Until expiry |
|
||||
| `state:{hex}` | `{ userId, createdAt }` | 600 seconds |
|
||||
104
docs/guide/deployment.md
Normal file
104
docs/guide/deployment.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Deployment
|
||||
|
||||
## Cloudflare Setup
|
||||
|
||||
### 1. Create KV Namespace
|
||||
|
||||
```bash
|
||||
npx wrangler kv namespace create KV
|
||||
```
|
||||
|
||||
This outputs a namespace ID. Update `wrangler.jsonc` with the ID:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kv_namespaces": [
|
||||
{
|
||||
"binding": "KV",
|
||||
"id": "your-namespace-id",
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Set Secrets
|
||||
|
||||
```bash
|
||||
npx wrangler secret put GITHUB_WEBHOOK_SECRET
|
||||
npx wrangler secret put GITHUB_APP_ID
|
||||
npx wrangler secret put GITHUB_PRIVATE_KEY
|
||||
npx wrangler secret put GITHUB_CLIENT_ID
|
||||
npx wrangler secret put GITHUB_CLIENT_SECRET
|
||||
npx wrangler secret put DISCORD_TOKEN
|
||||
npx wrangler secret put DISCORD_CHANNEL_ID
|
||||
```
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
```bash
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
Your worker is now live at `https://webhooker.<your-subdomain>.workers.dev`.
|
||||
|
||||
### 4. Configure GitHub Webhook
|
||||
|
||||
1. Go to your GitHub App settings
|
||||
2. Set **Webhook URL** to `https://webhooker.<your-subdomain>.workers.dev/webhook`
|
||||
3. Set **Webhook secret** to match `GITHUB_WEBHOOK_SECRET`
|
||||
|
||||
## GitHub App Setup
|
||||
|
||||
### 1. Create App
|
||||
|
||||
1. Go to <https://github.com/settings/apps/new>
|
||||
2. Fill in:
|
||||
- **GitHub App name**: `WebHooker` (or your choice)
|
||||
- **Homepage URL**: your domain
|
||||
- **Webhook URL**: `https://your-domain/webhook`
|
||||
- **Webhook secret**: generate and copy to `GITHUB_WEBHOOK_SECRET`
|
||||
3. Set permissions:
|
||||
- **Repository permissions**: Contents (read), Issues (write), Pull requests (write), Metadata (read)
|
||||
- **Organization permissions**: Members (read) — if needed
|
||||
4. Subscribe to events (all 23 supported):
|
||||
- Push, Pull request, Issues, Issue comment, Workflow run, Release, Create, Delete, Star, Fork, Check run, Pull request review, Pull request review comment, Commit comment, Deployment status, Member, Label, Milestone, Discussion, Discussion comment, Repository, Code scanning alert, Dependabot alert
|
||||
5. Generate private key → save contents to `GITHUB_PRIVATE_KEY` env var
|
||||
|
||||
### 2. Install App
|
||||
|
||||
1. After creation, go to the App settings page
|
||||
2. Click "Install App" → select org/user
|
||||
3. Choose repositories to monitor
|
||||
|
||||
### 3. Configure OAuth
|
||||
|
||||
1. Go to App → OAuth settings
|
||||
2. Set **Callback URL**: `https://your-domain/auth/github/callback`
|
||||
3. Copy Client ID and Client Secret to env
|
||||
|
||||
## Discord Bot Setup
|
||||
|
||||
1. Go to <https://discord.com/developers/applications>
|
||||
2. Create a new application → go to Bot section
|
||||
3. Copy the bot token to `DISCORD_TOKEN`
|
||||
4. Invite the bot to your server with `bot` scope and `Send Messages` permission
|
||||
5. Copy the target channel ID to `DISCORD_CHANNEL_ID`
|
||||
|
||||
## Custom Domain (Optional)
|
||||
|
||||
To use a custom domain instead of `*.workers.dev`:
|
||||
|
||||
1. Go to your Cloudflare Worker settings
|
||||
2. Add a custom domain or route
|
||||
3. Update `BASE_URL` to match
|
||||
|
||||
## Docker
|
||||
|
||||
A Dockerfile is provided for containerized deployments (e.g., behind a reverse proxy):
|
||||
|
||||
```bash
|
||||
docker build -t webhooker .
|
||||
docker run -p 8787:8787 --env-file .env webhooker
|
||||
```
|
||||
|
||||
Note: Docker mode runs without Durable Objects and KV. Use Cloudflare deployment for full functionality.
|
||||
72
docs/guide/getting-started.md
Normal file
72
docs/guide/getting-started.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) 18+
|
||||
- A [Cloudflare account](https://dash.cloudflare.com/) (free tier works)
|
||||
- A [GitHub App](https://github.com/settings/apps/new) (see [GitHub App Setup](/guide/deployment#github-app-setup))
|
||||
- A Discord bot token (see [Discord Bot Setup](/guide/deployment#discord-bot-setup))
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ReCloudStudio/WebHooker.git
|
||||
cd WebHooker
|
||||
npm install
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
### 1. Configure Secrets
|
||||
|
||||
Copy the example env file and fill in your secrets:
|
||||
|
||||
```bash
|
||||
cp .env.example .dev.vars
|
||||
```
|
||||
|
||||
Edit `.dev.vars` with your actual values:
|
||||
|
||||
```bash
|
||||
GITHUB_WEBHOOK_SECRET=your-webhook-secret
|
||||
GITHUB_APP_ID=your-app-id
|
||||
GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
|
||||
GITHUB_CLIENT_ID=your-client-id
|
||||
GITHUB_CLIENT_SECRET=your-client-secret
|
||||
DISCORD_TOKEN=your-bot-token
|
||||
DISCORD_CHANNEL_ID=your-channel-id
|
||||
BASE_URL=http://localhost:8787
|
||||
```
|
||||
|
||||
::: warning
|
||||
`.dev.vars` is gitignored and contains secrets. Never commit it.
|
||||
:::
|
||||
|
||||
### 2. Start Dev Server
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This starts a local Miniflare environment at `http://localhost:8787`.
|
||||
|
||||
### 3. Verify
|
||||
|
||||
```bash
|
||||
curl http://localhost:8787/health
|
||||
# → {"status":"ok"}
|
||||
```
|
||||
|
||||
## Available Scripts
|
||||
|
||||
| Script | Description |
|
||||
| ---------------------- | --------------------------------- |
|
||||
| `npm run dev` | Start local dev server (wrangler) |
|
||||
| `npm run deploy` | Deploy to Cloudflare |
|
||||
| `npm run typecheck` | TypeScript type checking |
|
||||
| `npm run lint` | ESLint |
|
||||
| `npm run lint:md` | Markdownlint |
|
||||
| `npm run format` | Format with Prettier |
|
||||
| `npm run format:check` | Check Prettier formatting |
|
||||
| `npm run docs:dev` | Start docs dev server |
|
||||
| `npm run docs:build` | Build docs site |
|
||||
44
docs/guide/introduction.md
Normal file
44
docs/guide/introduction.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Introduction
|
||||
|
||||
WebHooker is a GitHub webhook dispatcher built on Cloudflare Workers. It receives GitHub webhook events, applies configurable filters, formats them into rich Discord embeds, and routes messages to Discord channels or threads via a Durable Object-maintained Gateway connection.
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
GitHub Webhook → Cloudflare Worker (Hono)
|
||||
├── POST /webhook → verify → filter → format → DO (Discord Gateway) → Discord
|
||||
├── GET /auth/github → OAuth flow
|
||||
├── POST /api/* → user actions (Bearer token auth)
|
||||
└── GET /health → status check
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
| Component | Role |
|
||||
| ----------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| **Cloudflare Worker** | HTTP ingress, signature verification, event parsing, route matching |
|
||||
| **Durable Object (DiscordGateway)** | Persistent WebSocket to Discord Gateway, channel cache, message dispatch with retry |
|
||||
| **KV** | Token storage (`token:{userId}`), OAuth state (`state:{hex}`), route config (`config:routes`) |
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. GitHub sends a webhook to `POST /webhook`
|
||||
2. Worker verifies the HMAC-SHA256 signature
|
||||
3. Worker parses the event type and payload
|
||||
4. Routes are evaluated against filters (event, repo, actor, action, branch, keyword)
|
||||
5. Matching routes trigger formatter functions that produce Discord embeds
|
||||
6. Messages are dispatched to the Durable Object, which maintains the Gateway connection
|
||||
7. DO sends messages to Discord via REST API with rate-limit retry
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Runtime**: Cloudflare Workers
|
||||
- **HTTP Framework**: Hono
|
||||
- **Discord Gateway**: Durable Object (persistent WebSocket + channel cache)
|
||||
- **Storage**: Cloudflare KV
|
||||
- **Auth**: Web Crypto API (HMAC-SHA256), jose (JWT), octokit (GitHub API)
|
||||
- **Language**: TypeScript
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Loading…
Add table
Add a link
Reference in a new issue