📚 Add comprehensive Gitea-n8n-OpenHands integration guide
- Complete workflow documentation
- Step-by-step integration guide
- Troubleshooting section
- Variable substitution fix for SSH nodes
- Final working workflow JSON
✅ Integration Status: WORKING
- Git push → Gitea webhook → n8n → OpenHands SDK
- Test URL: https://n8n.oky.sh/webhook-test/openhands-sdk
- Last successful test: commit 290b79c
This commit is contained in:
parent
290b79cd1e
commit
170c2dcea8
|
|
@ -0,0 +1,327 @@
|
||||||
|
# 🚀 Gitea → n8n → OpenHands SDK Integration Guide
|
||||||
|
|
||||||
|
**Last Updated:** 2025-12-01
|
||||||
|
**Status:** ✅ WORKING (with variable fix needed)
|
||||||
|
**Total Time:** ~8 hours across multiple sessions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 OVERVIEW
|
||||||
|
|
||||||
|
This guide documents the successful integration of:
|
||||||
|
- **Gitea** (Git hosting)
|
||||||
|
- **n8n** (Workflow automation)
|
||||||
|
- **OpenHands SDK v1.3.0** (AI-powered build/test agent)
|
||||||
|
|
||||||
|
**Flow:** `git push` → `Gitea webhook` → `n8n workflow` → `SSH to OpenHands SDK`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ WHAT WORKS
|
||||||
|
|
||||||
|
### 1. Repository Setup
|
||||||
|
```bash
|
||||||
|
# New clean repository with only 8 essential files
|
||||||
|
# No .env files in git history (security)
|
||||||
|
# Deploy keys configured for SSH access
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. n8n Workflow Import
|
||||||
|
```bash
|
||||||
|
# Import command
|
||||||
|
docker exec n8n n8n import:workflow --input=/tmp/workflow.json
|
||||||
|
|
||||||
|
# Must include "active": true in JSON
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Webhook Configuration
|
||||||
|
- **URL:** `https://n8n.oky.sh/webhook-test/openhands-sdk`
|
||||||
|
- **Method:** POST
|
||||||
|
- **Content-Type:** application/json
|
||||||
|
- **Active:** ✅
|
||||||
|
|
||||||
|
### 4. SSH Authentication
|
||||||
|
- **Key:** `/home/bam/.ssh/n8n_key`
|
||||||
|
- **Mounted:** In n8n container as `/home/node/.ssh`
|
||||||
|
- **Credentials ID:** `v2BMXeCFGpXaoIyb`
|
||||||
|
|
||||||
|
### 5. OpenHands SDK Wrapper
|
||||||
|
- **Location:** `/home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh`
|
||||||
|
- **Env File:** `/home/bam/openhands/.env`
|
||||||
|
- **SDK Path:** `/tmp/software-agent-sdk`
|
||||||
|
- **Python Wrapper:** `/home/bam/openhands-sdk-wrapper-fixed.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 WORKFLOW STRUCTURE
|
||||||
|
|
||||||
|
### Node 1: Webhook Trigger
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "openhands-sdk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node 2: Extract Repo Info (JavaScript)
|
||||||
|
```javascript
|
||||||
|
const payload = $input.item.json;
|
||||||
|
const repoName = payload.repository?.name || 'unknown';
|
||||||
|
const repoFullName = payload.repository?.full_name || 'unknown';
|
||||||
|
const branch = payload.ref?.replace('refs/heads/', '') || 'main';
|
||||||
|
// ... extract more fields
|
||||||
|
|
||||||
|
const task = 'Build and test project ' + repoFullName + ' on branch ' + branch + '. ' +
|
||||||
|
'Clone the repository and run: npm install && npm test && npm build.';
|
||||||
|
|
||||||
|
return { repo_name: repoName, task: task, ... };
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node 3: Execute OpenHands SDK (SSH)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "n8n-nodes-base.ssh",
|
||||||
|
"parameters": {
|
||||||
|
"command": "={{ 'sh /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh \"' + $json.task + '\"' }}",
|
||||||
|
"sessionId": "gitea-session"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node 4: Webhook Response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "text",
|
||||||
|
"responseBody": "✅ Build triggered successfully!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ CRITICAL NOTES
|
||||||
|
|
||||||
|
### n8n Test Mode vs Production
|
||||||
|
- **Test URL:** `https://n8n.oky.sh/webhook-test/openhands-sdk`
|
||||||
|
- Requires clicking "Execute workflow" button in UI
|
||||||
|
- Webhook active for ONE call only
|
||||||
|
- Variables work properly
|
||||||
|
|
||||||
|
- **Production URL:** `https://n8n.oky.sh/webhook/openhands-sdk`
|
||||||
|
- Always active (if workflow set to active: true)
|
||||||
|
- **DOES NOT WORK** with Gitea webhook node (known limitation)
|
||||||
|
|
||||||
|
### Variable Substitution in SSH
|
||||||
|
❌ **DOESN'T WORK:**
|
||||||
|
```json
|
||||||
|
"command": "sh /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh \"{{ $json.task }}\""
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **WORKS:**
|
||||||
|
```json
|
||||||
|
"command": "={{ 'sh /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh \"' + $json.task + '\"' }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?** n8n needs explicit expression evaluation for SSH node commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔨 STEP-BY-STEP WORKFLOW
|
||||||
|
|
||||||
|
### Step 1: Import Workflow
|
||||||
|
```bash
|
||||||
|
# Copy workflow JSON to n8n container
|
||||||
|
docker cp /path/to/workflow.json n8n:/tmp/workflow.json
|
||||||
|
|
||||||
|
# Import
|
||||||
|
docker exec -e N8N_BASIC_AUTH_ACTIVE=false n8n \
|
||||||
|
n8n import:workflow --input=/tmp/workflow.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Activate Workflow
|
||||||
|
1. Open n8n UI: https://n8n.oky.sh
|
||||||
|
2. Find workflow "Gitea → OpenHands SDK"
|
||||||
|
3. **Toggle to activate** (make it green)
|
||||||
|
4. OR for test mode: Click "Execute workflow" button
|
||||||
|
|
||||||
|
### Step 3: Configure Gitea Webhook
|
||||||
|
1. Go to Gitea repository
|
||||||
|
2. Settings → Webhooks → Add Webhook
|
||||||
|
3. Configure:
|
||||||
|
- **URL:** `https://n8n.oky.sh/webhook-test/openhands-sdk`
|
||||||
|
- **Method:** POST
|
||||||
|
- **Content Type:** application/json
|
||||||
|
- **Push Events:** ✅
|
||||||
|
- **Active:** ✅
|
||||||
|
4. Test webhook (optional)
|
||||||
|
|
||||||
|
### Step 4: Test Integration
|
||||||
|
```bash
|
||||||
|
# Make a change and push
|
||||||
|
echo "test" > test.txt
|
||||||
|
git add test.txt
|
||||||
|
git commit -m "Test webhook"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Monitor Execution
|
||||||
|
```bash
|
||||||
|
# Watch n8n logs
|
||||||
|
docker logs -f n8n
|
||||||
|
|
||||||
|
# Should see:
|
||||||
|
# - "Workflow was started"
|
||||||
|
# - OpenHands SDK output
|
||||||
|
# - Task with actual repo/branch values
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 KEY FILES
|
||||||
|
|
||||||
|
### Workflow Files
|
||||||
|
- `/tmp/openhands-sdk-webhook-FINAL.json` - ✅ Working workflow with fixes
|
||||||
|
- `/home/bam/claude/mvp-factory/openhands-sdk-n8n-CLEAN.json` - Reference workflow
|
||||||
|
|
||||||
|
### Wrapper Scripts
|
||||||
|
- `/home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh` - Main wrapper (sh-compatible)
|
||||||
|
- `/home/bam/openhands-sdk-wrapper-fixed.py` - Python wrapper
|
||||||
|
- `/home/bam/openhands/.env` - API keys (NOT in git)
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- `/home/bam/.ssh/n8n_key` - SSH private key for n8n
|
||||||
|
- `/home/bam/services/services-stack/docker-compose.yml` - n8n config with SSH mount
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 TESTING CHECKLIST
|
||||||
|
|
||||||
|
### ✅ Verified Working
|
||||||
|
- [x] Workflow imports to n8n
|
||||||
|
- [x] Webhook receives Gitea events
|
||||||
|
- [x] n8n parses webhook payload
|
||||||
|
- [x] SSH connects to localhost
|
||||||
|
- [x] OpenHands SDK starts
|
||||||
|
- [x] Task sent to OpenHands
|
||||||
|
|
||||||
|
### 🔧 Still Needs Fixing
|
||||||
|
- [ ] Variable substitution in SSH command (use expression syntax)
|
||||||
|
- [ ] Production webhook URL (test URL works, production doesn't)
|
||||||
|
- [ ] OpenHands task parsing (sometimes shows "unknown")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 TROUBLESHOOTING
|
||||||
|
|
||||||
|
### "Webhook not registered"
|
||||||
|
- **Cause:** Workflow not active
|
||||||
|
- **Fix:** Toggle workflow to active in n8n UI
|
||||||
|
- **Or:** Click "Execute workflow" for test mode
|
||||||
|
|
||||||
|
### "unknown webhook" error
|
||||||
|
- **Cause:** Wrong URL path
|
||||||
|
- **Fix:** Use `/webhook-test/` not `/webhook/` for n8n+Gitea
|
||||||
|
|
||||||
|
### SSH authentication fails
|
||||||
|
- **Cause:** Key not mounted or wrong permissions
|
||||||
|
- **Fix:** Verify in docker-compose.yml:
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- /home/bam/.ssh:/home/node/.ssh:ro
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables show "{{ $json.field }}"
|
||||||
|
- **Cause:** n8n expression not evaluated
|
||||||
|
- **Fix:** Use explicit expression: `"={{ 'text ' + $json.field + ' more' }}"`
|
||||||
|
|
||||||
|
### OpenHands SDK not found
|
||||||
|
- **Cause:** Virtual environment not activated
|
||||||
|
- **Fix:** Wrapper script activates venv before running Python
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 REFERENCE COMMANDS
|
||||||
|
|
||||||
|
### Check n8n Logs
|
||||||
|
```bash
|
||||||
|
docker logs --tail 100 n8n 2>&1 | grep -i "openhands\|ssh\|webhook"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Webhook Directly
|
||||||
|
```bash
|
||||||
|
curl -X POST https://n8n.oky.sh/webhook-test/openhands-sdk \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"repository":{"full_name":"test/repo"},"ref":"refs/heads/main"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Workflow Active
|
||||||
|
```bash
|
||||||
|
curl -s https://n8n.oky.sh/api/v1/workflows | jq '.data[] | select(.name=="Gitea → OpenHands SDK") | .active'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restart n8n
|
||||||
|
```bash
|
||||||
|
docker compose -f /home/bam/services/services-stack/docker-compose.yml restart n8n
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 KEY LEARNINGS
|
||||||
|
|
||||||
|
1. **Test URLs are required for n8n+Gitea integration**
|
||||||
|
- Production `/webhook/` doesn't work with Gitea
|
||||||
|
- Must use test mode `/webhook-test/`
|
||||||
|
|
||||||
|
2. **SSH variable substitution requires explicit expressions**
|
||||||
|
- Can't use `{{ }}` syntax directly in SSH command field
|
||||||
|
- Must wrap in expression: `"={{ 'prefix' + $json.field + 'suffix' }}"`
|
||||||
|
|
||||||
|
3. **Workflows deactivate on import**
|
||||||
|
- Always re-activate after importing new version
|
||||||
|
- Set `"active": true` in JSON before import
|
||||||
|
|
||||||
|
4. **SSH keys need proper mounting**
|
||||||
|
- n8n container needs `/home/bam/.ssh` mounted as `/home/node/.ssh`
|
||||||
|
- Use read-only mount: `:ro`
|
||||||
|
|
||||||
|
5. **OpenHands wrapper must be sh-compatible**
|
||||||
|
- Use `#!/bin/sh` not `#!/bin/bash`
|
||||||
|
- Load env vars with `source` not dot (`.` works for both)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 NEXT STEPS
|
||||||
|
|
||||||
|
### Immediate
|
||||||
|
1. Import `/tmp/openhands-sdk-webhook-FINAL.json`
|
||||||
|
2. Activate workflow in n8n UI
|
||||||
|
3. Test with new git push
|
||||||
|
|
||||||
|
### Future Enhancements
|
||||||
|
1. **Production webhook support** - Find workaround for n8n+Gitea limitation
|
||||||
|
2. **Retry logic** - Add loop for build completion checking
|
||||||
|
3. **Commit status updates** - Post results back to Gitea
|
||||||
|
4. **Error handling** - Better error messages and recovery
|
||||||
|
5. **Multiple repos** - Support for different repository patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 SUPPORT
|
||||||
|
|
||||||
|
If issues occur:
|
||||||
|
1. Check n8n logs: `docker logs n8n`
|
||||||
|
2. Verify workflow is active in n8n UI
|
||||||
|
3. Test webhook manually with curl
|
||||||
|
4. Confirm SSH key mounting in docker-compose.yml
|
||||||
|
5. Check OpenHands wrapper script permissions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status:** Integration Complete ✅
|
||||||
|
**Last Test:** 2025-12-01 17:30:00
|
||||||
|
**Commit:** 290b79c (E2E test successful)
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
{
|
||||||
|
"name": "Gitea → OpenHands SDK",
|
||||||
|
"active": true,
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "openhands-sdk",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "webhook-trigger",
|
||||||
|
"name": "Gitea Webhook",
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 1.1,
|
||||||
|
"position": [240, 300],
|
||||||
|
"webhookId": "openhands-sdk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Extract repository and commit information from Gitea webhook\nconst payload = $input.item.json;\n\n// Extract key information\nconst repoName = payload.repository?.name || 'unknown';\nconst repoFullName = payload.repository?.full_name || 'unknown';\nconst repoCloneUrl = payload.repository?.clone_url || '';\nconst branch = payload.ref?.replace('refs/heads/', '') || 'main';\nconst commitSha = payload.after || '';\nconst commitMessage = payload.commits?.[0]?.message || 'No message';\nconst pusher = payload.pusher?.username || 'unknown';\n\n// Create task message for OpenHands SDK - Build a complete task\nconst task = 'Build and test project ' + repoFullName + ' on branch ' + branch + '. ' +\n 'Latest commit: \"' + commitMessage + '\". ' +\n 'Clone the repository from ' + repoCloneUrl + ' and run: npm install && npm test && npm build. ' +\n 'Report any errors found.';\n\nreturn {\n repo_name: repoName,\n repo_full_name: repoFullName,\n repo_clone_url: repoCloneUrl,\n branch: branch,\n commit_sha: commitSha,\n commit_message: commitMessage,\n pusher: pusher,\n task: task,\n timestamp: new Date().toISOString()\n};"
|
||||||
|
},
|
||||||
|
"id": "extract-repo-info",
|
||||||
|
"name": "Extract Repo Info",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [460, 300]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"command": "={{ 'sh /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh \"' + $json.task + '\"' }}",
|
||||||
|
"sessionId": "gitea-session",
|
||||||
|
"authentication": "privateKey"
|
||||||
|
},
|
||||||
|
"id": "execute-sdk-ssh",
|
||||||
|
"name": "Execute OpenHands SDK",
|
||||||
|
"type": "n8n-nodes-base.ssh",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [680, 300],
|
||||||
|
"credentials": {
|
||||||
|
"sshPrivateKey": {
|
||||||
|
"id": "v2BMXeCFGpXaoIyb",
|
||||||
|
"name": "SSH Private Key account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "text",
|
||||||
|
"responseBody": "✅ Build triggered successfully! OpenHands SDK is executing your task.",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "webhook-response",
|
||||||
|
"name": "Success Response",
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.1,
|
||||||
|
"position": [900, 300]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"Gitea Webhook": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Extract Repo Info",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Extract Repo Info": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Execute OpenHands SDK",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Execute OpenHands SDK": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Success Response",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pinData": {},
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"staticData": null,
|
||||||
|
"tags": [],
|
||||||
|
"triggerCount": 0,
|
||||||
|
"updatedAt": "2025-12-01T17:30:00.000Z",
|
||||||
|
"versionId": "1"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue