Skip to content

Commit 8f01a0e

Browse files
committed
feat(trogon-source-linear): add Linear webhook receiver that sinks events to NATS JetStream
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent d821ac0 commit 8f01a0e

File tree

13 files changed

+1883
-36
lines changed

13 files changed

+1883
-36
lines changed

devops/docker/compose/.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
1010
# GITHUB_NATS_ACK_TIMEOUT_SECS=10
1111
# GITHUB_MAX_BODY_SIZE=26214400
1212

13+
# --- Linear Source ---
14+
# LINEAR_WEBHOOK_SECRET=
15+
# LINEAR_WEBHOOK_PORT=8080
16+
# LINEAR_SUBJECT_PREFIX=linear
17+
# LINEAR_STREAM_NAME=LINEAR
18+
# LINEAR_STREAM_MAX_AGE_SECS=604800
19+
# LINEAR_WEBHOOK_TIMESTAMP_TOLERANCE_SECS=60
20+
# LINEAR_NATS_ACK_TIMEOUT_MS=10000
21+
1322
# --- Slack Source ---
1423
# SLACK_SIGNING_SECRET=
1524
# SLACK_WEBHOOK_PORT=3000

devops/docker/compose/compose.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,31 @@ services:
4242
start_period: 10s
4343
retries: 3
4444

