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

14 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, gap_closure, must_haves
phase plan type wave depends_on files_modified autonomous gap_closure must_haves
16-api-migration 06 execute 1
n8n-workflow.json
true true
truths artifacts key_links
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
path provides contains
n8n-workflow.json Main workflow with all text command paths using GraphQL UNRAID_HOST
from to via pattern
n8n-workflow.json (Query Containers for Action) Unraid GraphQL API POST to $env.UNRAID_HOST/graphql UNRAID_HOST.*graphql
from to via pattern
n8n-workflow.json (Query Containers for Update) Unraid GraphQL API POST to $env.UNRAID_HOST/graphql UNRAID_HOST.*graphql
from to via pattern
n8n-workflow.json (Query Containers for Batch) Unraid GraphQL API POST to $env.UNRAID_HOST/graphql UNRAID_HOST.*graphql
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.

<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>

@.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 Task 1: Replace 3 Execute Command nodes with GraphQL query chains n8n-workflow.json 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).

  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 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.
Task 2: ALREADY COMPLETED — dead code and orphan removal n8n-workflow.json 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. Already verified. Node count is 181. Completed in prior hotfix.

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

<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>
After completion, create `.planning/phases/16-api-migration/16-06-SUMMARY.md`