654 lines
26 KiB
Markdown
654 lines
26 KiB
Markdown
# Architecture Research: Unraid GraphQL API Integration
|
|
|
|
**Domain:** n8n workflow system migration from Docker socket proxy to Unraid GraphQL API
|
|
**Researched:** 2026-02-09
|
|
**Confidence:** HIGH
|
|
|
|
## Integration Architecture Overview
|
|
|
|
### Current Architecture (Docker Socket Proxy)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ n8n Workflow System │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Main Workflow (169 nodes) │
|
|
│ ├─── Execute Workflow → Update Sub-workflow │
|
|
│ ├─── Execute Workflow → Actions Sub-workflow │
|
|
│ ├─── Execute Workflow → Logs Sub-workflow │
|
|
│ ├─── Execute Workflow → Status Sub-workflow │
|
|
│ └─── Execute Workflow → Matching Sub-workflow │
|
|
│ │
|
|
│ Sub-workflows call Docker API: │
|
|
│ HTTP Request → docker-socket-proxy:2375/v1.47/... │
|
|
│ Execute Command → curl docker-socket-proxy:2375/... │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ docker-socket-proxy (tecnativa/...) │
|
|
│ Security layer: Exposes only safe endpoints │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Docker Engine │
|
|
│ /var/run/docker.sock │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Target Architecture (Unraid GraphQL API)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ n8n Workflow System │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Main Workflow (169 nodes) │
|
|
│ ├─── Execute Workflow → Update Sub-workflow │
|
|
│ ├─── Execute Workflow → Actions Sub-workflow │
|
|
│ ├─── Execute Workflow → Logs Sub-workflow │
|
|
│ ├─── Execute Workflow → Status Sub-workflow │
|
|
│ └─── Execute Workflow → Matching Sub-workflow │
|
|
│ │
|
|
│ Sub-workflows call Unraid GraphQL API: │
|
|
│ HTTP Request → UNRAID_HOST/graphql (POST) │
|
|
│ Header Auth: x-api-key │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Unraid GraphQL API Endpoint │
|
|
│ https://192-168-x-x.hash.myunraid.net:8443/graphql │
|
|
│ Authentication: x-api-key header │
|
|
│ Permission: DOCKER:UPDATE_ANY │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Unraid Docker Service │
|
|
│ Native container management + status sync │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Key architectural change:** Single unified API replaces two-layer proxy system. Unraid GraphQL API provides both Docker operations AND native Unraid integration (update status sync).
|
|
|
|
## Component Modification Map
|
|
|
|
### Sub-workflows Requiring Modification
|
|
|
|
| Sub-workflow | Docker API Nodes | Operations | Complexity | Priority |
|
|
|--------------|------------------|------------|------------|----------|
|
|
| **n8n-update.json** | 9 nodes | Pull, stop, remove, create, start, inspect, cleanup | HIGH | 1 |
|
|
| **n8n-actions.json** | 4 nodes | List, start, stop, restart | LOW | 2 |
|
|
| **n8n-status.json** | 3 nodes | List, inspect | LOW | 3 |
|
|
| **n8n-logs.json** | 2 nodes (Execute Command) | Container logs retrieval | LOW | 4 |
|
|
| **n8n-matching.json** | 0 nodes | Pure data transformation | NONE | N/A |
|
|
| **n8n-batch-ui.json** | 5 nodes (inherited) | Calls Update/Actions | NONE | N/A |
|
|
| **n8n-confirmation.json** | 0 nodes | Pure UI logic | NONE | N/A |
|
|
|
|
**Total impact:** 4 sub-workflows modified (18 Docker API nodes), 3 sub-workflows unchanged
|
|
|
|
### Sub-workflows NOT Requiring Modification
|
|
|
|
- **n8n-matching.json** — Container name matching logic operates on data shape, not API source
|
|
- **n8n-batch-ui.json** — Pure Telegram keyboard UI, no direct Docker API calls
|
|
- **n8n-confirmation.json** — Confirmation dialogs, no Docker operations
|
|
|
|
These workflows receive transformed data from modified workflows and operate on contracts (input/output shapes) that remain stable.
|
|
|
|
## Node Type Changes
|
|
|
|
### HTTP Request Node Pattern (Actions, Status)
|
|
|
|
**Before (Docker API):**
|
|
```javascript
|
|
// n8n HTTP Request node
|
|
{
|
|
method: 'POST',
|
|
url: 'http://docker-socket-proxy:2375/v1.47/containers/{{ $json.containerId }}/start',
|
|
responseFormat: 'json'
|
|
}
|
|
```
|
|
|
|
**After (Unraid GraphQL API):**
|
|
```javascript
|
|
// n8n HTTP Request node
|
|
{
|
|
method: 'POST',
|
|
url: '={{ $env.UNRAID_HOST }}/graphql',
|
|
authentication: 'genericCredentialType',
|
|
genericAuthType: 'httpHeaderAuth',
|
|
credential: 'Unraid API Key', // Header Auth: x-api-key
|
|
sendBody: true,
|
|
bodyContentType: 'json',
|
|
body: {
|
|
query: 'mutation { docker { start(id: "{{ $json.unraidContainerId }}") { id state } } }'
|
|
},
|
|
options: {
|
|
ignoreSSLIssues: true
|
|
}
|
|
}
|
|
```
|
|
|
|
**Node type:** Same (HTTP Request), but parameters change
|
|
**Count:** ~15 HTTP Request nodes across Actions, Status, Update workflows
|
|
|
|
### Execute Command → HTTP Request (Logs, Update)
|
|
|
|
**Before (Execute Command with curl):**
|
|
```javascript
|
|
// n8n Execute Command node (Pull Image)
|
|
{
|
|
command: '=curl -s --max-time 600 -X POST \'http://docker-socket-proxy:2375/v1.47/images/create?fromImage={{ encodeURIComponent($json.imageName) }}\' | tail -c 10000'
|
|
}
|
|
```
|
|
|
|
**After (HTTP Request with GraphQL mutation):**
|
|
```javascript
|
|
// n8n HTTP Request node
|
|
{
|
|
method: 'POST',
|
|
url: '={{ $env.UNRAID_HOST }}/graphql',
|
|
authentication: 'genericCredentialType',
|
|
genericAuthType: 'httpHeaderAuth',
|
|
credential: 'Unraid API Key',
|
|
sendBody: true,
|
|
bodyContentType: 'json',
|
|
body: {
|
|
query: 'mutation { docker { updateContainer(id: "{{ $json.unraidContainerId }}") { id imageId state } } }'
|
|
},
|
|
timeout: 600000 // 10 minutes
|
|
}
|
|
```
|
|
|
|
**Node type change:** Execute Command → HTTP Request
|
|
**Count:** 3 Execute Command nodes (1 in Update for image pull, 2 in Logs for log retrieval)
|
|
**Impact:** Simpler — no shell escaping, native n8n timeout handling, structured GraphQL errors
|
|
|
|
## Data Transformation Requirements
|
|
|
|
### Container ID Mapping
|
|
|
|
**Docker API format:**
|
|
- Container ID: 64-character hex string (`8a9907a245766012...`)
|
|
- Container names: Array with `/` prefix (`["/n8n"]`)
|
|
|
|
**Unraid GraphQL format:**
|
|
- Container ID: `PrefixedID` scalar — `{server_hash}:{container_hash}` (128 chars + colon)
|
|
- Example: `1639d2f04f44841b...a558d85071fa23e0:8a9907a245766012...840cefdec67af6b7`
|
|
- Container names: Array WITH `/` prefix (`["/n8n"]`) — GraphQL returns Docker's raw value
|
|
|
|
**Mapping strategy:**
|
|
|
|
1. **Container matching (Matching sub-workflow):**
|
|
- User input: "n8n" (plain name)
|
|
- Docker API query: Returns names `["/n8n"]` → strip `/` for comparison
|
|
- Unraid GraphQL query: Returns names `["/n8n"]` → strip `/` for comparison
|
|
- **No change needed** — name matching logic already strips `/` prefix
|
|
|
|
2. **Container identification:**
|
|
- Docker API: Use Docker container ID (64 chars)
|
|
- Unraid GraphQL: Use Unraid PrefixedID (128+ chars)
|
|
- **Strategy:** Store Unraid ID from initial query, pass through workflow
|
|
- **Where:** Matching sub-workflow output, Update/Actions/Status inputs
|
|
|
|
3. **Workflow contract stability:**
|
|
- Field name stays `containerId` throughout all workflows
|
|
- Value changes from Docker ID → Unraid PrefixedID
|
|
- No contract changes needed — ID is opaque token to workflows
|
|
|
|
### Response Shape Transformation
|
|
|
|
**Docker API response (GET /containers/json):**
|
|
```json
|
|
[
|
|
{
|
|
"Id": "8a9907a245766012741662a5840cefdec67af6b70e4c6f1629af7ef8f1ee2925",
|
|
"Names": ["/n8n"],
|
|
"Image": "n8nio/n8n:latest",
|
|
"ImageID": "sha256:abc123...",
|
|
"State": "running",
|
|
"Status": "Up 2 hours"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Unraid GraphQL response:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"docker": {
|
|
"containers": [
|
|
{
|
|
"id": "1639d2f04f44841b...a558d85071fa23e0:8a9907a245766012...840cefdec67af6b7",
|
|
"names": ["/n8n"],
|
|
"image": "n8nio/n8n:latest",
|
|
"imageId": "sha256:abc123...",
|
|
"state": "RUNNING",
|
|
"status": "Up 2 hours",
|
|
"isUpdateAvailable": false
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key differences:**
|
|
|
|
| Field | Docker API | Unraid GraphQL | Impact |
|
|
|-------|------------|----------------|--------|
|
|
| Container ID | `Id` | `id` | Lowercase (minor) |
|
|
| ID format | 64 hex chars | PrefixedID (128+) | Length check updates |
|
|
| Names | `Names` | `names` | Lowercase (minor) |
|
|
| Image ID | `ImageID` | `imageId` | Lowercase (minor) |
|
|
| State | `"running"` (lowercase) | `"RUNNING"` (uppercase) | Case handling in comparisons |
|
|
| Status | `Status` | `status` | Lowercase (minor) |
|
|
| Update available | Not available | `isUpdateAvailable` | NEW field (benefit!) |
|
|
|
|
**Transformation pattern:**
|
|
|
|
Add a **"Normalize Container Data"** Code node after Unraid GraphQL queries to map to workflow's expected shape:
|
|
|
|
```javascript
|
|
// Code node: Normalize Unraid GraphQL Response
|
|
const graphqlResponse = $input.item.json;
|
|
|
|
// Check for GraphQL errors first
|
|
if (graphqlResponse.errors) {
|
|
throw new Error(`GraphQL error: ${graphqlResponse.errors.map(e => e.message).join(', ')}`);
|
|
}
|
|
|
|
// Extract containers from GraphQL response structure
|
|
const containers = graphqlResponse.data?.docker?.containers || [];
|
|
|
|
// Map to workflow's expected format
|
|
return containers.map(c => ({
|
|
json: {
|
|
containerId: c.id, // Unraid PrefixedID
|
|
containerName: (c.names?.[0] || '').replace(/^\//, ''), // Strip leading /
|
|
imageName: c.image,
|
|
imageId: c.imageId,
|
|
state: c.state.toLowerCase(), // RUNNING → running (for existing comparisons)
|
|
status: c.status,
|
|
updateAvailable: c.isUpdateAvailable || false // NEW
|
|
}
|
|
}));
|
|
```
|
|
|
|
**Where to add:** After every `docker.containers` GraphQL query in:
|
|
- Status sub-workflow (container list query)
|
|
- Matching sub-workflow (container list for matching)
|
|
- Actions sub-workflow (container list before action)
|
|
|
|
## Integration Patterns
|
|
|
|
### Pattern 1: Direct Mutation (Simple Operations)
|
|
|
|
**Use for:** Start, stop, restart, pause (single-step operations)
|
|
|
|
**Before (Docker API):**
|
|
```javascript
|
|
// HTTP Request node: POST /containers/{id}/start
|
|
// Response: HTTP 204 No Content (empty body)
|
|
// Success check: response.statusCode === 204
|
|
```
|
|
|
|
**After (Unraid GraphQL):**
|
|
```javascript
|
|
// HTTP Request node: POST /graphql
|
|
// Body: { query: 'mutation { docker { start(id: "...") { id state } } }' }
|
|
// Response: { data: { docker: { start: { id: "...", state: "RUNNING" } } } }
|
|
// Success check: response.data?.docker?.start?.state === "RUNNING"
|
|
```
|
|
|
|
**Error handling:**
|
|
- Docker API: HTTP status codes (404 = not found, 304 = already running)
|
|
- Unraid GraphQL: `response.errors[]` array with structured error messages
|
|
- **New pattern:** Check `response.errors` first, then validate `response.data` structure
|
|
|
|
**Example implementation (Actions sub-workflow):**
|
|
|
|
```javascript
|
|
// Code node: Validate Start Result
|
|
const response = $input.item.json;
|
|
|
|
// Check GraphQL errors
|
|
if (response.errors) {
|
|
const errorMsg = response.errors.map(e => e.message).join(', ');
|
|
return {
|
|
json: {
|
|
success: false,
|
|
message: `Start failed: ${errorMsg}`,
|
|
action: 'start'
|
|
}
|
|
};
|
|
}
|
|
|
|
// Validate response structure
|
|
const startResult = response.data?.docker?.start;
|
|
if (!startResult) {
|
|
return {
|
|
json: {
|
|
success: false,
|
|
message: 'Invalid GraphQL response: missing start result',
|
|
action: 'start'
|
|
}
|
|
};
|
|
}
|
|
|
|
// Success
|
|
return {
|
|
json: {
|
|
success: true,
|
|
action: 'start',
|
|
containerId: startResult.id,
|
|
state: startResult.state,
|
|
message: 'Container started successfully'
|
|
}
|
|
};
|
|
```
|
|
|
|
### Pattern 2: Complex Multi-Step Operations (Update Workflow)
|
|
|
|
**Docker API approach (9 nodes):**
|
|
1. Get container list → Find container
|
|
2. Inspect container → Extract config
|
|
3. Pull image (Execute Command with curl)
|
|
4. Inspect new image → Get digest
|
|
5. Stop container
|
|
6. Remove container
|
|
7. Create container (with old config)
|
|
8. Start container
|
|
9. Remove old image
|
|
|
|
**Unraid GraphQL approach (1-2 nodes):**
|
|
1. Call `updateContainer` mutation → Unraid handles all steps atomically
|
|
2. (Optional) Query container status after update
|
|
|
|
**Simplification:**
|
|
|
|
```javascript
|
|
// Single HTTP Request node: Update Container
|
|
{
|
|
method: 'POST',
|
|
url: '={{ $env.UNRAID_HOST }}/graphql',
|
|
authentication: 'genericCredentialType',
|
|
genericAuthType: 'httpHeaderAuth',
|
|
credential: 'Unraid API Key',
|
|
sendBody: true,
|
|
bodyContentType: 'json',
|
|
body: {
|
|
query: 'mutation { docker { updateContainer(id: "{{ $json.containerId }}") { id imageId state isUpdateAvailable } } }'
|
|
},
|
|
timeout: 600000 // 10 minutes (pull can be slow)
|
|
}
|
|
|
|
// Code node: Validate Update Result
|
|
const response = $input.item.json;
|
|
|
|
if (response.errors) {
|
|
const errorMsg = response.errors.map(e => e.message).join(', ');
|
|
return {
|
|
json: {
|
|
success: false,
|
|
updated: false,
|
|
message: `Update failed: ${errorMsg}`
|
|
}
|
|
};
|
|
}
|
|
|
|
const updateResult = response.data?.docker?.updateContainer;
|
|
if (!updateResult) {
|
|
return {
|
|
json: {
|
|
success: false,
|
|
updated: false,
|
|
message: 'Invalid GraphQL response'
|
|
}
|
|
};
|
|
}
|
|
|
|
// Check if update was needed
|
|
if (updateResult.isUpdateAvailable === false) {
|
|
return {
|
|
json: {
|
|
success: true,
|
|
updated: false,
|
|
message: 'Container is already up to date'
|
|
}
|
|
};
|
|
}
|
|
|
|
// Success
|
|
return {
|
|
json: {
|
|
success: true,
|
|
updated: true,
|
|
message: `Container updated successfully`,
|
|
newImageId: updateResult.imageId,
|
|
state: updateResult.state
|
|
}
|
|
};
|
|
```
|
|
|
|
**Impact:** Update sub-workflow shrinks from 34 nodes to ~15-20 nodes (remove 9 Docker API nodes, add 1-2 GraphQL nodes + data normalization)
|
|
|
|
**Benefit:** Unraid's `updateContainer` handles image pull, container stop/remove/create/start atomically AND syncs update status internally (solves v1.3's "apply update" badge issue automatically).
|
|
|
|
### Pattern 3: Log Retrieval (Logs Sub-workflow)
|
|
|
|
**Before (Execute Command):**
|
|
```javascript
|
|
// Execute Command node: Get Logs
|
|
{
|
|
command: '=curl -s "http://docker-socket-proxy:2375/v1.47/containers/{{ $json.containerId }}/logs?stdout=true&stderr=true&tail={{ $json.lineCount || 50 }}"'
|
|
}
|
|
```
|
|
|
|
**After (Unraid GraphQL):**
|
|
```javascript
|
|
// HTTP Request node: Query Logs
|
|
{
|
|
method: 'POST',
|
|
url: '={{ $env.UNRAID_HOST }}/graphql',
|
|
authentication: 'genericCredentialType',
|
|
genericAuthType: 'httpHeaderAuth',
|
|
credential: 'Unraid API Key',
|
|
sendBody: true,
|
|
bodyContentType: 'json',
|
|
body: {
|
|
query: 'query { docker { logs(id: "{{ $json.containerId }}", tail: {{ $json.lineCount || 50 }}) { entries } } }'
|
|
}
|
|
}
|
|
|
|
// Code node: Format Logs
|
|
const response = $input.item.json;
|
|
|
|
if (response.errors) {
|
|
throw new Error(`Log retrieval failed: ${response.errors.map(e => e.message).join(', ')}`);
|
|
}
|
|
|
|
const logData = response.data?.docker?.logs;
|
|
if (!logData) {
|
|
throw new Error('Invalid GraphQL response: missing logs');
|
|
}
|
|
|
|
const logs = logData.entries || [];
|
|
|
|
return {
|
|
json: {
|
|
success: true,
|
|
logs: logs.join('\n'),
|
|
lineCount: logs.length
|
|
}
|
|
};
|
|
```
|
|
|
|
**Benefit:** Native GraphQL logs query returns structured data (array of log entries) instead of raw Docker log format with control characters. Simpler parsing.
|
|
|
|
## Modified vs. New Components
|
|
|
|
### Modified Components
|
|
|
|
| Component | File | Modification Type | Node Count Change | Reason |
|
|
|-----------|------|-------------------|-------------------|--------|
|
|
| Container List Query | n8n-status.json | Replace HTTP Request URL + add normalization node | 3 → 4 nodes | GraphQL endpoint + response shape |
|
|
| Container Actions | n8n-actions.json | Replace HTTP Request URLs + update mutation body | 4 → 5 nodes | GraphQL mutations + error handling |
|
|
| Container Update | n8n-update.json | Replace 9 Docker API nodes with 2 GraphQL nodes | 34 → 27 nodes | Unraid atomic update |
|
|
| Container Logs | n8n-logs.json | Replace Execute Command with HTTP Request | 2 → 3 nodes | Native logs query + formatting |
|
|
| Container Matching | n8n-matching.json | Add normalization node after list query | 23 → 24 nodes | Data shape consistency |
|
|
|
|
**Total node count change:** 290 nodes → ~284 nodes (net reduction of ~6 nodes)
|
|
|
|
### New Components
|
|
|
|
| Component | Purpose | Where Added | Why New |
|
|
|-----------|---------|-------------|---------|
|
|
| Normalize Container Data | Map GraphQL response to workflow contract | All modified sub-workflows | Data shape consistency |
|
|
| GraphQL Error Handler | Validate `response.errors` array | Inline in validation Code nodes | GraphQL-specific error handling |
|
|
| Container ID Resolver | Map container name → Unraid PrefixedID | n8n-matching.json | ID format difference |
|
|
|
|
**Pattern:** Each modified sub-workflow gains 1-2 new Code nodes for normalization/validation.
|
|
|
|
## Suggested Build Order
|
|
|
|
### Phase 1: Infrastructure (Low Risk) — Already Complete
|
|
|
|
1. **Credential setup** — Completed in Phase 14
|
|
- `.env.unraid-api` file
|
|
- n8n Header Auth credential
|
|
- Test query validated
|
|
- Network connectivity proven (myunraid.net relay)
|
|
|
|
2. **Data normalization library** — Create reusable template
|
|
- Build "Normalize Unraid Response" Code node
|
|
- Test with Phase 14's test query
|
|
- Template ready for copy-paste into sub-workflows
|
|
|
|
### Phase 2: Simple Sub-workflows (Low Complexity)
|
|
|
|
**Order:** Actions → Status → Logs → Matching
|
|
|
|
3. **Actions sub-workflow** (4 Docker API nodes → 5 GraphQL nodes)
|
|
- Start/stop/restart mutations
|
|
- Direct 1:1 mapping
|
|
- Add normalization + error handling
|
|
- Test: `start <container>`, `stop <container>`, `restart <container>`
|
|
|
|
4. **Status sub-workflow** (3 Docker API nodes → 4 GraphQL nodes)
|
|
- Container list query
|
|
- Single container query
|
|
- Add normalization
|
|
- Test: `status`, tap container in list
|
|
|
|
5. **Logs sub-workflow** (2 Execute Command → 3 HTTP Request)
|
|
- Logs query with tail parameter
|
|
- Add formatting Code node
|
|
- Test: `logs <container>`
|
|
|
|
6. **Matching sub-workflow** (0 API nodes → 1 normalization node)
|
|
- Container list source changes (inherited from Status)
|
|
- Add normalization if not already present
|
|
- Test: All name matching patterns
|
|
|
|
### Phase 3: Complex Sub-workflow (High Impact)
|
|
|
|
7. **Update sub-workflow** (9 Docker API nodes → 2 GraphQL nodes)
|
|
- Replace multi-step Docker API flow with single `updateContainer` mutation
|
|
- Unraid handles atomic update AND status sync
|
|
- Validate success/failure cases
|
|
- Test: `update <container>`, verify image pull + recreate + status cleared
|
|
|
|
### Phase 4: Dependency Cleanup (Post-Integration)
|
|
|
|
8. **Remove docker-socket-proxy dependency**
|
|
- Update Docker Compose / Unraid template
|
|
- Remove container from deployment
|
|
- Remove volume mount for Docker socket
|
|
- Update architecture docs
|
|
|
|
### Phase 5: Validation
|
|
|
|
9. **Full workflow testing**
|
|
- All text commands (status, start, stop, restart, update, logs)
|
|
- All inline keyboard flows (tap container → action)
|
|
- Batch operations (stop multiple, update all)
|
|
- Error cases (invalid container, network failure, API errors)
|
|
- Verify Unraid update badges clear automatically after bot updates
|
|
|
|
**Rationale:** Start simple (Actions = 4 nodes, low risk), build confidence, tackle complex Update workflow last when patterns are proven.
|
|
|
|
## What NOT to Change
|
|
|
|
### Stable Components (DO NOT MODIFY)
|
|
|
|
1. **Main workflow structure** — Keyword Router, Auth IF, Correlation ID generator
|
|
- Reason: No Docker API calls, pure orchestration logic
|
|
|
|
2. **Sub-workflow contracts** — Input/output shapes remain the same
|
|
- Example: Update sub-workflow still receives `{ containerId, containerName, chatId, messageId, responseMode }`
|
|
- Reason: Preserves integration points with main workflow
|
|
- Note: `containerId` value changes (Docker ID → Unraid PrefixedID) but field name/type stable
|
|
|
|
3. **Telegram response nodes** — All "Send Message" and "Edit Message" nodes
|
|
- Reason: User-facing messages, no Docker/Unraid API dependency
|
|
|
|
4. **Matching logic algorithm** — Container name matching strategy
|
|
- Reason: Operates on container names array, agnostic to API source
|
|
- Note: Data SOURCE changes, algorithm does not
|
|
|
|
5. **Batch UI logic** — Selection keyboard, bitmap encoding
|
|
- Reason: Pure UI state management, no Docker operations
|
|
|
|
6. **Confirmation dialogs** — Stop/update confirmation flows
|
|
- Reason: Pure UI logic, orchestration only
|
|
|
|
7. **Error logging patterns** — Structured error returns, correlation IDs
|
|
- Reason: API-agnostic observability infrastructure
|
|
|
|
### Anti-Patterns to Avoid
|
|
|
|
- **Do NOT change sub-workflow contracts** — Main workflow dispatch nodes depend on stable input shapes
|
|
- **Do NOT mix Docker API and Unraid GraphQL** in same workflow — Pick one, commit fully, test, ship
|
|
- **Do NOT remove correlation IDs** — Observability is critical for debugging GraphQL errors
|
|
- **Do NOT change Telegram message formats** — User-facing stability matters
|
|
- **Do NOT optimize prematurely** — Get it working with Unraid GraphQL first, optimize later
|
|
- **Do NOT skip data normalization** — Consistent data shapes prevent cascading failures
|
|
|
|
## Risk Assessment
|
|
|
|
| Component | Risk Level | Mitigation |
|
|
|-----------|------------|------------|
|
|
| Actions sub-workflow | LOW | Simple 1:1 mutation mapping, test early |
|
|
| Status sub-workflow | LOW | Query-only, add normalization layer |
|
|
| Logs sub-workflow | LOW | GraphQL logs query is simpler than Docker API |
|
|
| Update sub-workflow | MEDIUM | Complex flow → atomic mutation (big simplification but needs thorough testing) |
|
|
| Container ID mapping | MEDIUM | ID format difference requires careful validation |
|
|
| Matching sub-workflow | LOW | No API changes, only data source changes |
|
|
| Main workflow | NONE | No modifications needed |
|
|
| docker-socket-proxy removal | LOW | Remove after all sub-workflows migrated, test thoroughly |
|
|
|
|
**Overall risk:** LOW-MEDIUM — Most changes are direct API replacements with simpler GraphQL equivalents. Update workflow is the only complex migration but Unraid's atomic operation actually simplifies the flow.
|
|
|
|
## Key Benefits of Migration
|
|
|
|
1. **Simplified Update Flow** — 9 Docker API nodes → 2 GraphQL nodes (atomic operation)
|
|
2. **Automatic Status Sync** — Unraid update badges clear automatically (solves v1.3's manual sync issue)
|
|
3. **Better Error Messages** — Structured GraphQL errors vs. HTTP status codes
|
|
4. **Native Integration** — Direct Unraid API vs. Docker proxy layer
|
|
5. **Update Detection** — `isUpdateAvailable` field enables proactive notifications
|
|
6. **Simpler Log Parsing** — Structured entries vs. raw Docker log format with control characters
|
|
7. **Security Improvement** — API key with granular permissions vs. Docker socket proxy
|
|
8. **Fewer Dependencies** — Remove docker-socket-proxy container from deployment
|
|
|
|
## Sources
|
|
|
|
- [Unraid GraphQL Schema](https://raw.githubusercontent.com/unraid/api/main/api/generated-schema.graphql) — Complete API specification (HIGH confidence)
|
|
- `.planning/phases/14-unraid-api-access/14-RESEARCH.md` — Phase 14 connectivity research (HIGH confidence)
|
|
- `ARCHITECTURE.md` — Existing workflow architecture, contracts (HIGH confidence)
|
|
- `n8n-workflow.json`, `n8n-*.json` — Actual workflow implementations (HIGH confidence)
|
|
- [n8n GraphQL Node Documentation](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.graphql/) — HTTP Request patterns (HIGH confidence)
|
|
- [Docker API Documentation](https://docs.docker.com/engine/api/v1.47/) — Current Docker API patterns (HIGH confidence)
|
|
|
|
---
|
|
*Architecture research for: Unraid Docker Manager v1.4 — Unraid API Native*
|
|
*Researched: 2026-02-09*
|
|
*Confidence: HIGH — Unraid GraphQL schema verified, Phase 14 connectivity proven, workflow structure analyzed*
|