Skip to content

Commit 3cf6316

Browse files
committed
feat(slack): add trogon-source-slack webhook receiver
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 6fdf052 commit 3cf6316

24 files changed

Lines changed: 2317 additions & 96 deletions

File tree

.mise/tasks/tunnels

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
#MISE description="Show ngrok tunnel URLs with webhook paths"
3+
set -euo pipefail
4+
5+
NGROK_API="http://ngrok.trogonai.orb.local:4040/api/tunnels"
6+
7+
if ! tunnels=$(curl -sf "$NGROK_API" 2>/dev/null); then
8+
echo "ngrok is not running. Start it with: mise run dev:up"
9+
exit 1
10+
fi
11+
12+
echo ""
13+
echo " ngrok tunnels"
14+
echo " ─────────────────────────────────────────────────────────"
15+
echo ""
16+
17+
echo "$tunnels" | jq -r '.tunnels[] | "\(.name)\t\(.public_url)"' | while IFS=$'\t' read -r name url; do
18+
printf " %-10s %s/webhook\n" "$name" "$url"
19+
done
20+
21+
echo ""
22+
echo " dashboard http://ngrok.trogonai.orb.local:4040"
23+
echo ""

devops/docker/compose/.env.example

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# --- NATS ---
2+
# NATS_URL=nats://localhost:4222
3+
4+
# --- GitHub Source ---
5+
# GITHUB_WEBHOOK_SECRET=
6+
# GITHUB_WEBHOOK_PORT=8080
7+
# GITHUB_SUBJECT_PREFIX=github
8+
# GITHUB_STREAM_NAME=GITHUB
9+
# GITHUB_STREAM_MAX_AGE_SECS=604800
10+
# GITHUB_NATS_ACK_TIMEOUT_SECS=10
11+
# GITHUB_MAX_BODY_SIZE=26214400
12+
13+
# --- Slack Source ---
14+
# SLACK_SIGNING_SECRET=
15+
# SLACK_WEBHOOK_PORT=3000
16+
# SLACK_SUBJECT_PREFIX=slack
17+
# SLACK_STREAM_NAME=SLACK
18+
# SLACK_STREAM_MAX_AGE_SECS=604800
19+
# SLACK_NATS_ACK_TIMEOUT_SECS=10
20+
# SLACK_MAX_BODY_SIZE=1048576
21+
# SLACK_TIMESTAMP_MAX_DRIFT_SECS=300
22+
23+
# --- Logging ---
24+
# RUST_LOG=info
25+
26+
# --- Dev (ngrok tunnels) ---
27+
# NGROK_AUTHTOKEN=

devops/docker/compose/compose.yml

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ services:
77
- "--jetstream"
88
- "--store_dir=/data"
99
- "--http_port=8222"
10-
ports:
11-
- "4222:4222"
12-
- "8222:8222"
1310
volumes:
1411
- nats_data:/data
1512
restart: unless-stopped
@@ -23,12 +20,10 @@ services:
2320
trogon-source-github:
2421
build:
2522
context: ../../../rsworkspace
26-
dockerfile: crates/trogon-source-github/Dockerfile
27-
ports:
28-
- "${GITHUB_WEBHOOK_PORT:-8080}:${GITHUB_WEBHOOK_PORT:-8080}"
23+
dockerfile: ../devops/docker/compose/services/trogon-source-github/Dockerfile
2924
environment:
3025
NATS_URL: "nats:4222"
31-
GITHUB_WEBHOOK_SECRET: "${GITHUB_WEBHOOK_SECRET:?GITHUB_WEBHOOK_SECRET is required}"
26+
GITHUB_WEBHOOK_SECRET: "${GITHUB_WEBHOOK_SECRET:-}"
3227
GITHUB_WEBHOOK_PORT: "${GITHUB_WEBHOOK_PORT:-8080}"
3328
GITHUB_SUBJECT_PREFIX: "${GITHUB_SUBJECT_PREFIX:-github}"
3429
GITHUB_STREAM_NAME: "${GITHUB_STREAM_NAME:-GITHUB}"
@@ -47,18 +42,58 @@ services:
4742
start_period: 10s
4843
retries: 3
4944

