Files
unraid-docker-manager/.planning/phases/03-container-actions/03-01-PLAN.md
T
Lucas Berger 893412f405 docs(03): create phase plan
Phase 03: Container Actions
- 4 plans in 4 waves (sequential due to shared workflow file)
- Ready for execution
2026-01-29 21:48:58 -05:00

8.8 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
03-container-actions 01 execute 1
n8n-workflow.json
true
truths artifacts key_links
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
path provides contains
n8n-workflow.json Action routing and Docker API POST calls Route Action Message
from to via pattern
Switch node (Route Message) Action routing branch contains start/stop/restart start|stop|restart
from to via pattern
Execute Command node Docker API curl POST to /containers/{id}/start|stop|restart -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.

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

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

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/03-container-actions/03-01-SUMMARY.md`