summaryrefslogtreecommitdiffhomepage
path: root/.rules/plan/webhook-forwarder.md
blob: 75b6cc4f7fbd76467470cd044c9d89cb7b058335 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
### Webhook Forwarder — Implementation Plan

---

### Overview

A minimal Rust HTTP server that accepts GitHub webhook POST requests on a public domain (`webhook.catgirls.rodeo`) and forwards them to Dokploy's internal deploy API. This avoids exposing Dokploy's panel to the public internet.

---

### URL Routing

| Incoming Request | Forwarded To |
|---|---|
| `POST webhook.catgirls.rodeo/<token>` | `POST {DOKPLOY_BASE_URL}/api/deploy/<token>` |
| `POST webhook.catgirls.rodeo/compose/<token>` | `POST {DOKPLOY_BASE_URL}/api/deploy/compose/<token>` |

All other methods/paths return `405 Method Not Allowed` or `404 Not Found`.

---

### Configuration

| Variable | Default | Description |
|---|---|---|
| `DOKPLOY_BASE_URL` | `http://100.102.55.49:3000` | Internal Dokploy panel URL |
| `PORT` | `8080` | Port the forwarder listens on |

No secrets are stored in this app. The deploy tokens live in GitHub's webhook config and Dokploy.

---

### Behavior

1. Accept only `POST` requests. Return `405` for all other methods.
2. Match paths:
   - `/<token>` → forward to `/api/deploy/<token>`
   - `/compose/<token>` → forward to `/api/deploy/compose/<token>`
   - Everything else → `404`
3. Forward the full request body and all headers from the incoming request to Dokploy.
4. Forward Dokploy's response (status code + body) back to the caller.
5. Log each request to stdout: timestamp, method, path, upstream status code.
6. On upstream connection failure, return `502 Bad Gateway`.

---

### Technology Stack

- **Language**: Rust
- **HTTP server**: `hyper` (minimal async HTTP library, no full framework overhead)
- **HTTP client**: `hyper` + `hyper-util` (reuse the same library for outbound requests)
- **Async runtime**: `tokio` (minimal feature set: `rt`, `net`, `macros`)
- **No other dependencies** unless strictly necessary

This yields a single static binary of ~2–4 MB with musl libc.

---

### File Structure

```
webhook-forwarder/
├── .dockerignore
├── .gitignore
├── Cargo.toml
├── Cargo.lock
├── Dockerfile
├── src/
│   └── main.rs
└── .rules/
    └── plan/
        └── webhook-forwarder.md   (this file)
```

Total: ~5 meaningful files. Minimal git repo.

---

### Dockerfile Strategy

Multi-stage build for smallest possible image:

```
Stage 1: rust:alpine (build with musl for static binary)
  - cargo build --release --target x86_64-unknown-linux-musl

Stage 2: scratch (empty image — just the binary)
  - COPY binary from stage 1
  - EXPOSE 8080
  - ENTRYPOINT ["./webhook-forwarder"]
```

Final image: ~3–5 MB total (just the static binary, no OS, no shell).

---

### Networking

**Primary approach (Option A):** Use Docker bridge gateway IP to reach Dokploy.

The container accesses Dokploy via the Docker host's bridge IP (typically `172.17.0.1` on default bridge, or the gateway of whatever network Dokploy uses). Set `DOKPLOY_BASE_URL=http://172.17.0.1:3000` in Dokploy's environment config for this app.

However, the default value is the Tailscale IP `http://100.102.55.49:3000` for direct use if networking allows.

**Fallback (Option B):** If the Docker bridge approach doesn't work (e.g., Dokploy's firewall blocks it), options include:
- `network_mode: host` in compose to give the container access to the host's Tailscale interface
- Running a Tailscale sidecar container
- Adding the container to Dokploy's own Docker network

We'll try Option A first since it's simplest.

---

### Dokploy Deployment

1. Push this repo to GitHub
2. In Dokploy, create a new **Application** (not Compose — single container, no need for compose)
3. Set source to the GitHub repo, branch `main`
4. Build type: **Dockerfile**
5. Environment variable: `DOKPLOY_BASE_URL=http://172.17.0.1:3000` (or whatever internal IP works)
6. Domain: `webhook.catgirls.rodeo`, port `8080`, HTTPS on, letsencrypt
7. Deploy

Then update GitHub webhook URLs for your other repos:
- Application deploys: `https://webhook.catgirls.rodeo/<token>`
- Compose deploys: `https://webhook.catgirls.rodeo/compose/<token>`

---

### Implementation Steps

1. **Initialize Cargo project**: `Cargo.toml` with minimal deps (`hyper`, `tokio`, `http-body-util`, `hyper-util`)
2. **Write `src/main.rs`**:
   - Read `DOKPLOY_BASE_URL` and `PORT` from env (with defaults)
   - Start hyper HTTP server on `0.0.0.0:{PORT}`
   - Route handler:
     - Reject non-POST → 405
     - Parse path: extract `/<token>` or `/compose/<token>`
     - Build upstream URL: `{base}/api/deploy/{path}`
     - Forward headers + body via hyper client
     - Return upstream response to caller
     - Log to stdout
3. **Write `Dockerfile`**: multi-stage, musl static build, `FROM scratch`
4. **Write `.dockerignore`**: exclude `target/`, `.git/`, `.rules/`, `*.md`
5. **Write `.gitignore`**: standard Rust ignores (`/target`)
6. **Test locally**: `cargo run`, then `curl -X POST localhost:8080/test-token`
7. **Push to GitHub, deploy via Dokploy**
8. **Test end-to-end**: update one GitHub repo's webhook URL, push, verify deploy triggers

---

### Resource Footprint Estimate

| Metric | Estimate |
|---|---|
| Docker image size | ~3–5 MB |
| RAM (idle) | ~1–2 MB |
| RAM (under load) | ~3–5 MB |
| CPU (idle) | ~0% |
| Git repo size | < 50 KB (excluding target/) |