Add webhook auto-deploy: Gitea push → Coolify build

- Add webhook endpoints (setup/teardown/status) for batch management
- Add step 10 (webhook) to upsert pipeline for automatic setup
- Add DELETE /routes endpoint for Traefik route removal
- Add giteaFetch helper for Gitea API calls
- Document webhook flow, CIFS hooks fix, IPv6 healthcheck gotcha
- Update port allocation table (all 11 apps deployed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 13:27:14 -06:00
parent 5119c94b37
commit dc8f392ff5
2 changed files with 468 additions and 34 deletions

123
README.md
View File

@@ -104,10 +104,19 @@ All endpoints return JSON. The Vite dev server proxies `/api/*` to the API serve
| GET | `/api/coolify/servers` | List Coolify servers |
| GET | `/api/coolify/next-port` | Get next available HOST_PORT |
| POST | `/api/coolify/routes` | Add Traefik route via SSH |
| DELETE | `/api/coolify/routes` | Remove Traefik route via SSH |
| POST | `/api/coolify/drift` | Check drift between coolify.json and live state |
| POST | `/api/coolify/upsert` | Full pipeline: config → create/update → env → route → deploy |
| POST | `/api/coolify/upsert` | Full pipeline: config → create/update → env → route → webhook → deploy |
| GET | `/api/coolify/config?path=...` | Read coolify.json from a project |
### Webhooks
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/coolify/webhooks/setup` | Set Coolify webhook secret + create Gitea webhook on all apps |
| DELETE | `/api/coolify/webhooks/teardown` | Remove webhook secret + delete Gitea webhooks |
| GET | `/api/coolify/webhooks/status` | Check webhook configuration status per app |
### System
| Method | Path | Description |
@@ -120,7 +129,7 @@ All endpoints return JSON. The Vite dev server proxies `/api/*` to the API serve
### Coolify Config (`gitea.repo.management/config.json`)
Shared with the gitea.repo.management project. Contains Coolify API credentials, server UUIDs, SSH config:
Shared with the gitea.repo.management project. Contains Coolify API credentials, server UUIDs, SSH config, and webhook/Gitea settings:
```json
{
@@ -134,7 +143,13 @@ Shared with the gitea.repo.management project. Contains Coolify API credentials,
"sshUser": "clint",
"sshPassword": "<password>",
"traefikPath": "/home/clint/containers/coolify/data/proxy/dynamic/custom.yml",
"targetIp": "192.168.69.5"
"targetIp": "192.168.69.5",
"domain": "dotrepo.com",
"webhookSecret": "<shared-hmac-secret>"
},
"gitea": {
"url": "http://192.168.69.4:2003",
"token": "<gitea-api-token>"
}
}
```
@@ -172,6 +187,7 @@ The `POST /api/coolify/upsert` endpoint runs the full deploy pipeline:
7. **route** — Add Traefik route (remote) or set FQDN (local)
8. **changelog** — Write coolify.changelog.json to repo
9. **deploy** — Trigger Coolify deployment
10. **webhook** — Set Coolify webhook secret + create/update Gitea webhook for auto-deploy on push
## Docker Deploy Pipeline (Legacy/Direct)
@@ -201,9 +217,11 @@ The `POST /api/docker/deploy` endpoint handles tar-based deployment:
| 2004 | Every Noise at Once | Deployed |
| 2005 | Learn X In Y | Deployed |
| 2006 | Actual Budget Report | Deployed |
| 2007 | dotrepo-timer | Pending |
| 2008 | dotrepo-travel | Pending |
| 2007 | dotrepo-timer | Deployed |
| 2008 | dotrepo-travel | Deployed |
| 2009 | dotrepo-racker | Deployed |
| 2011 | spike-breakdown | Deployed |
| 2012 | dotrepo-site | Deployed |
## LLM / Agent Usage
@@ -231,3 +249,98 @@ curl "http://localhost:3100/api/servers/1771305558920/logs?remotePath=~/containe
```
**Always use this API** to access/change Coolify and Gitea deployment state. Do not call the Coolify API directly — this API wraps it with enrichment (HOST_PORT), safety (drift detection, changelog), and routing (Traefik management).
## Webhook Auto-Deploy
Every `git push` to Gitea automatically triggers a Coolify deployment. The pipeline:
```
git push → Gitea post-receive hook → Gitea webhook POST → Coolify webhook handler → Docker build + deploy
```
### How It Works
1. Each Coolify app has a `manual_webhook_secret_gitea` — a shared HMAC-SHA256 secret
2. Each Gitea repo has a webhook pointing to `http://192.168.69.4:2010/webhooks/source/gitea/events/manual`
3. On push, Gitea signs the payload with the secret and POSTs to Coolify
4. Coolify validates the signature, matches the repo+branch, and queues a deployment
### Setup
Webhooks are automatically configured during `upsert` (step 10). To set up webhooks on all existing apps:
```bash
# Set up webhooks on all Coolify apps
curl -X POST http://localhost:3100/api/coolify/webhooks/setup
# Check status
curl http://localhost:3100/api/coolify/webhooks/status
# Remove all webhooks
curl -X DELETE http://localhost:3100/api/coolify/webhooks/teardown
```
### Key Details
- **Webhook URL must use internal IP** (`http://192.168.69.4:2010`), NOT the public URL (`https://coolify.clintmasden.duckdns.org`). The public URL fails due to hairpin NAT — Gitea and Coolify are on the same box (192.168.69.4).
- **Gitea ports**: 2003 = HTTP API, 2004 = SSH. Do not confuse them.
- **Branch**: All repos use `master` (not `main`). Coolify's `git_branch` must match. The upsert pipeline defaults to `master`.
## Gotchas and Troubleshooting
### Docker Healthcheck IPv6 Issue
Alpine Linux resolves `localhost` to `::1` (IPv6) before `127.0.0.1` (IPv4). If your container only listens on IPv4 (e.g., nginx on `0.0.0.0:3000`), healthchecks using `localhost` will fail silently.
**Fix**: Always use `127.0.0.1` instead of `localhost` in healthcheck commands:
```yaml
# docker-compose.yml
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/"]
```
```dockerfile
# Dockerfile
HEALTHCHECK CMD wget -qO- http://127.0.0.1:3000/ || exit 1
```
### Gitea CIFS Hooks Not Executable
Gitea's git data is stored on a CIFS/SMB mount (`//192.168.69.2/archive`). CIFS doesn't support per-file permissions — all files inherit `file_mode` from the mount options. If `file_mode=0664` (the default), git hooks won't execute because they lack the execute bit. This silently breaks:
- Webhook delivery (post-receive hook doesn't fire)
- Branch tracking (Gitea sees repos as "empty")
- All git server-side hooks
**Fix**: Change `file_mode=0664` to `file_mode=0775` in `/etc/fstab`:
```
//192.168.69.2/archive /home/clint/archive cifs ...,file_mode=0775,... 0 0
```
Then remount: `sudo mount -o remount /home/clint/archive`
After remounting, run `resync_all_hooks` via Gitea admin API to regenerate hook files:
```bash
curl -X POST -H "Authorization: token <token>" \
"http://192.168.69.4:2003/api/v1/admin/cron/resync_all_hooks"
```
### Gitea Default Branch Mismatch
All repos use `master` as their default branch. If Gitea's `default_branch` setting is `main` (the Gitea default for new repos), it will report the repo as `empty: true` and won't deliver webhooks for pushes to `master`.
**Fix**: Set `default_branch` to `master` via API:
```bash
curl -X PATCH -H "Authorization: token <token>" \
-H "Content-Type: application/json" \
"http://192.168.69.4:2003/api/v1/repos/clint/<repo>" \
-d '{"default_branch":"master"}'
```
### dockerComposeLocation vs baseDirectory
In coolify.json, `dockerComposeLocation` is **relative to** `baseDirectory`. If your `baseDirectory` is `/myapp`, set `dockerComposeLocation` to `/docker-compose.yml`, NOT `/myapp/docker-compose.yml`.