50-
smee:
51-
image: node:alpine
52-
command:
53-
- npx
54-
- smee-client
55-
- --url
56-
- "${SMEE_URL:?SMEE_URL is required — create a channel at https://smee.io}"
57-
- --target
58-
- "http://trogon-source-github:${GITHUB_WEBHOOK_PORT:-8080}/webhook"
45+
trogon-source-slack:
46+
build:
47+
context: ../../../rsworkspace
48+
dockerfile: ../devops/docker/compose/services/trogon-source-slack/Dockerfile
49+
environment:
50+
NATS_URL: "nats:4222"
51+
SLACK_SIGNING_SECRET: "${SLACK_SIGNING_SECRET:-}"
52+
SLACK_WEBHOOK_PORT: "${SLACK_WEBHOOK_PORT:-3000}"
53+
SLACK_SUBJECT_PREFIX: "${SLACK_SUBJECT_PREFIX:-slack}"
54+
SLACK_STREAM_NAME: "${SLACK_STREAM_NAME:-SLACK}"
55+
SLACK_STREAM_MAX_AGE_SECS: "${SLACK_STREAM_MAX_AGE_SECS:-604800}"
56+
SLACK_NATS_ACK_TIMEOUT_SECS: "${SLACK_NATS_ACK_TIMEOUT_SECS:-10}"
57+
SLACK_MAX_BODY_SIZE: "${SLACK_MAX_BODY_SIZE:-1048576}"
58+
SLACK_TIMESTAMP_MAX_DRIFT_SECS: "${SLACK_TIMESTAMP_MAX_DRIFT_SECS:-300}"
59+
RUST_LOG: "${RUST_LOG:-info}"
60+
depends_on:
61+
nats:
62+
condition: service_healthy
63+
restart: unless-stopped
64+
healthcheck:
65+
test: ["CMD", "curl", "-sf", "http://localhost:${SLACK_WEBHOOK_PORT:-3000}/health"]
66+
interval: 10s
67+
timeout: 3s
68+
start_period: 10s
69+
retries: 3
70+
71+
ngrok:
72+
image: ngrok/ngrok:alpine
73+
environment:
74+
NGROK_AUTHTOKEN: "${NGROK_AUTHTOKEN:-}"
75+
GITHUB_ADDR: "trogon-source-github:${GITHUB_WEBHOOK_PORT:-8080}"
76+
SLACK_ADDR: "trogon-source-slack:${SLACK_WEBHOOK_PORT:-3000}"
77+
entrypoint:
78+
- /bin/sh
79+
- -c
80+
- |
81+
cat > /tmp/ngrok.yml <<EOF
82+
version: 3
83+
tunnels:
84+
github:
85+
addr: $${GITHUB_ADDR}
86+
proto: http
87+
slack:
88+
addr: $${SLACK_ADDR}
89+
proto: http
90+
EOF
91+
exec ngrok start --all --config /tmp/ngrok.yml
5992
depends_on:
6093
trogon-source-github:
6194
condition: service_healthy
95+
trogon-source-slack:
96+
condition: service_healthy
6297
restart: unless-stopped
6398
profiles:
6499
- dev

rsworkspace/crates/trogon-source-github/Dockerfile renamed to devops/docker/compose/services/trogon-source-github/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ RUN cargo build --release -p trogon-source-github && \
2929
FROM debian:bookworm-20250317-slim AS runtime
3030

