docs(14): create phase plan

This commit is contained in:
Lucas Berger
2026-02-08 20:22:11 -05:00
parent 00f0dcfd21
commit ff0773b84b
3 changed files with 424 additions and 4 deletions
@@ -0,0 +1,244 @@
---
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"
---
<objective>
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.
</objective>
<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/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
</context>
<tasks>
<task type="auto">
<name>Task 1: Create credential infrastructure</name>
<files>.env.unraid-api, .gitignore</files>
<action>
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.
</action>
<verify>
- `.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
</verify>
<done>Credential template file exists with correct variables, is gitignored, and usage is documented in CLAUDE.md</done>
</task>
<task type="auto">
<name>Task 2: Add Unraid GraphQL test nodes to main workflow</name>
<files>n8n-workflow.json</files>
<action>
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: `❌ <b>Unraid API Error</b>\n\n` +
`<b>Error:</b> ${response.error.message || JSON.stringify(response.error)}\n\n` +
`<b>Check:</b>\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: `❌ <b>Unraid GraphQL Error</b>\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: '❌ <b>Unexpected response</b>\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: `✅ <b>Unraid API Connected</b>\n\n` +
`<b>Containers:</b> ${containers.length}\n` +
`<b>Updates available:</b> ${updateCount}\n` +
`<b>ID format:</b> <code>${sampleId}</code>\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.
</action>
<verify>
- `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.
</verify>
<done>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.</done>
</task>
</tasks>
<verification>
- `.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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/14-unraid-api-access/14-01-SUMMARY.md`
</output>
@@ -0,0 +1,175 @@
---
phase: 14-unraid-api-access
plan: 02
type: execute
wave: 2
depends_on: ["14-01"]
files_modified:
- ARCHITECTURE.md
autonomous: false
user_setup: []
must_haves:
truths:
- "ARCHITECTURE.md documents the Unraid GraphQL API contract alongside existing Docker API contract"
- "Container ID format is documented based on actual test query results"
- "User has verified n8n can reach Unraid GraphQL API and receive valid container data"
- "GraphQL authentication pattern (x-api-key header) is documented"
artifacts:
- path: "ARCHITECTURE.md"
provides: "Unraid GraphQL API contract section"
contains: "GraphQL"
key_links:
- from: "ARCHITECTURE.md (GraphQL section)"
to: "n8n-workflow.json (Unraid API Test node)"
via: "Documents the API contract used by workflow nodes"
pattern: "graphql|GraphQL"
---
<objective>
Document the Unraid GraphQL API contract in ARCHITECTURE.md and verify end-to-end connectivity with the user.
Purpose: Completes Phase 14 by adding Unraid API documentation to the architecture reference (consistent with existing Docker API documentation style) and verifying that the test query actually works in the user's environment. The container ID format discovered during testing is critical for Phase 15 mutations.
Output: Updated `ARCHITECTURE.md` with GraphQL API section, verified connectivity.
</objective>
<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/ROADMAP.md
@.planning/STATE.md
@.planning/phases/14-unraid-api-access/14-CONTEXT.md
@.planning/phases/14-unraid-api-access/14-RESEARCH.md
@.planning/phases/14-unraid-api-access/14-01-SUMMARY.md
@ARCHITECTURE.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Document Unraid GraphQL API contract in ARCHITECTURE.md</name>
<files>ARCHITECTURE.md</files>
<action>
Add a new section to ARCHITECTURE.md titled "## Unraid GraphQL API" placed after the "## System Overview" section (before "## Request Flow"). This keeps all external API documentation together. Style must match the existing Docker API documentation patterns in the file.
The section should contain:
**1. API Overview**
- Endpoint: `{UNRAID_HOST}/graphql` (POST)
- Authentication: `x-api-key` header with Unraid API key
- Available in: Unraid 7.2+ (native) or 6.9-7.1 (Connect plugin)
- Purpose: Sync container update status back to Unraid after bot-initiated updates
**2. Authentication**
- Header: `x-api-key: {api_key}`
- Credential: n8n Header Auth credential named "Unraid API Key"
- Permission required: `DOCKER:UPDATE_ANY`
- Key creation: `unraid-api apikey --create --name "Docker Manager Bot" --permissions "DOCKER:UPDATE_ANY"`
**3. Network Access**
- Primary: `host.docker.internal` (requires `--add-host=host.docker.internal:host-gateway` in n8n container Extra Parameters)
- Fallback: Direct Unraid IP/hostname (e.g., `http://192.168.1.100/graphql`)
- SSL: Set "Ignore SSL Issues" for HTTPS with self-signed certs
**4. Container Query (Phase 14 — read-only)**
```graphql
query { docker { containers { id names state isUpdateAvailable } } }
```
Document expected response structure:
```json
{
"data": {
"docker": {
"containers": [
{ "id": "<PrefixedID>", "names": ["containername"], "state": "running", "isUpdateAvailable": false }
]
}
}
}
```
**5. Container ID Format**
- Type: `PrefixedID` scalar
- Format: TBD — to be updated after user runs test query in checkpoint below
- Note: "Actual format discovered during Phase 14 testing. Phase 15 mutations use this format."
**6. Update Mutation (Phase 15 — planned, not yet implemented)**
```graphql
mutation { docker { updateContainer(id: "<PrefixedID>") { id } } }
```
Mark as "Planned for Phase 15" — do not implement yet per phase boundary.
**7. Error Patterns**
- HTTP errors: Connection refused (network), 401 (bad API key), 404 (API not available)
- GraphQL errors: `response.errors[]` array with message field
- Error handling: Same pattern as Docker API errors (structured return, descriptive messages)
Per user decision: Document only queries/mutations relevant to update sync, not the full Docker schema. Keep documentation style consistent with existing Docker API contract sections.
</action>
<verify>
- ARCHITECTURE.md contains "## Unraid GraphQL API" section
- Section documents: endpoint, authentication, network access, container query, container ID format, planned mutation, error patterns
- Style is consistent with existing Docker API documentation in the file
</verify>
<done>ARCHITECTURE.md has a complete Unraid GraphQL API section documenting the contract for Phase 14 (queries) and Phase 15 (planned mutations), with container ID format marked as TBD pending test results.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 2: Verify Unraid API connectivity end-to-end</name>
<files>ARCHITECTURE.md</files>
<action>
Checkpoint: User verifies end-to-end Unraid API connectivity and reports container ID format.
What was built: Plan 14-01 created credential infrastructure (.env.unraid-api, .gitignore) and added Unraid GraphQL test nodes to the main workflow (Keyword Router "unraid" branch -> HTTP Request -> Validate Response -> Telegram Send). Plan 14-02 Task 1 documented the GraphQL API contract in ARCHITECTURE.md.
Setup steps (one-time):
1. Create Unraid API key — SSH to Unraid and run:
unraid-api apikey --create --name "Docker Manager Bot" --permissions "DOCKER:UPDATE_ANY" --description "Container update status sync" --json
Or: Unraid WebGUI -> Settings -> Management Access -> API Keys -> Create key with DOCKER:UPDATE_ANY permission.
2. Configure .env.unraid-api — Edit the file and replace template values with real UNRAID_HOST and UNRAID_API_KEY.
3. Create n8n credential — In n8n UI: Credentials -> Add Credential -> Header Auth, Name: "Unraid API Key", Header Name: x-api-key, Header Value: paste your API key.
4. Configure network access (if using host.docker.internal): Unraid WebGUI -> Docker -> n8n container -> Edit -> Extra Parameters: add --add-host=host.docker.internal:host-gateway -> Apply.
5. Update HTTP Request node credential — Open "Unraid API Test" node in n8n editor and select the "Unraid API Key" credential.
6. Send "unraid" to the Telegram bot.
After user reports container ID format, update ARCHITECTURE.md to replace the TBD container ID format with the actual format observed.
</action>
<verify>
- User confirms bot responds to "unraid" command with container list and count
- Container ID format is reported and updated in ARCHITECTURE.md
- No persistent connectivity or authentication errors
</verify>
<done>User has verified Unraid API connectivity works end-to-end, container ID format is documented in ARCHITECTURE.md, all three INFRA requirements validated.</done>
</task>
</tasks>
<verification>
- ARCHITECTURE.md contains Unraid GraphQL API documentation section
- User confirms: Telegram bot responds to "unraid" command with container list
- User confirms: Container ID format is documented
- User confirms: No connectivity/auth errors
</verification>
<success_criteria>
- ARCHITECTURE.md documents GraphQL API contract consistent with existing Docker API documentation style
- User has verified end-to-end connectivity: n8n -> Unraid GraphQL API -> container data -> Telegram response
- Container ID format is known and documented (Phase 15 can use it for mutations)
- INFRA-01 validated (connectivity), INFRA-02 validated (API key works), INFRA-03 validated (container ID format documented)
</success_criteria>
<output>
After completion, create `.planning/phases/14-unraid-api-access/14-02-SUMMARY.md`
</output>