--- phase: 03-container-actions plan: 01 type: execute wave: 1 depends_on: [] files_modified: [n8n-workflow.json] autonomous: true must_haves: truths: - "User can start a stopped container by name" - "User can stop a running container by name" - "User can restart a container by name" - "Single container matches execute immediately without confirmation" artifacts: - path: "n8n-workflow.json" provides: "Action routing and Docker API POST calls" contains: "Route Action Message" key_links: - from: "Switch node (Route Message)" to: "Action routing branch" via: "contains start/stop/restart" pattern: "start|stop|restart" - from: "Execute Command node" to: "Docker API" via: "curl POST to /containers/{id}/start|stop|restart" pattern: "-X POST.*containers" --- Implement basic container actions (start, stop, restart) for single-container matches. Purpose: Enable users to control containers through Telegram by sending commands like "start plex" or "stop sonarr". When exactly one container matches, the action executes immediately. Output: Extended n8n workflow with action command routing and Docker API POST calls. @/home/luc/.claude/get-shit-done/workflows/execute-plan.md @/home/luc/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-container-actions/03-CONTEXT.md @.planning/phases/03-container-actions/03-RESEARCH.md @.planning/phases/02-docker-integration/02-02-SUMMARY.md @n8n-workflow.json Task 1: Add action command routing to workflow n8n-workflow.json Extend the existing "Route Message" Switch node to detect action commands. Add new routes for patterns: - "start " → action branch - "stop " → action branch - "restart " → action branch The route should match case-insensitively and capture the container name portion. Add a Code node after the route that: 1. Parses the action type (start/stop/restart) from message text 2. Parses the container name from message text 3. Returns: { action, containerQuery, chatId, messageId } Example parsing: ```javascript const text = $json.message.text.toLowerCase().trim(); const match = text.match(/^(start|stop|restart)\s+(.+)$/i); if (!match) { return { json: { error: 'Invalid action format' } }; } return { json: { action: match[1].toLowerCase(), containerQuery: match[2].trim(), chatId: $json.message.chat.id, messageId: $json.message.message_id } }; ``` Send "start test" to bot - should route to action branch (may fail on execution, but routing works) Action commands route to dedicated branch, action and container name are parsed Task 2: Implement container matching and action execution n8n-workflow.json After the action parsing node, add nodes to: 1. **Docker List Containers** (Execute Command node): - Same as existing status query: `curl -s --unix-socket /var/run/docker.sock 'http://localhost/v1.47/containers/json?all=true'` 2. **Match Container** (Code node): - Reuse the fuzzy matching logic from Phase 2: - Case-insensitive substring match - Strip common prefixes (linuxserver-, binhex-) - Return match results: - `matches`: array of matching containers (Id, Name, State) - `matchCount`: number of matches - `action`: preserved from input - `chatId`: preserved from input 3. **Check Match Count** (Switch node): - Route based on matchCount: - 0 matches → "No Match" branch - 1 match → "Single Match" branch (execute action) - >1 matches → "Multiple Matches" branch 4. **Execute Action** (Execute Command node on "Single Match" branch): - Build curl command based on action: ```javascript const containerId = $json.matches[0].Id; const action = $json.action; // stop and restart use ?t=10 for graceful timeout const timeout = (action === 'stop' || action === 'restart') ? '?t=10' : ''; const cmd = `curl -s -o /dev/null -w "%{http_code}" --unix-socket /var/run/docker.sock -X POST 'http://localhost/v1.47/containers/${containerId}/${action}${timeout}'`; return { json: { cmd, containerId, action, containerName: $json.matches[0].Name } }; ``` 5. **Parse Result** (Code node): - Handle HTTP response codes: - 204: Success - 304: Already in state (also success for user) - 404: Container not found (shouldn't happen after match) - 500: Docker error ```javascript const statusCode = parseInt($json.stdout.trim()); const containerName = $('Execute Action').first().json.containerName.replace(/^\//, ''); const action = $('Match Container').first().json.action; if (statusCode === 204 || statusCode === 304) { const verb = action === 'start' ? 'started' : action === 'stop' ? 'stopped' : 'restarted'; return { json: { success: true, message: `${containerName} ${verb} successfully` } }; } return { json: { success: false, message: `Failed to ${action} ${containerName}: HTTP ${statusCode}` } }; ``` 6. **Send Response** (Telegram Send Message node): - Chat ID: `{{ $json.chatId }}` - Text: `{{ $json.message }}` - Parse Mode: HTML For "No Match" and "Multiple Matches" branches, add placeholder Send Message nodes: - No Match: "No container found matching '{{ $json.containerQuery }}'" - Multiple Matches: "Found {{ $json.matchCount }} containers matching '{{ $json.containerQuery }}'. Confirmation required." These placeholders will be replaced with proper callback flows in Plan 03-02. 1. Start a stopped container: "start [container-name]" → should start and report success 2. Stop a running container: "stop [container-name]" → should stop and report success 3. Restart a container: "restart [container-name]" → should restart and report success 4. Try with partial name (fuzzy match): "restart plex" for container named "plex-server" → should work Single-match container actions execute via Docker API and report results to Telegram Task 3: Handle action errors gracefully n8n-workflow.json Add error handling throughout the action flow: 1. **Docker List Error** (after Docker List Containers): - Add IF node to check for curl errors - On error: Send diagnostic message to user ```javascript const hasError = !$json.stdout || $json.stdout.trim() === '' || !$json.stdout.startsWith('['); return { json: { hasError, errorDetail: $json.stderr || 'Empty response from Docker API' } }; ``` 2. **Execute Action Error** (after Execute Command): - The parse result node already handles non-success codes - Add stderr check for curl-level failures: ```javascript if ($json.stderr && $json.stderr.trim()) { return { json: { success: false, message: `Docker error: ${$json.stderr}` } }; } ``` 3. **Error Response Format** (per CONTEXT.md - diagnostic details): - Include actual error info in messages - Example: "Failed to stop plex: HTTP 500 - Container is not running" - Don't hide technical details from user Ensure all error paths eventually reach a Send Message node so the user always gets feedback. 1. Try to stop an already-stopped container → should report success (304 treated as success) 2. Try to start an already-running container → should report success (304 treated as success) 3. Try action on non-existent container → should report "No container found" All error cases report diagnostic details to user, no silent failures End-to-end verification: 1. "start [stopped-container]" → Container starts, user sees "started successfully" 2. "stop [running-container]" → Container stops, user sees "stopped successfully" 3. "restart [any-container]" → Container restarts, user sees "restarted successfully" 4. "stop [already-stopped]" → User sees success (not error) 5. "stop nonexistent" → User sees "No container found matching 'nonexistent'" 6. "stop arr" (matches sonarr, radarr, lidarr) → User sees placeholder about multiple matches Import updated workflow into n8n and verify all scenarios via Telegram. - Single-match actions execute immediately without confirmation - All three actions (start/stop/restart) work correctly - Fuzzy matching finds containers by partial name - 204 and 304 responses both treated as success - Error messages include diagnostic details - No silent failures - user always gets response After completion, create `.planning/phases/03-container-actions/03-01-SUMMARY.md`