3131
RUN apt-get update && apt-get install -y --no-install-recommends \
32-
ca-certificates \
32+
ca-certificates curl \
3333
&& rm -rf /var/lib/apt/lists/*
3434

3535
RUN useradd --no-create-home --shell /usr/sbin/nologin trogon

devops/docker/compose/GITHUB_WEBHOOKS.md renamed to devops/docker/compose/services/trogon-source-github/README.md

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Docker Compose
66
- A GitHub App (org or personal) — the app's webhook delivers events from every
77
repo where it's installed, so you configure the URL once
8+
- An [ngrok](https://ngrok.com) account (free tier works)
89

910
## 1. Generate a webhook secret
1011

@@ -14,51 +15,54 @@ openssl rand -hex 32
1415

1516
Save the output — you'll use it in both GitHub and the local stack.
1617

17-
## 2. Create a smee.io channel
18+
## 2. Start the stack
1819

19-
Go to [smee.io](https://smee.io) and click **Start a new channel**. Copy the
20-
channel URL (e.g. `https://smee.io/abc123`).
20+
```bash
21+
docker compose --profile dev up
22+
```
23+
24+
This starts NATS, the webhook receiver, and ngrok. Find the public tunnel URL
25+
in the ngrok container logs:
26+
27+
```bash
28+
docker compose logs ngrok
29+
```
30+
31+
Look for the `github` tunnel URL (e.g. `https://abc123.ngrok-free.app`).
2132

2233
## 3. Configure your GitHub App
2334

2435
1. Go to **Settings → Developer settings → GitHub Apps**
2536
2. Select your app (or create a new one)
2637
3. Under **Webhook**:
27-
- **Webhook URL**: your smee.io channel URL
38+
- **Webhook URL**: `https://<ngrok-url>/webhook`
2839
- **Webhook secret**: the secret you generated in step 1
2940
4. Under **Permissions & events**, subscribe to the events you need
3041
5. Install the app on the repositories or organization you want to receive
3142
events from
3243

33-
## 4. Start the stack
34-
35-
```bash
36-
SMEE_URL=https://smee.io/abc123 \
37-
GITHUB_WEBHOOK_SECRET=<secret-from-step-1> \
38-
docker compose --profile dev up
39-
```
44+
## 4. Verify
4045

41-
This starts NATS, the webhook receiver, and the smee client. The smee client
42-
connects to your channel and forwards events to the webhook receiver.
46+
Trigger an event in a repository where the app is installed (e.g. push a
47+
commit). You should see:
4348

44-
Without `--profile dev`, the smee client is excluded and only the core services
45-
start.
49+
- The webhook receiver log the incoming event
50+
- The event published to NATS on `github.{event}`
4651

47-
## 5. Verify
52+
You can inspect NATS with:
4853

49-
Trigger an event in a repository where the app is installed (e.g. push a
50-
commit). You should see:
54+
```bash
55+
nats sub -s nats://nats.trogonai.orb.local:4222 "github.>"
56+
```
5157

52-
- The event appear on your smee.io channel page
53-
- The smee client forward it to the webhook receiver
54-
- The webhook receiver publish it to NATS on `github.{event}`
58+
Without `--profile dev`, ngrok is excluded and only the core services start.
5559

5660
## Environment variables
5761

5862
| Variable | Required | Default | Description |
5963
|---|---|---|---|
6064
| `GITHUB_WEBHOOK_SECRET` | yes || HMAC-SHA256 secret (must match GitHub App) |
61-
| `SMEE_URL` | yes (dev profile) || smee.io channel URL |
65+
| `NGROK_AUTHTOKEN` | yes (dev profile) || ngrok auth token |
6266
| `GITHUB_WEBHOOK_PORT` | no | `8080` | HTTP port for the webhook receiver |
6367
| `GITHUB_SUBJECT_PREFIX` | no | `github` | NATS subject prefix |
6468
| `GITHUB_STREAM_NAME` | no | `GITHUB` | JetStream stream name |
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ── Stage 1: chef — generate dependency recipe ──────────────────────────────
2+
FROM rust:1.93-slim AS chef
3+
4+
RUN cargo install cargo-chef --locked
5+
6+
WORKDIR /build
7+
8+
# ── Stage 2: planner — capture dependency graph ─────────────────────────────
9+
FROM chef AS planner
10+
11+
COPY Cargo.toml Cargo.lock ./
12+
COPY crates/ crates/
13+
14+
RUN cargo chef prepare --recipe-path recipe.json
15+
16+
# ── Stage 3: builder — cached dependency build + final compile ──────────────
17+
FROM chef AS builder
18+
19+
COPY --from=planner /build/recipe.json recipe.json
20+
RUN cargo chef cook --release --recipe-path recipe.json -p trogon-source-slack
21+
22+
COPY Cargo.toml Cargo.lock ./
23+
COPY crates/ crates/
24+
25+
RUN cargo build --release -p trogon-source-slack && \
26+
strip target/release/trogon-source-slack
27+
28+
# ── Stage 4: runtime ────────────────────────────────────────────────────────
29+
FROM debian:bookworm-20250317-slim AS runtime
30+
31+
RUN apt-get update && apt-get install -y --no-install-recommends \
32+
ca-certificates curl \
33+
&& rm -rf /var/lib/apt/lists/*
34+
35+
RUN useradd --no-create-home --shell /usr/sbin/nologin trogon
36+
37+
COPY --from=builder /build/target/release/trogon-source-slack /usr/local/bin/trogon-source-slack
38+
39+
USER trogon
40+
41+
EXPOSE 3000
42+
43+
STOPSIGNAL SIGTERM
44+
45+
ENTRYPOINT ["/usr/local/bin/trogon-source-slack"]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Receiving Slack Events Locally
2+
3+
## Prerequisites
4+
5+
- Docker Compose
6+
- A Slack App with Event Subscriptions enabled
7+
- An [ngrok](https://ngrok.com) account (free tier works)
8+
9+
## 1. Create a Slack App
10+
11+
1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App**
12+
2. Choose **From scratch**, pick a name and workspace
13+
3. Under **Basic Information → App Credentials**, copy the **Signing Secret**
14+
15+
## 2. Start the stack
16+
17+
```bash
18+
docker compose --profile dev up
19+
```
20+
21+
This starts NATS, the Slack webhook receiver, and ngrok. Find the public tunnel
22+
URL in the ngrok container logs:
23+
24+
```bash
25+
docker compose logs ngrok
26+
```
27+
28+
Look for the `slack` tunnel URL (e.g. `https://def456.ngrok-free.app`).
29+
30+
## 3. Enable Event Subscriptions
31+
32+
1. In your Slack App settings, go to **Event Subscriptions**
33+
2. Toggle **Enable Events** to On
34+
3. Set the **Request URL** to `https://<ngrok-url>/webhook`
35+
4. Slack will send a `url_verification` challenge — the server responds
36+
automatically
37+
5. Under **Subscribe to bot events**, add the events you need:
38+
- `message.channels` — public channels
39+
- `message.groups` — private channels
40+
- `message.im` — direct messages
41+
- `app_mention`@mentions anywhere
42+
6. Click **Save Changes**
43+
44+
## 4. Enable Interactivity (optional)
45+
46+
1. Go to **Interactivity & Shortcuts**
47+
2. Toggle **Interactivity** to On
48+
3. Set the **Request URL** to the same `https://<ngrok-url>/webhook`
49+
4. Click **Save Changes**
50+
51+
Block actions, modal submissions, and shortcuts will be published to NATS on
52+
`slack.interaction.{type}` subjects (e.g. `slack.interaction.block_actions`).
53+
54+
## 5. Register Slash Commands (optional)
55+
56+
1. Go to **Slash Commands** and click **Create New Command**
57+
2. Set the **Request URL** to the same `https://<ngrok-url>/webhook`
58+
3. Fill in the command name, description, and usage hint
59+
4. Click **Save**
60+
61+
Slash commands will be published to NATS on `slack.command.{command_name}`
62+
subjects (e.g. `slack.command.trogon`).
63+
64+
## 6. Install the app to your workspace
65+
66+
1. Go to **OAuth & Permissions**
67+
2. Under **Scopes → Bot Token Scopes**, ensure you have the scopes required
68+
by the events you subscribed to
69+
3. Click **Install to Workspace** and authorize
70+
71+
## 7. Verify
72+
73+
Send a message in a channel where the app is installed. You should see:
74+
75+
- The webhook receiver log the incoming event
76+
- The event published to NATS on `slack.event.message`
77+
78+
You can inspect NATS with:
79+
80+
```bash
81+
nats sub -s nats://nats.trogonai.orb.local:4222 "slack.>"
82+
```
83+
84+
Without `--profile dev`, ngrok is excluded and only the core services start.
85+
86+
## NATS subject mapping
87+
88+
| Slack payload | NATS subject | `X-Slack-Payload-Kind` header |
89+
|---|---|---|
90+
| Events API (`event_callback`) | `slack.event.{event.type}` | `event` |
91+
| Interactions (block actions, modals) | `slack.interaction.{type}` | `interaction` |
92+
| Slash commands | `slack.command.{command_name}` | `command` |
93+
94+
## Environment variables
95+
96+
| Variable | Required | Default | Description |
97+
|---|---|---|---|
98+
| `SLACK_SIGNING_SECRET` | yes || Slack app signing secret |
99+
| `NGROK_AUTHTOKEN` | yes (dev profile) || ngrok auth token |
100+
| `SLACK_WEBHOOK_PORT` | no | `3000` | HTTP port for the webhook receiver |
101+
| `SLACK_SUBJECT_PREFIX` | no | `slack` | NATS subject prefix |
102+
| `SLACK_STREAM_NAME` | no | `SLACK` | JetStream stream name |
103+
| `SLACK_STREAM_MAX_AGE_SECS` | no | `604800` | Max message age in seconds (default 7 days) |
104+
| `SLACK_NATS_ACK_TIMEOUT_SECS` | no | `10` | NATS acknowledgement timeout in seconds |
105+
| `SLACK_MAX_BODY_SIZE` | no | `1048576` | Max HTTP request body size in bytes (default 1 MB) |
106+
| `SLACK_TIMESTAMP_MAX_DRIFT_SECS` | no | `300` | Max allowed clock drift in seconds (default 5 min) |
107+
| `RUST_LOG` | no | `info` | Log level |

0 commit comments

Comments
 (0)