45+
trogon-source-linear:
46+
build:
47+
context: ../../../rsworkspace
48+
dockerfile: ../devops/docker/compose/services/trogon-source-linear/Dockerfile
49+
environment:
50+
NATS_URL: "nats:4222"
51+
LINEAR_WEBHOOK_SECRET: "${LINEAR_WEBHOOK_SECRET}"
52+
LINEAR_WEBHOOK_PORT: "${LINEAR_WEBHOOK_PORT:-8080}"
53+
LINEAR_SUBJECT_PREFIX: "${LINEAR_SUBJECT_PREFIX:-linear}"
54+
LINEAR_STREAM_NAME: "${LINEAR_STREAM_NAME:-LINEAR}"
55+
LINEAR_STREAM_MAX_AGE_SECS: "${LINEAR_STREAM_MAX_AGE_SECS:-604800}"
56+
LINEAR_WEBHOOK_TIMESTAMP_TOLERANCE_SECS: "${LINEAR_WEBHOOK_TIMESTAMP_TOLERANCE_SECS:-60}"
57+
LINEAR_NATS_ACK_TIMEOUT_MS: "${LINEAR_NATS_ACK_TIMEOUT_MS:-10000}"
58+
RUST_LOG: "${RUST_LOG:-info}"
59+
depends_on:
60+
nats:
61+
condition: service_healthy
62+
restart: unless-stopped
63+
healthcheck:
64+
test: ["CMD", "curl", "-sf", "http://localhost:${LINEAR_WEBHOOK_PORT:-8080}/health"]
65+
interval: 10s
66+
timeout: 3s
67+
start_period: 10s
68+
retries: 3
69+
4570
trogon-source-slack:
4671
build:
4772
context: ../../../rsworkspace
@@ -73,6 +98,7 @@ services:
7398
environment:
7499
NGROK_AUTHTOKEN: "${NGROK_AUTHTOKEN:-}"
75100
GITHUB_ADDR: "trogon-source-github:${GITHUB_WEBHOOK_PORT:-8080}"
101+
LINEAR_ADDR: "trogon-source-linear:${LINEAR_WEBHOOK_PORT:-8080}"
76102
SLACK_ADDR: "trogon-source-slack:${SLACK_WEBHOOK_PORT:-3000}"
77103
entrypoint:
78104
- /bin/sh
@@ -84,6 +110,9 @@ services:
84110
github:
85111
addr: $${GITHUB_ADDR}
86112
proto: http
113+
linear:
114+
addr: $${LINEAR_ADDR}
115+
proto: http
87116
slack:
88117
addr: $${SLACK_ADDR}
89118
proto: http
@@ -92,6 +121,8 @@ services:
92121
depends_on:
93122
trogon-source-github:
94123
condition: service_healthy
124+
trogon-source-linear:
125+
condition: service_healthy
95126
trogon-source-slack:
96127
condition: service_healthy
97128
restart: unless-stopped
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-linear
21+
22+
COPY Cargo.toml Cargo.lock ./
23+
COPY crates/ crates/
24+
25+
RUN cargo build --release -p trogon-source-linear && \
26+
strip target/release/trogon-source-linear
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-linear /usr/local/bin/trogon-source-linear
38+
39+
USER trogon
40+
41+
EXPOSE 8080
42+
43+
STOPSIGNAL SIGTERM
44+
45+
ENTRYPOINT ["/usr/local/bin/trogon-source-linear"]
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Receiving Linear Webhooks Locally
2+
3+
## Prerequisites
4+
5+
- Docker Compose
6+
- A [Linear](https://linear.app) workspace with admin access
7+
- An [ngrok](https://ngrok.com) account (free tier works)
8+
9+
## 1. Get your ngrok tunnel URL
10+
11+
Start only ngrok and NATS to obtain a public URL before configuring Linear:
12+
13+
```bash
14+
docker compose --profile dev up ngrok nats
15+
```
16+
17+
Find the tunnel URL in the ngrok container logs:
18+
19+
```bash
20+
docker compose logs ngrok
21+
```
22+
23+
Look for the `linear` tunnel URL (e.g. `https://abc123.ngrok-free.app`).
24+
25+
## 2. Configure your Linear webhook
26+
27+
1. Go to **Settings → API → Webhooks**
28+
2. Click **New webhook**
29+
3. Set:
30+
- **URL**: `https://<ngrok-url>/webhook`
31+
4. Select the resource types you want to receive events for
32+
5. Save
33+
6. Copy the **signing secret** that Linear generates
34+
35+
## 3. Start the webhook receiver
36+
37+
Set `LINEAR_WEBHOOK_SECRET` in your `.env` to the signing secret from step 2,
38+
then bring up the full stack:
39+
40+
```bash
41+
docker compose --profile dev up
42+
```
43+
44+
## 4. Verify
45+
46+
Trigger an event in Linear (e.g. create an issue). You should see:
47+
48+
- The webhook receiver log the incoming event
49+
- The event published to NATS on `linear.{type}.{action}`
50+
51+
You can inspect NATS with:
52+
53+
```bash
54+
nats sub -s nats://nats.trogonai.orb.local:4222 "linear.>"
55+
```
56+
57+
Without `--profile dev`, ngrok is excluded and only the core services start.
58+
59+
## Environment variables
60+
61+
| Variable | Required | Default | Description |
62+
|---|---|---|---|
63+
| `LINEAR_WEBHOOK_SECRET` | yes || Signing secret from Linear's webhook settings |
64+
| `NGROK_AUTHTOKEN` | yes (dev profile) || ngrok auth token |
65+
| `LINEAR_WEBHOOK_PORT` | no | `8080` | HTTP port for the webhook receiver |
66+
| `LINEAR_SUBJECT_PREFIX` | no | `linear` | NATS subject prefix |
67+
| `LINEAR_STREAM_NAME` | no | `LINEAR` | JetStream stream name |
68+
| `LINEAR_STREAM_MAX_AGE_SECS` | no | `604800` | Max message age in seconds (7 days) |
69+
| `LINEAR_WEBHOOK_TIMESTAMP_TOLERANCE_SECS` | no | `60` | Replay-attack window in seconds (0 to disable) |
70+
| `LINEAR_NATS_ACK_TIMEOUT_MS` | no | `10000` | JetStream ACK timeout in milliseconds |
71+
| `NATS_URL` | no | `localhost:4222` | NATS server URL(s) |
72+
| `RUST_LOG` | no | `info` | Log level |

0 commit comments

Comments
 (0)