903e73d616
Unraid GraphQL API foundation — connectivity, authentication, and
container ID format verified for native Unraid API integration.
Phase 14: Unraid API Access (2 plans, 4 tasks)
- Established Unraid GraphQL API connectivity via myunraid.net cloud relay
- Dual credential storage (.env.unraid-api + n8n env vars)
- Container ID format: {server_hash}:{container_hash} (128-char SHA256 pair)
- Complete API contract documented in ARCHITECTURE.md
- "unraid" test command added to Telegram bot
Phases 15-16 dropped (superseded by v1.4 Unraid API Native).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
540 lines
22 KiB
Markdown
540 lines
22 KiB
Markdown
# Architecture
|
|
|
|
Technical reference for the Unraid Docker Manager workflow system.
|
|
|
|
## System Overview
|
|
|
|
The bot is an n8n workflow that receives Telegram messages, routes them through authentication and keyword matching, dispatches to domain-specific sub-workflows, and sends responses back to the user.
|
|
|
|
```
|
|
Telegram
|
|
|
|
|
v
|
|
n8n-workflow.json (166 nodes)
|
|
|-- Auth check (Telegram user ID)
|
|
|-- Correlation ID generation
|
|
|-- Text path: Keyword Router -> Parse Command -> Match Container -> Execute
|
|
|-- Callback path: Parse Callback Data -> Route -> Execute
|
|
|
|
|
|-- n8n-update.json (34 nodes) Container image pull + recreate
|
|
|-- n8n-actions.json (11 nodes) Start / stop / restart
|
|
|-- n8n-logs.json (9 nodes) Log retrieval + formatting
|
|
|-- n8n-batch-ui.json (17 nodes) Batch selection keyboard UI
|
|
|-- n8n-status.json (11 nodes) Container list + status display
|
|
|-- n8n-confirmation.json (16 nodes) Confirmation dialogs
|
|
|-- n8n-matching.json (23 nodes) Container name matching
|
|
|
|
|
v
|
|
docker-socket-proxy (tecnativa/docker-socket-proxy)
|
|
|
|
|
v
|
|
Docker Engine
|
|
```
|
|
|
|
**Total:** 287 nodes (166 main + 121 across 7 sub-workflows)
|
|
|
|
## Unraid GraphQL API
|
|
|
|
The Unraid GraphQL API is used to sync container update status back to Unraid after bot-initiated updates, resolving the "apply update" badge issue (see Known Limitations).
|
|
|
|
### 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
|
|
|
|
### Authentication
|
|
|
|
- **Header:** `x-api-key: {api_key}`
|
|
- **Credential:** n8n Header Auth credential named "Unraid API Key" - Header Name: `x-api-key`
|
|
- Header Value: Unraid API key
|
|
- **Permission required:** `DOCKER:UPDATE_ANY`
|
|
- **Key creation:** `unraid-api apikey --create --name "Docker Manager Bot" --permissions "DOCKER:UPDATE_ANY"`
|
|
|
|
Alternative: Unraid WebGUI -> Settings -> Management Access -> API Keys -> Create key with DOCKER:UPDATE_ANY permission.
|
|
|
|
### Network Access
|
|
|
|
**Critical findings:**
|
|
- Direct LAN IP (http://192.168.90.103) does NOT work — nginx redirects HTTP to HTTPS, stripping auth headers on redirect
|
|
- Direct HTTPS with standard port (https://192.168.90.103:8443) does NOT have /graphql endpoint
|
|
- **Working approach:** Use Unraid's myunraid.net cloud relay URL
|
|
|
|
**Working configuration:**
|
|
- **URL format:** `https://{ip-dashed}.{hash}.myunraid.net:8443/graphql`
|
|
- Example: `https://192-168-90-103.abc123def456.myunraid.net:8443/graphql`
|
|
- **Environment variable:** Set `UNRAID_HOST` on n8n container (without /graphql suffix)
|
|
- **SSL:** Valid certs via myunraid.net — no SSL ignore needed
|
|
- **Authentication:** n8n Header Auth credential
|
|
**Why this works:**
|
|
- myunraid.net cloud relay properly handles GraphQL endpoint routing
|
|
- Auth headers preserved through the cloud relay
|
|
- No nginx redirect issues
|
|
- GraphQL sandbox mode NOT required for API access
|
|
|
|
**Note:** The `host.docker.internal` and direct IP approaches documented in Phase 14 Plan 01 research did not work in production testing. The myunraid.net relay is the verified working solution for Unraid 7.2.
|
|
|
|
### Container Query (Phase 14 — read-only)
|
|
|
|
```graphql
|
|
query { docker { containers { id names state } } }
|
|
```
|
|
|
|
**Expected response structure:**
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"docker": {
|
|
"containers": [
|
|
{
|
|
"id": "1639d2f04f44841bc62fec38d18e1869a558d85071fa23e0a8bf64d374b317fa:8a9907a245766012741662a5840cefdec67af6b70e4c6f1629af7ef8f1ee2925",
|
|
"names": ["/n8n"],
|
|
"state": "RUNNING"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Important field behaviors:**
|
|
- `state` values are **UPPERCASE** (`RUNNING`, not `running`)
|
|
- `names` array entries are **prefixed with `/`** (e.g., `/n8n` not `n8n`)
|
|
- `isUpdateAvailable` field does **NOT exist** in Unraid 7.2 schema (research was incorrect)
|
|
|
|
**Schema differences from research:**
|
|
- Phase 14 research incorrectly documented `isUpdateAvailable` field
|
|
- Actual schema introspection shows this field is not present in DockerContainer type
|
|
- Phase 15 will need to determine correct field for update status tracking
|
|
|
|
### Container ID Format
|
|
|
|
- **Type:** `PrefixedID` scalar
|
|
- **Format:** `{server_hash}:{container_hash}` — two 64-character SHA256 hex strings joined by colon
|
|
- **Example:** `1639d2f04f44841bc62fec38d18e1869a558d85071fa23e0a8bf64d374b317fa:8a9907a245766012741662a5840cefdec67af6b70e4c6f1629af7ef8f1ee2925`
|
|
- **Components:**
|
|
- First part (server hash): Same for all containers on a given Unraid server
|
|
- Second part (container hash): Unique per container
|
|
- **Note:** Discovered during Phase 14 verification testing. Phase 15 mutations use this format.
|
|
|
|
### n8n Container Configuration
|
|
|
|
**Required environment variable:**
|
|
- `UNRAID_HOST` — Unraid myunraid.net URL (without /graphql suffix)
|
|
- Example: `https://192-168-90-103.abc123def456.myunraid.net:8443`
|
|
- Set via Unraid WebGUI -> Docker -> n8n container -> Edit -> Add Variable
|
|
|
|
**Required n8n credential:**
|
|
- **Type:** Header Auth
|
|
- **Name:** "Unraid API Key"
|
|
- **Header Name:** `x-api-key`
|
|
- **Header Value:** Unraid API key (from `unraid-api apikey --create`)
|
|
|
|
**HTTP Request node configuration:**
|
|
- **Authentication:** `genericCredentialType` with `httpHeaderAuth`
|
|
- **Credential:** "Unraid API Key"- **URL:** `{{ $env.UNRAID_HOST }}/graphql`
|
|
|
|
### Update Mutation (Phase 15 — planned, not yet implemented)
|
|
|
|
```graphql
|
|
mutation { docker { updateContainer(id: "<PrefixedID>") { id } } }
|
|
```
|
|
|
|
**Status:** Planned for Phase 15 — not yet implemented per phase boundary.
|
|
**Note:** Update tracking field needs discovery via schema introspection (isUpdateAvailable does not exist).
|
|
|
|
### 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)
|
|
|
|
## Workflow Files
|
|
|
|
| File | n8n ID | Purpose | Nodes |
|
|
|------|--------|---------|-------|
|
|
| n8n-workflow.json | `HmiXBlJefBRPMS0m4iNYc` | Main orchestrator | 166 |
|
|
| n8n-update.json | `7AvTzLtKXM2hZTio92_mC` | Container update (pull, recreate, cleanup) | 34 |
|
|
| n8n-actions.json | `fYSZS5PkH0VSEaT5` | Container start/stop/restart | 11 |
|
|
| n8n-logs.json | `oE7aO2GhbksXDEIw` | Container log retrieval | 9 |
|
|
| n8n-batch-ui.json | `ZJhnGzJT26UUmW45` | Batch selection keyboard | 17 |
|
|
| n8n-status.json | `lqpg2CqesnKE2RJQ` | Container list and status | 11 |
|
|
| n8n-confirmation.json | `fZ1hu8eiovkCk08G` | Confirmation dialogs | 16 |
|
|
| n8n-matching.json | `kL4BoI8ITSP9Oxek` | Container name matching | 23 |
|
|
|
|
## Request Flow
|
|
|
|
### Text Commands
|
|
|
|
1. Telegram Trigger receives message
|
|
2. Auth IF node checks user ID
|
|
3. Correlation ID generator creates a unique request trace ID (`timestamp-random`)
|
|
4. Keyword Router (Switch node) matches command keyword
|
|
5. Parse Command (Code node) extracts parameters
|
|
6. Matching sub-workflow resolves container name to Docker ID
|
|
7. Domain sub-workflow executes the operation
|
|
8. Result routed to Telegram response node
|
|
|
|
### Callback (Inline Keyboard)
|
|
|
|
1. Telegram Trigger receives callback query
|
|
2. Auth IF node checks user ID
|
|
3. Correlation ID generator creates a unique request trace ID
|
|
4. Parse Callback Data (Code node, 441 lines) decodes callback string
|
|
5. Route Callback (Switch node) dispatches by prefix
|
|
6. Domain sub-workflow executes the operation
|
|
7. Result routed to Telegram response node (editMessageText)
|
|
|
|
## Observability
|
|
|
|
### Correlation IDs
|
|
|
|
Every request gets a unique correlation ID generated at the entry point of the main workflow. This ID flows through all sub-workflow calls via Prepare Input nodes, enabling request tracing across workflow boundaries in the n8n execution log.
|
|
|
|
**How it works:**
|
|
- Two generator nodes in the main workflow: one for the text path, one for the callback path
|
|
- Format: `timestamp-randomString` (no external dependencies)
|
|
- All 19 Prepare Input nodes include `correlationId: $json.correlationId` in their output
|
|
- Sub-workflows receive the ID as an input field and can reference it in their logs
|
|
|
|
**Where to find it:** Open any execution in the n8n UI, inspect the output of a Prepare Input node — the `correlationId` field traces back to the original user request.
|
|
|
|
**Limitations:** Correlation IDs are only visible in the n8n execution log. There is no persistent storage or user-facing output. n8n's workflow static data is execution-scoped (not workflow-scoped), so ring buffers and cross-execution logging are not possible on this platform.
|
|
|
|
### Structured Error Returns
|
|
|
|
All 7 sub-workflows return structured error objects on failure:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "Container not found: foo",
|
|
"correlationId": "1707400000-abc123"
|
|
}
|
|
```
|
|
|
|
This provides a consistent error shape for the main workflow to route and format error messages to the user.
|
|
|
|
### Debugging a Request
|
|
|
|
1. Open the n8n execution list for the main workflow
|
|
2. Find the execution by timestamp or Telegram message content
|
|
3. Check the Prepare Input node output for the `correlationId`
|
|
4. Search sub-workflow executions for the same `correlationId`
|
|
5. Trace the full request path: main workflow -> sub-workflow -> Docker API -> response
|
|
|
|
## Sub-workflow Contracts
|
|
|
|
Each sub-workflow has a defined input/output contract. The main workflow communicates with sub-workflows through Prepare Input (Code) nodes that build the input object, and Route Result (Code) nodes that interpret the response.
|
|
|
|
### n8n-update.json (Container Update)
|
|
|
|
**Input:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| containerId | string | yes* | Docker container ID (empty string to resolve by name) |
|
|
| containerName | string | yes | Container display name |
|
|
| chatId | number | yes | Telegram chat ID |
|
|
| messageId | number | yes | Telegram message ID (0 for text mode) |
|
|
| responseMode | string | yes | `"text"`, `"inline"`, or `"batch"` |
|
|
| correlationId | string | no | Request trace ID |
|
|
|
|
*containerId can be empty — the sub-workflow resolves by name via its Resolve Container ID node.
|
|
|
|
**Output:**
|
|
|
|
| Outcome | Fields |
|
|
|---------|--------|
|
|
| Updated | `success: true, updated: true, message, oldDigest, newDigest` |
|
|
| No update needed | `success: true, updated: false, message` |
|
|
| Error | `success: false, updated: false, message` |
|
|
|
|
**Callers:** Execute Text Update, Execute Callback Update, Execute Batch Update
|
|
|
|
---
|
|
|
|
### n8n-actions.json (Container Actions)
|
|
|
|
**Input:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| containerId | string | yes | Docker container ID |
|
|
| containerName | string | yes | Container display name |
|
|
| action | string | yes | `"start"`, `"stop"`, or `"restart"` |
|
|
| chatId | number | yes | Telegram chat ID |
|
|
| messageId | number | yes | Telegram message ID (0 for text mode) |
|
|
| responseMode | string | yes | `"text"`, `"inline"`, or `"batch"` |
|
|
| correlationId | string | no | Request trace ID |
|
|
|
|
**Output:** `success, message, action, containerName, containerId, chatId, messageId, responseMode`
|
|
|
|
HTTP status codes are checked before message content: 204 = success, 304 = already in state (treated as success), others = error.
|
|
|
|
**Callers:** Execute Container Action, Execute Inline Action, Execute Batch Action Sub-workflow
|
|
|
|
---
|
|
|
|
### n8n-logs.json (Container Logs)
|
|
|
|
**Input:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| containerName | string | yes* | Container name for lookup |
|
|
| containerId | string | no | Docker container ID (optional) |
|
|
| lineCount | number | no | Lines to retrieve (default: 50, max: 1000) |
|
|
| chatId | number | yes | Telegram chat ID |
|
|
| messageId | number | no | Telegram message ID (default: 0) |
|
|
| responseMode | string | no | `"text"` or `"inline"` (default: "text") |
|
|
| correlationId | string | no | Request trace ID |
|
|
|
|
*Either containerId or containerName is required.
|
|
|
|
**Output:** `success: true, message, containerName, lineCount`
|
|
|
|
Errors (container not found, Docker error) throw exceptions handled by n8n's error system.
|
|
|
|
**Callers:** Execute Text Logs, Execute Inline Logs
|
|
|
|
---
|
|
|
|
### n8n-batch-ui.json (Batch Selection UI)
|
|
|
|
**Input:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| chatId | number | yes | Telegram chat ID |
|
|
| messageId | number | yes | Telegram message ID |
|
|
| callbackData | string | yes | Raw callback data string |
|
|
| queryId | string | yes | Telegram callback query ID |
|
|
| action | string | yes | `"mode"`, `"toggle"`, `"nav"`, `"exec"`, `"clear"`, `"cancel"` |
|
|
| batchPage | number | no | Current page number (default: 0) |
|
|
| selectedCsv | string | no | Comma-separated selected container names |
|
|
| toggleName | string | no | Container name being toggled |
|
|
| batchAction | string | no | Action for batch execution (stop/restart/update) |
|
|
| correlationId | string | no | Request trace ID |
|
|
|
|
**Output:**
|
|
|
|
| action | Description |
|
|
|--------|-------------|
|
|
| `"keyboard"` | Rendered selection keyboard with checkmarks |
|
|
| `"execute"` | User confirmed — includes containerNames and batchAction |
|
|
| `"cancel"` | User cancelled batch selection |
|
|
|
|
Batch selection uses bitmap encoding (base36 BigInt) to fit container selections within Telegram's 64-byte callback data limit.
|
|
|
|
**Callers:** Execute Batch UI
|
|
|
|
---
|
|
|
|
### n8n-status.json (Container Status/List)
|
|
|
|
**Input:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| chatId | number | yes | Telegram chat ID |
|
|
| messageId | number | yes | Telegram message ID |
|
|
| action | string | yes | `"list"`, `"status"`, `"paginate"` |
|
|
| containerId | string | no | Docker container ID (for status lookup) |
|
|
| containerName | string | no | Container name (for status lookup) |
|
|
| page | number | no | Page number for pagination (default: 0) |
|
|
| queryId | string | no | Telegram callback query ID |
|
|
| searchTerm | string | no | Container name search term |
|
|
| correlationId | string | no | Request trace ID |
|
|
|
|
**Output:**
|
|
|
|
| action | Description |
|
|
|--------|-------------|
|
|
| `"list"` | Container list with pagination keyboard |
|
|
| `"status"` | Single container detail with action buttons |
|
|
| `"paginate"` | Updated page of container list |
|
|
|
|
The container list keyboard includes an "Update All :latest" button after the navigation row.
|
|
|
|
**Callers:** Execute Container Status, Execute Select Status, Execute Paginate Status, Execute Batch Cancel Status
|
|
|
|
---
|
|
|
|
### n8n-confirmation.json (Confirmation Dialogs)
|
|
|
|
**Input:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| chatId | number | yes | Telegram chat ID |
|
|
| messageId | number | yes | Telegram message ID |
|
|
| action | string | yes | `"show_stop"`, `"show_update"`, `"confirm"`, `"cancel"`, `"expired"` |
|
|
| containerId | string | no | Docker container ID |
|
|
| containerName | string | yes | Container display name |
|
|
| confirmAction | string | no | `"stop"` or `"update"` (for confirm action) |
|
|
| confirmationToken | string | no | Timestamp token for 30-second expiry check |
|
|
| expired | boolean | no | Whether confirmation has expired |
|
|
| responseMode | string | no | `"inline"` (default) |
|
|
| correlationId | string | no | Request trace ID |
|
|
|
|
This sub-workflow internally calls n8n-actions.json for confirmed stop actions.
|
|
|
|
**Output:**
|
|
|
|
| action | Description |
|
|
|--------|-------------|
|
|
| `"show_stop"` | Stop confirmation dialog rendered |
|
|
| `"show_update"` | Update confirmation dialog rendered |
|
|
| `"confirm_stop_result"` | Stop executed, result returned |
|
|
| `"confirm_update"` | Update confirmed, containerId/name returned for update sub-workflow |
|
|
| `"cancel"` | Confirmation cancelled |
|
|
| `"expired"` | Confirmation token expired (30-second timeout) |
|
|
|
|
**Callers:** Execute Confirmation (fed by 3 Prepare Input nodes: Prepare Confirm Input, Prepare Show Stop Input, Prepare Show Update Input)
|
|
|
|
---
|
|
|
|
### n8n-matching.json (Container Matching/Disambiguation)
|
|
|
|
**Input:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| action | string | yes | `"match_action"`, `"match_update"`, or `"match_batch"` |
|
|
| containerList | string | yes | Raw Docker API JSON output (container list) |
|
|
| searchTerm | string | yes | Container name query to match |
|
|
| selectedContainers | string | no | Comma-separated names for batch matching |
|
|
| chatId | number | yes | Telegram chat ID |
|
|
| messageId | number | yes | Telegram message ID |
|
|
| correlationId | string | no | Request trace ID |
|
|
|
|
Matching priority: exact match > single substring match > multiple matches (disambiguation) > no match (suggestion keyboard).
|
|
|
|
**Output (action match):**
|
|
|
|
| action | Description |
|
|
|--------|-------------|
|
|
| `"matched"` | Single container matched — includes containerId, containerName |
|
|
| `"multiple"` | Multiple matches — includes matches array for disambiguation |
|
|
| `"no_match"` | No match found |
|
|
| `"suggestion"` | Suggestion keyboard with close matches |
|
|
| `"error"` | Matching error |
|
|
|
|
**Output (update match):** Same as action match with `_update` suffix on action values.
|
|
|
|
**Output (batch match):**
|
|
|
|
| action | Description |
|
|
|--------|-------------|
|
|
| `"batch_matched"` | All names resolved — includes matchedContainers array |
|
|
| `"disambiguation"` | Some names ambiguous — disambiguation keyboard |
|
|
| `"not_found"` | Some names not found |
|
|
|
|
**Callers:** Execute Action Match, Execute Update Match, Execute Batch Match
|
|
|
|
## Execute Workflow Node Map
|
|
|
|
17 Execute Workflow nodes in the main workflow dispatch to 7 sub-workflows:
|
|
|
|
| Target | Execute Nodes |
|
|
|--------|---------------|
|
|
| n8n-update.json | Execute Text Update, Execute Callback Update, Execute Batch Update |
|
|
| n8n-actions.json | Execute Container Action, Execute Inline Action, Execute Batch Action Sub-workflow |
|
|
| n8n-logs.json | Execute Text Logs, Execute Inline Logs |
|
|
| n8n-batch-ui.json | Execute Batch UI |
|
|
| n8n-status.json | Execute Container Status, Execute Select Status, Execute Paginate Status, Execute Batch Cancel Status |
|
|
| n8n-confirmation.json | Execute Confirmation |
|
|
| n8n-matching.json | Execute Action Match, Execute Update Match, Execute Batch Match |
|
|
|
|
## Main Workflow Internals
|
|
|
|
### Node Breakdown (166 nodes)
|
|
|
|
| Category | Count | Purpose |
|
|
|----------|-------|---------|
|
|
| Code | 60 | Command parsing, input preparation, result routing, response building, batch orchestration |
|
|
| HTTP Request | 40 | Docker API and Telegram API calls |
|
|
| Telegram | 23 | User-facing response nodes |
|
|
| Execute Workflow | 17 | Sub-workflow dispatch |
|
|
| Switch | 13 | Keyword Router, Route Callback, result routing |
|
|
| If | 8 | Auth checks, batch completion, expiry, status routing |
|
|
| Execute Command | 6 | Docker CLI (container list, exec) |
|
|
| Telegram Trigger | 1 | Entry point |
|
|
|
|
### Code Node Categories
|
|
|
|
The 60 Code nodes break down into orchestration categories. All are infrastructure — none contain extractable domain logic:
|
|
|
|
| Category | Count | Role |
|
|
|----------|-------|------|
|
|
| prepare-input | 27 | Build input objects for sub-workflow calls |
|
|
| route-result | 12 | Interpret sub-workflow responses for routing |
|
|
| build-response | 8 | Build Telegram messages and keyboards |
|
|
| orchestration | 6 | Batch loop control and state management |
|
|
| parse-command | 5 | Parse text commands into structured data |
|
|
| domain-logic | 2 | Legacy candidates (net-negative extraction) |
|
|
|
|
### Callback Data Encoding
|
|
|
|
Telegram limits callback data to 64 bytes. The system uses two encoding schemes:
|
|
|
|
**Colon-delimited** for single operations: `s:containerId` (status), `stop:containerId` (action), `cfm:stop:containerId:token` (confirmation)
|
|
|
|
**Bitmap encoding** for batch selection: `b:0:1a3:5` where the middle segment is a base36 BigInt representing selected container indices. Supports 50+ containers within the 64-byte limit.
|
|
|
|
Legacy parsers (`batch:toggle:`, `batch:nav:`, `batch:exec:`) are retained for graceful migration of in-flight messages.
|
|
|
|
## Deployment
|
|
|
|
### Redeploying After Changes
|
|
|
|
1. Import the modified sub-workflow JSON into n8n
|
|
2. If main workflow changed, re-import n8n-workflow.json
|
|
3. Activate the workflow
|
|
|
|
Workflow IDs are stable — n8n preserves them across re-imports of the same workflow.
|
|
|
|
### Execute Workflow Node Format
|
|
|
|
All Execute Workflow nodes use typeVersion 1.2:
|
|
|
|
```json
|
|
"workflowId": { "__rl": true, "mode": "list", "value": "<id>" }
|
|
```
|
|
|
|
### Testing Checklist
|
|
|
|
After deployment, verify:
|
|
|
|
- [ ] `status` — Shows container list with pagination
|
|
- [ ] Tap container — Shows detail with action buttons
|
|
- [ ] `stop <name>` — Confirmation dialog, confirm executes stop
|
|
- [ ] `update <name>` — Confirmation dialog, confirm pulls image + recreates
|
|
- [ ] `restart <name>` — Immediate restart
|
|
- [ ] `logs <name>` — Shows last 50 lines
|
|
- [ ] `stop plex sonarr` — Batch selection keyboard
|
|
- [ ] Select multiple, execute — Batch processes all selected
|
|
- [ ] `update all` — Lists :latest containers, confirm updates all
|
|
- [ ] Update All button in keyboard — Same flow via inline keyboard
|
|
|
|
## Known Limitations
|
|
|
|
### Unraid Update Badges
|
|
|
|
After the bot updates a container, Unraid's Docker tab may show "apply update" on the next check. The bot uses Docker API directly while Unraid tracks containers through its XML template system — it doesn't know the container was updated externally.
|
|
|
|
**Resolution:** Click "Apply Update" in Unraid. It completes instantly since the image is already cached.
|
|
|
|
**Why not automated:** Clearing the badge would require calling Unraid's internal web API (authentication, template parsing) for a cosmetic issue that takes one click.
|
|
|
|
### n8n Static Data
|
|
|
|
n8n workflow static data (`$getWorkflowStaticData('global')`) is execution-scoped, not workflow-scoped. Data written in one execution is not available in the next. This prevents persistent cross-execution features like error ring buffers or debug command history.
|
|
|
|
### Orphan Nodes
|
|
|
|
3 legacy Code nodes remain in the main workflow (Build Action Command, Build Immediate Action Command, Prepare Cancel Return). They are unreachable dead code from pre-modularization inline action paths. They have no incoming connections and do not affect functionality.
|