{ "name": "Garvis Webhook - Bot Actions", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "garvis", "responseMode": "responseNode", "options": {} }, "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [260, 300], "webhookId": "garvis-webhook-001" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }, "conditions": [ { "id": "auth1", "leftValue": "={{ $json.headers['x-garvis-secret'] }}", "rightValue": "={{ $env.GARVIS_WEBHOOK_SECRET }}", "operator": { "type": "string", "operation": "equals" } } ], "combinator": "and" } }, "name": "IF Auth Valid?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [480, 300] }, { "parameters": { "options": {}, "respondWith": "json", "responseBody": "={\"error\": \"Unauthorized\", \"status\": 401}", "responseCode": 401 }, "name": "Respond 401", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [700, 500] }, { "parameters": { "rules": { "values": [ {"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "run_pipeline", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Run Pipeline"}, {"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "check_nas", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Check NAS"}, {"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "check_services", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Check Services"}, {"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "send_message", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Send Message"}, {"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "get_status", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Get Status"}, {"conditions": {"conditions": [{"leftValue": "={{ $json.body.action }}", "rightValue": "get_analytics", "operator": {"type": "string", "operation": "equals"}}]}, "renameOutput": true, "outputKey": "Get Analytics"} ] }, "options": {} }, "name": "Switch Action", "type": "n8n-nodes-base.switch", "typeVersion": 3, "position": [700, 300] }, { "parameters": { "mode": "manual", "duplicateItem": false, "assignments": { "assignments": [ {"id": "rp1", "name": "result", "value": "Pipeline triggered manually. Processing DropZone...", "type": "string"}, {"id": "rp2", "name": "action", "value": "run_pipeline", "type": "string"} ] } }, "name": "Handle Run Pipeline", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [960, 60] }, { "parameters": { "method": "POST", "url": "http://192.168.2.40:5000/webapi/entry.cgi", "sendBody": true, "bodyParameters": { "parameters": [ {"name": "api", "value": "SYNO.FileStation.List"}, {"name": "version", "value": "2"}, {"name": "method", "value": "list"}, {"name": "folder_path", "value": "/BlendedFamilyKitchen/DropZone"} ] } }, "name": "Handle Check NAS", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [960, 200] }, { "parameters": { "command": "echo '{\"docker\": \"'$(systemctl is-active docker)'\", \"n8n\": \"running\", \"whisper\": \"'$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:9000/health 2>/dev/null || echo 'unreachable')'\", \"ffmpeg\": \"'$(ffmpeg -version 2>/dev/null | head -1 || echo 'not installed')'\"}'" }, "name": "Handle Check Services", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [960, 340] }, { "parameters": { "method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"chat_id\": \"{{ $json.body.chat_id || 'TODO_DEFAULT_CHAT_ID' }}\",\n \"text\": \"{{ $json.body.message }}\",\n \"parse_mode\": \"Markdown\"\n}" }, "name": "Handle Send Message", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [960, 480] }, { "parameters": { "mode": "manual", "duplicateItem": false, "assignments": { "assignments": [ {"id": "gs1", "name": "status", "value": "operational", "type": "string"}, {"id": "gs2", "name": "workflows", "value": "content_pipeline: inactive, garvis_webhook: active", "type": "string"}, {"id": "gs3", "name": "uptime", "value": "={{ new Date().toISOString() }}", "type": "string"} ] } }, "name": "Handle Get Status", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [960, 620] }, { "parameters": { "options": {}, "respondWith": "json", "responseBody": "={\"success\": true, \"action\": \"{{ $json.body?.action || 'unknown' }}\", \"result\": {{ JSON.stringify($json) }}}", "responseCode": 200 }, "name": "Respond 200 Success", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [1200, 300] }, { "parameters": { "options": {}, "respondWith": "json", "responseBody": "={\"error\": \"Unknown action\", \"received\": \"{{ $json.body?.action }}\", \"available\": [\"run_pipeline\", \"check_nas\", \"check_services\", \"send_message\", \"get_status\", \"get_analytics\"]}", "responseCode": 400 }, "name": "Respond 400 Unknown", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [960, 800] }, { "parameters": { "content": "## 🔐 Garvis Webhook — Auth & Setup\n\n**Endpoint:** POST http://192.168.2.113:5678/webhook/garvis\n\n**Authentication:**\n- Header: x-garvis-secret\n- Validate against env var GARVIS_WEBHOOK_SECRET\n- Returns 401 if invalid\n\n**Request Format:**\n```json\n{\n \"action\": \"run_pipeline|check_nas|check_services|send_message|get_status\",\n \"message\": \"optional message text\",\n \"chat_id\": \"optional telegram chat id\"\n}\n```\n\n**Test with curl:**\n```\ncurl -X POST http://192.168.2.113:5678/webhook/garvis \\\n -H 'Content-Type: application/json' \\\n -H 'x-garvis-secret: YOUR_SECRET' \\\n -d '{\"action\": \"get_status\"}'\n```\n\n**TODO:**\n1. Set GARVIS_WEBHOOK_SECRET in n8n environment variables\n2. Set TELEGRAM_BOT_TOKEN in n8n environment variables\n3. Activate workflow when ready\n4. Test each action endpoint\n5. Add webhook URL to Garvis bot config", "height": 620, "width": 520 }, "name": "Sticky - Webhook Setup", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [200, -320] }, { "parameters": { "method": "POST", "url": "http://192.168.2.40:5000/webapi/entry.cgi", "sendBody": true, "bodyParameters": { "parameters": [ {"name": "api", "value": "SYNO.FileStation.List"}, {"name": "version", "value": "2"}, {"name": "method", "value": "list"}, {"name": "folder_path", "value": "/BlendedFamilyKitchen/Analytics"}, {"name": "sort_by", "value": "crtime"}, {"name": "sort_direction", "value": "desc"}, {"name": "limit", "value": "={{ $json.body.limit || 10 }}"} ] } }, "name": "Handle Get Analytics", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [960, 760] }, { "parameters": { "content": "## 🔀 Action Router — Available Actions\n\n**run_pipeline:** Manually triggers the Content Pipeline workflow. Use when Cloe drops a video and doesn't want to wait for the 30-min poll cycle.\n\n**check_nas:** Queries Synology FileStation API to list files in DropZone. Returns file count and names. Useful for: \"Garvis, anything in the drop zone?\"\n\n**check_services:** Runs local service checks — Docker, Whisper, FFmpeg availability. Returns health status JSON. Useful for: \"Garvis, is the pipeline infrastructure healthy?\"\n\n**send_message:** Forwards a message to a Telegram chat via the bot. Requires chat_id and message in request body. Useful for: cross-service notifications.\n\n**get_status:** Returns current n8n workflow states, uptime, and basic system info. Useful for: \"Garvis, n8n status?\"\n\n**TODO:**\n1. Wire run_pipeline to actually trigger Content Pipeline (use n8n Execute Workflow node)\n2. Add NAS auth to check_nas handler\n3. Expand check_services with more endpoints\n4. Add rate limiting / cooldown logic\n5. Add logging to each action handler", "height": 580, "width": 520 }, "name": "Sticky - Action Router", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [880, -320] } ], "connections": { "Webhook": {"main": [[{"node": "IF Auth Valid?", "type": "main", "index": 0}]]}, "IF Auth Valid?": { "main": [ [{"node": "Switch Action", "type": "main", "index": 0}], [{"node": "Respond 401", "type": "main", "index": 0}] ] }, "Switch Action": { "main": [ [{"node": "Handle Run Pipeline", "type": "main", "index": 0}], [{"node": "Handle Check NAS", "type": "main", "index": 0}], [{"node": "Handle Check Services", "type": "main", "index": 0}], [{"node": "Handle Send Message", "type": "main", "index": 0}], [{"node": "Handle Get Status", "type": "main", "index": 0}], [{"node": "Handle Get Analytics", "type": "main", "index": 0}], [{"node": "Respond 400 Unknown", "type": "main", "index": 0}] ] }, "Handle Run Pipeline": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]}, "Handle Check NAS": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]}, "Handle Check Services": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]}, "Handle Send Message": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]}, "Handle Get Status": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]}, "Handle Get Analytics": {"main": [[{"node": "Respond 200 Success", "type": "main", "index": 0}]]} }, "settings": { "executionOrder": "v1" } }