Files
unraid-docker-manager/.planning/phases/16-api-migration/16-06-PLAN.md
T
Lucas Berger d5bc0be2fe docs(16-06): add critical lessons from hotfix to gap closure plan
Incorporates 5 lessons from commit 216f3a4 into the plan:
- Connection keys/targets must use node names, not IDs
- HTTP auth must use Header Auth credential, not manual env vars
- Node names must be unique
- Use $('Node Name') after GraphQL chains, not $input.item.json
- Added validation checklist to verification section

Marks Task 2 as already completed (dead code removal done in hotfix).
Updates node counts from 193 to 181 baseline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 11:31:53 -05:00

255 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 16-api-migration
plan: 06
type: execute
wave: 1
depends_on: []
files_modified: [n8n-workflow.json]
autonomous: true
gap_closure: true
must_haves:
truths:
- "Text command 'start/stop/restart <container>' queries containers via GraphQL, not Docker socket proxy"
- "Text command 'update <container>' queries containers via GraphQL, not Docker socket proxy"
- "Text command 'batch' queries containers via GraphQL, not Docker socket proxy"
- "Zero active Execute Command nodes with docker-socket-proxy references remain in n8n-workflow.json"
artifacts:
- path: "n8n-workflow.json"
provides: "Main workflow with all text command paths using GraphQL"
contains: "UNRAID_HOST"
key_links:
- from: "n8n-workflow.json (Query Containers for Action)"
to: "Unraid GraphQL API"
via: "POST to $env.UNRAID_HOST/graphql"
pattern: "UNRAID_HOST.*graphql"
- from: "n8n-workflow.json (Query Containers for Update)"
to: "Unraid GraphQL API"
via: "POST to $env.UNRAID_HOST/graphql"
pattern: "UNRAID_HOST.*graphql"
- from: "n8n-workflow.json (Query Containers for Batch)"
to: "Unraid GraphQL API"
via: "POST to $env.UNRAID_HOST/graphql"
pattern: "UNRAID_HOST.*graphql"
---
<objective>
Migrate the 3 remaining text command entry points in the main workflow from Docker socket proxy Execute Command nodes to Unraid GraphQL API queries.
Purpose: Close the verification gaps that block Phase 17 (docker-socket-proxy removal). The 3 text command paths (start/stop/restart, update, batch) still use Execute Command nodes with `curl` to the docker-socket-proxy. After this plan, ALL container operations in the main workflow use GraphQL -- zero Docker socket proxy dependencies remain.
Output: Updated n8n-workflow.json with 3 GraphQL query chains replacing 3 Execute Command nodes.
NOTE: Dead code removal (Task 2 originally) and orphan cleanup were already completed in commit 216f3a4. The current node count is 181, not 193. Only Task 1 remains.
</objective>
<critical_lessons>
Plans 16-02 through 16-05 introduced defects that required a hotfix (commit 216f3a4). Do NOT repeat these mistakes:
1. **Connection keys MUST use node NAMES, never node IDs.**
n8n resolves connections by node name. Using IDs as dictionary keys (e.g., `"http-get-container-for-action"`) creates orphaned wiring that silently fails at runtime.
- WRONG: `"connections": { "http-my-node-id": { "main": [...] } }`
- RIGHT: `"connections": { "My Node Display Name": { "main": [...] } }`
2. **Connection targets MUST also use node NAMES, never IDs.**
- WRONG: `{ "node": "code-normalizer-action", "type": "main", "index": 0 }`
- RIGHT: `{ "node": "Normalize GraphQL Response (Action)", "type": "main", "index": 0 }`
3. **GraphQL HTTP Request nodes MUST use Header Auth credential, NOT manual headers.**
Using `$env.UNRAID_API_KEY` as a manual header causes `Invalid CSRF token` / `UNAUTHENTICATED` errors. The correct config:
- `"authentication": "genericCredentialType"`
- `"genericAuthType": "httpHeaderAuth"`
- `"credentials": { "httpHeaderAuth": { "id": "unraid-api-key-credential-id", "name": "Unraid API Key" } }`
- Do NOT add `x-api-key` to `headerParameters` — the credential handles it.
Copy the exact auth config from any existing working node (e.g., "Get Container For Action").
4. **Node names MUST be unique.** Duplicate names cause connection ambiguity. n8n cannot distinguish which node a connection refers to.
5. **After a GraphQL query chain (HTTP → Normalizer → Registry), downstream Code nodes receive container item arrays, NOT upstream preparation data.** Use `$('Upstream Node Name').item.json` to reference data from before the chain. Using `$input.item.json` will give you a container object, not the preparation data.
</critical_lessons>
<execution_context>
@/home/luc/.claude/get-shit-done/workflows/execute-plan.md
@/home/luc/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/STATE.md
@.planning/phases/16-api-migration/16-01-SUMMARY.md
@.planning/phases/16-api-migration/16-05-SUMMARY.md
@.planning/phases/16-api-migration/16-VERIFICATION.md
@n8n-workflow.json
@CLAUDE.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Replace 3 Execute Command nodes with GraphQL query chains</name>
<files>n8n-workflow.json</files>
<action>
Replace 3 Execute Command nodes that use `curl` to docker-socket-proxy with GraphQL HTTP Request + Normalizer + Registry Update chains. Follow the exact same pattern established in Plan 16-05 (Task 1) for the 6 inline keyboard query paths.
**Node 1: "Docker List for Action" (id: exec-docker-list-action)**
Current: Execute Command node running `curl -s --max-time 5 'http://docker-socket-proxy:2375/v1.47/containers/json?all=true'`
Position: [1120, 400]
Connected FROM: "Parse Action Command"
Connected TO: "Prepare Action Match Input"
Replace with 3 nodes:
1a. **"Query Containers for Action"** — HTTP Request node (replaces Execute Command)
- type: n8n-nodes-base.httpRequest, typeVersion: 4.2
- method: POST
- url: `={{ $env.UNRAID_HOST }}/graphql`
- authentication: genericCredentialType, genericAuthType: httpHeaderAuth
- credentials: `{ "httpHeaderAuth": { "id": "unraid-api-key-credential-id", "name": "Unraid API Key" } }`
- Do NOT add manual x-api-key headers — the credential handles auth automatically
- sendBody: true, specifyBody: json
- jsonBody: `{"query": "query { docker { containers { id names state image status } } }"}`
- options: timeout: 15000
- position: [1120, 400]
- Copy the full node structure from an existing working node (e.g., "Get Container For Action") and only change name, id, position, and jsonBody
1b. **"Normalize Action Containers"** — Code node (GraphQL response normalizer)
- Inline normalizer code (same as Plan 16-01/16-05 pattern):
- Extract `data.docker.containers` from GraphQL response
- Map fields: id->Id, names->Names (add '/' prefix), state->State (RUNNING->running, STOPPED->exited, PAUSED->paused), image->Image, status->Status
- Handle GraphQL errors (check response.errors array)
- position: [1230, 400] (shift right to make room)
1c. **"Update Registry (Action)"** — Code node (Container ID Registry update)
- Inline registry update code (same as Plan 16-01/16-05 pattern):
- Read static data `_containerIdRegistry`, parse JSON
- Map each normalized container: name (strip '/') -> { name, unraidId: container.Id }
- Write back to static data with JSON.stringify (top-level assignment for persistence)
- Pass through all container items unchanged
- position: [1340, 400] (note: this is where "Prepare Action Match Input" currently sits)
**CRITICAL wiring change for "Prepare Action Match Input":**
- Move "Prepare Action Match Input" position to [1450, 400] (shift right to accommodate new nodes)
- Update its Code to read normalized containers instead of `stdout`:
- OLD: `const dockerOutput = $input.item.json.stdout;`
- NEW: `const containers = $input.all().map(item => item.json);` then `const dockerOutput = JSON.stringify(containers);`
- The matching sub-workflow (n8n-matching.json) expects `containerList` as a JSON string of the container array, so JSON.stringify the normalized array.
- Connection chain: Query Containers for Action -> Normalize Action Containers -> Update Registry (Action) -> Prepare Action Match Input -> Execute Action Match (unchanged)
**Node 2: "Docker List for Update" (id: exec-docker-list-update)**
Current: Execute Command node running same curl command
Position: [1120, 1000]
Connected FROM: "Parse Update Command"
Connected TO: "Prepare Update Match Input"
Replace with 3 nodes (same pattern):
2a. **"Query Containers for Update"** — HTTP Request node
- Same config as 1a, position: [1120, 1000]
2b. **"Normalize Update Containers"** — Code node
- Same normalizer code, position: [1230, 1000]
2c. **"Update Registry (Update)"** — Code node
- Same registry code, position: [1340, 1000]
**Update "Prepare Update Match Input":**
- Move position to [1450, 1000]
- Change Code from `$input.item.json.stdout` to `JSON.stringify($input.all().map(item => item.json))`
- Connection chain: Query Containers for Update -> Normalize Update Containers -> Update Registry (Update) -> Prepare Update Match Input -> Execute Update Match (unchanged)
**Node 3: "Get Containers for Batch" (id: exec-docker-list-batch)**
Current: Execute Command node running same curl command
Position: [1340, -300]
Connected FROM: "Is Batch Command"
Connected TO: "Prepare Batch Match Input"
Replace with 3 nodes (same pattern):
3a. **"Query Containers for Batch"** — HTTP Request node
- Same config as 1a, position: [1340, -300]
3b. **"Normalize Batch Containers"** — Code node
- Same normalizer code, position: [1450, -300]
3c. **"Update Registry (Batch)"** — Code node
- Same registry code, position: [1560, -300]
**Update "Prepare Batch Match Input":**
- Move position to [1670, -300]
- Change Code from `$input.item.json.stdout` to `JSON.stringify($input.all().map(item => item.json))`
- Connection chain: Is Batch Command [output 0] -> Query Containers for Batch -> Normalize Batch Containers -> Update Registry (Batch) -> Prepare Batch Match Input -> Execute Batch Match (unchanged)
**Connection updates in the connections object:**
- "Parse Action Command" target changes from "Docker List for Action" to "Query Containers for Action"
- "Parse Update Command" target changes from "Docker List for Update" to "Query Containers for Update"
- "Is Batch Command" output 0 target changes from "Get Containers for Batch" to "Query Containers for Batch"
- Add new connection entries for each 3-node chain (Query -> Normalize -> Registry -> Prepare)
- Remove old connection entries for deleted nodes
**Important:** Use the same inline normalizer and registry update Code exactly as implemented in Plan 16-05 Task 1. Copy the jsCode from any of the 6 existing normalizer/registry nodes already in n8n-workflow.json (e.g., find "Normalize GraphQL Response" or "Update Container Registry" nodes). Do NOT reference utility node templates from the main workflow -- sub-workflow pattern requires inline code (per Phase 16-01 decision).
</action>
<verify>
1. Search n8n-workflow.json for "docker-socket-proxy" -- should find ONLY the 2 infra-exclusion filter references in "Check Available Updates" (line ~2776) and "Prepare Update All Batch" (line ~3093) Code nodes which use `socket-proxy` as a container name pattern, NOT as an API endpoint
2. Search for "executeCommand" node type -- should find ZERO instances (all 3 Execute Command nodes removed)
3. Search for "Query Containers for Action", "Query Containers for Update", "Query Containers for Batch" -- all 3 must exist
4. Search for "Normalize Action Containers", "Normalize Update Containers", "Normalize Batch Containers" -- all 3 must exist
5. Search for "Update Registry (Action)", "Update Registry (Update)", "Update Registry (Batch)" -- all 3 must exist
6. Verify connections: "Parse Action Command" -> "Query Containers for Action", "Parse Update Command" -> "Query Containers for Update", "Is Batch Command" [0] -> "Query Containers for Batch"
7. Push workflow to n8n and verify HTTP 200 response
</verify>
<done>
All 3 text command entry points (action, update, batch) query containers via Unraid GraphQL API using the HTTP Request -> Normalizer -> Registry Update -> Prepare Match Input chain. Zero Execute Command nodes remain. Workflow pushes successfully to n8n.
</done>
</task>
<task type="auto">
<name>Task 2: ALREADY COMPLETED — dead code and orphan removal</name>
<files>n8n-workflow.json</files>
<action>
SKIP THIS TASK — already completed in hotfix commit 216f3a4.
Removed 12 nodes: 6 dead code chains (Build/Execute/Parse Action Command × 2) and 6 orphan utility templates (GraphQL Response Normalizer, Container ID Registry, GraphQL Error Handler, Unraid API HTTP Template, Callback Token Encoder/Decoder). Node count went from 193 to 181.
The 2 remaining `socket-proxy` string references in "Check Available Updates" and "Prepare Update All Batch" are functional infrastructure exclusion filters — they will be addressed in Phase 17.
</action>
<verify>
Already verified. Node count is 181.
</verify>
<done>
Completed in prior hotfix.
</done>
</task>
</tasks>
<verification>
1. `grep -c "docker-socket-proxy" n8n-workflow.json` returns 2 (only infra-exclusion filter patterns, not API endpoints)
2. `grep -c "executeCommand" n8n-workflow.json` returns 0 (zero Execute Command nodes)
3. `grep -c "UNRAID_HOST" n8n-workflow.json` returns 12+ (9 existing GraphQL nodes + 3 new ones)
4. `grep "Query Containers for Action\|Query Containers for Update\|Query Containers for Batch" n8n-workflow.json` finds all 3 new query nodes
5. Workflow pushes to n8n successfully (HTTP 200)
6. All connection chains intact: Parse Command -> Query -> Normalize -> Registry -> Prepare Match -> Execute Match -> Route Result
7. **Connection integrity check:** All connection dictionary keys match actual node names (no node IDs as keys)
8. **Auth check:** All new HTTP Request nodes use `genericCredentialType` + `httpHeaderAuth` credential, NOT manual `x-api-key` headers
9. **Name uniqueness check:** No duplicate node names exist
</verification>
<success_criteria>
- Zero Execute Command nodes with docker-socket-proxy curl commands
- 3 new GraphQL HTTP Request + Normalizer + Registry Update chains for text command paths
- Total node count: 181 + 9 new - 3 removed = 187
- Workflow pushes to n8n successfully
- All text command paths route through GraphQL before reaching matching sub-workflow
- All new connection keys use node NAMES (not IDs)
- All new HTTP nodes use Header Auth credential (not $env.UNRAID_API_KEY)
- No duplicate node names introduced
- Phase 16 verification gaps closed: all 3 partial truths become fully verified
</success_criteria>
<output>
After completion, create `.planning/phases/16-api-migration/16-06-SUMMARY.md`
</output>