--- phase: 14-unraid-api-access plan: 01 type: execute wave: 1 depends_on: [] files_modified: - .env.unraid-api - .gitignore - n8n-workflow.json autonomous: true user_setup: - service: unraid-api why: "GraphQL API authentication for container update sync" env_vars: - name: UNRAID_API_KEY source: "Unraid WebGUI -> Settings -> Management Access -> API Keys -> Create, or SSH: unraid-api apikey --create --name 'Docker Manager Bot' --permissions 'DOCKER:UPDATE_ANY' --description 'Container update status sync' --json" - name: UNRAID_HOST source: "User's Unraid WebGUI URL (e.g., http://192.168.1.100 or http://tower.local). If using host.docker.internal, must add '--add-host=host.docker.internal:host-gateway' to n8n container Extra Parameters in Unraid Docker template editor." dashboard_config: - task: "Create API key with DOCKER:UPDATE_ANY permission" location: "Unraid WebGUI -> Settings -> Management Access -> API Keys, or via SSH" - task: "Add --add-host=host.docker.internal:host-gateway to n8n container Extra Parameters (if using host.docker.internal)" location: "Unraid WebGUI -> Docker -> n8n container -> Edit -> Extra Parameters" must_haves: truths: - "Credential template file exists with correct variable names matching .env.n8n-api pattern" - "Credential file is gitignored and will not be committed" - "n8n workflow contains HTTP Request node configured for Unraid GraphQL API" - "n8n workflow contains error handling for GraphQL response validation" - "Test query requests fields needed by downstream phases (id, names, state, isUpdateAvailable)" artifacts: - path: ".env.unraid-api" provides: "Credential template with UNRAID_HOST and UNRAID_API_KEY" contains: "UNRAID_HOST" - path: ".gitignore" provides: "Gitignore entry for .env.unraid-api" contains: ".env.unraid-api" - path: "n8n-workflow.json" provides: "Unraid GraphQL test query nodes in main workflow" contains: "Unraid" key_links: - from: "n8n-workflow.json (HTTP Request node)" to: "Unraid GraphQL API" via: "POST to UNRAID_HOST/graphql with x-api-key header" pattern: "/graphql" - from: "n8n-workflow.json (Code node)" to: "HTTP Request node" via: "Response validation and error handling" pattern: "data\\.docker\\.containers" --- Set up credential infrastructure and build Unraid GraphQL API test nodes in the main n8n workflow. Purpose: Establishes the foundation for Unraid API connectivity — credential storage mirroring the existing `.env.n8n-api` pattern, gitignore protection, and actual n8n workflow nodes that query the Unraid GraphQL API to validate connectivity, authentication, and container data structure. Output: `.env.unraid-api` template file, updated `.gitignore`, and new nodes in `n8n-workflow.json` for Unraid API testing. @/home/luc/.claude/get-shit-done/workflows/execute-plan.md @/home/luc/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/14-unraid-api-access/14-CONTEXT.md @.planning/phases/14-unraid-api-access/14-RESEARCH.md @ARCHITECTURE.md @CLAUDE.md @.gitignore @n8n-workflow.json Task 1: Create credential infrastructure .env.unraid-api, .gitignore 1. Create `.env.unraid-api` file with template values, mirroring the existing `.env.n8n-api` pattern: ``` UNRAID_HOST=http://host.docker.internal UNRAID_API_KEY=your-unraid-api-key-here ``` 2. Add `.env.unraid-api` to `.gitignore` on a new line after the existing `.env.n8n-api` entry. Keep the comment header relevant (update if needed, e.g., "Environment files with sensitive credentials"). 3. Update `CLAUDE.md` — add a new section "## Unraid API Access" (after the existing "## n8n API Access" section) documenting: - Credential file location and structure (`.env.unraid-api`) - Loading pattern: `. .env.unraid-api; curl -X POST "${UNRAID_HOST}/graphql" ...` - GraphQL query pattern for testing - Note that this mirrors the n8n API access pattern Per user decision: Dual storage approach — `.env.unraid-api` for CLI/deploy scripts, n8n Header Auth credential for workflow nodes. The n8n credential creation is a user setup step (documented in plan's user_setup frontmatter), not automated here. - `.env.unraid-api` exists with `UNRAID_HOST` and `UNRAID_API_KEY` variables - `grep -q ".env.unraid-api" .gitignore` returns 0 - `CLAUDE.md` contains "Unraid API Access" section Credential template file exists with correct variables, is gitignored, and usage is documented in CLAUDE.md Task 2: Add Unraid GraphQL test nodes to main workflow n8n-workflow.json Add a new branch to the main workflow's Keyword Router (Switch node) that handles an "unraid" keyword. This creates a test path: user sends "unraid" -> Keyword Router -> Unraid test nodes -> Telegram response. The branch needs 3 new nodes: **Node 1: HTTP Request node ("Unraid API Test")** - Method: POST - URL: `={{ $env.UNRAID_HOST }}/graphql` (reads from n8n environment variable, OR use expression that can be overridden) - Authentication: Header Auth - Credential name: "Unraid API Key" (Header Auth type, header name: `x-api-key`) - NOTE: The credential must be created manually by the user in n8n. Use credential ID placeholder — the user will need to update this after creating the credential. - Body (JSON): `{ "query": "query { docker { containers { id names state isUpdateAvailable } } }" }` - Options: "Ignore SSL Issues" = true (handles self-signed certs per research pitfall #2) - On Error: "Continue on Error" (so the Code node can handle errors gracefully) **Node 2: Code node ("Validate Unraid Response")** - Validates the GraphQL response: ```javascript const response = $input.item.json; // Check for HTTP errors (from continueOnError) if (response.error) { return { json: { action: 'send_message', text: `❌ Unraid API Error\n\n` + `Error: ${response.error.message || JSON.stringify(response.error)}\n\n` + `Check:\n` + `• Is UNRAID_HOST correct?\n` + `• Is the API key valid?\n` + `• Can n8n reach the Unraid host? (--add-host flag)\n` + `• Is Unraid GraphQL API enabled? (v7.2+ or Connect plugin)`, chatId: $('Telegram Trigger').item.json.message.chat.id } }; } // Check for GraphQL errors if (response.errors) { const errorMsg = response.errors.map(e => e.message).join(', '); return { json: { action: 'send_message', text: `❌ Unraid GraphQL Error\n\n${errorMsg}`, chatId: $('Telegram Trigger').item.json.message.chat.id } }; } // Validate response structure if (!response.data?.docker?.containers) { return { json: { action: 'send_message', text: '❌ Unexpected response\n\nNo container data in GraphQL response.', chatId: $('Telegram Trigger').item.json.message.chat.id } }; } const containers = response.data.docker.containers; const sampleId = containers[0]?.id || 'N/A'; const updateCount = containers.filter(c => c.isUpdateAvailable).length; return { json: { action: 'send_message', text: `✅ Unraid API Connected\n\n` + `Containers: ${containers.length}\n` + `Updates available: ${updateCount}\n` + `ID format: ${sampleId}\n\n` + `Sample:\n` + containers.slice(0, 5).map(c => `• ${c.names?.[0] || c.id} (${c.state})${c.isUpdateAvailable ? ' 🔄' : ''}` ).join('\n'), chatId: $('Telegram Trigger').item.json.message.chat.id } }; ``` - Uses clear, descriptive error messages per user decision (what failed, why, what to check) - Uses existing error logging pattern (structured return, same shape as Docker API errors) **Node 3: Telegram Send Message node ("Send Unraid Test Result")** - Chat ID: `={{ $json.chatId }}` - Text: `={{ $json.text }}` - Parse Mode: HTML - Use existing Telegram credential (ID: `I0xTTiASl7C1NZhJ`) **Wiring:** - Connect Keyword Router's new "unraid" output -> HTTP Request node -> Code node -> Telegram Send node - Add "unraid" as a new `contains` rule in the Keyword Router Switch node. Place it AFTER existing `startsWith` rules but the order among `contains` rules doesn't matter since "unraid" is unique. - Update the Keyword Router's `rules` array and `connections` accordingly. **Important n8n JSON patterns (from CLAUDE.md):** - Telegram credential: `{ "id": "I0xTTiASl7C1NZhJ", "name": "Telegram account" }` - Node IDs: Generate unique UUIDs for each new node - Position: Place nodes visually below/after existing keyword branches (check existing node positions for Y-offset pattern) - For the Header Auth credential: Use a placeholder credential reference. The user creates "Unraid API Key" credential in n8n manually and the credential ID gets set. Use name "Unraid API Key" in the node JSON. **Auth check:** The "unraid" command goes through the existing Auth IF node (same path as all other text commands), so it's already protected. - `python3 -c "import json; wf=json.load(open('n8n-workflow.json')); nodes=[n['name'] for n in wf['nodes']]; print('Unraid API Test' in nodes, 'Validate Unraid Response' in nodes, 'Send Unraid Test Result' in nodes)"` All three should print True. - Keyword Router Switch node contains "unraid" rule. - Connections wire: Keyword Router -> Unraid API Test -> Validate Unraid Response -> Send Unraid Test Result. - Push workflow to n8n: `. .env.n8n-api; ...` push recipe from CLAUDE.md. Verify HTTP 200. Main workflow contains a working "unraid" keyword branch with HTTP Request (GraphQL query), validation Code node (error handling with descriptive messages), and Telegram response node. Workflow pushes to n8n successfully. - `.env.unraid-api` exists with template values - `.gitignore` includes `.env.unraid-api` - `CLAUDE.md` documents Unraid API access pattern - `n8n-workflow.json` contains 3 new nodes: "Unraid API Test", "Validate Unraid Response", "Send Unraid Test Result" - Keyword Router has "unraid" rule - All connections properly wired - Workflow pushes to n8n without errors - Credential infrastructure mirrors existing `.env.n8n-api` pattern exactly - Test workflow branch is wired into main workflow's existing auth-protected text path - Error handling provides clear, actionable feedback (what failed, why, what to check) - Test query includes fields needed by Phase 15+: id, names, state, isUpdateAvailable After completion, create `.planning/phases/14-unraid-api-access/14-01-SUMMARY.md`