{ "name": "Container Logs", "nodes": [ { "parameters": {}, "id": "711c56f9-46d5-41dd-aa5c-e7e4230793f3", "name": "Execute Workflow Trigger", "type": "n8n-nodes-base.executeWorkflowTrigger", "typeVersion": 1, "position": [ 240, 300 ] }, { "parameters": { "jsCode": "// Parse and validate input\nconst input = $json;\n\n// Get container identifier (ID or name)\nconst containerId = input.containerId || '';\nconst containerName = input.containerName || '';\nconst lineCount = input.lineCount || 50;\nconst chatId = input.chatId;\nconst messageId = input.messageId || 0;\nconst responseMode = input.responseMode || 'text';\n\nif (!containerId && !containerName) {\n throw new Error('Either containerId or containerName required');\n}\n\nif (!chatId) {\n throw new Error('chatId required');\n}\n\nreturn {\n json: {\n containerId: containerId,\n containerName: containerName,\n lineCount: Math.min(Math.max(parseInt(lineCount), 1), 1000),\n chatId: chatId,\n messageId: messageId,\n responseMode: responseMode\n }\n};" }, "id": "dac65a64-173a-4536-b3c5-0699704380fa", "name": "Parse Input", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 460, 300 ] }, { "parameters": { "jsCode": "// Get container ID if needed\nconst data = $json;\n\n// If we already have container ID, pass through\nif (data.containerId) {\n return {\n json: {\n ...data,\n useDirectId: true\n }\n };\n}\n\n// Otherwise, need to query Docker to find by name\nreturn {\n json: {\n ...data,\n useDirectId: false,\n dockerCommand: 'curl -s --max-time 5 \"http://docker-socket-proxy:2375/v1.47/containers/json?all=1\"'\n }\n};" }, "id": "d6db7666-5ecd-4200-8231-ffa6307e4c39", "name": "Check Container ID", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 680, 300 ] }, { "parameters": { "rules": { "values": [ { "id": "has-id", "conditions": { "options": { "caseSensitive": true, "typeValidation": "loose" }, "conditions": [ { "id": "check-direct", "leftValue": "={{ $json.useDirectId }}", "rightValue": "true", "operator": { "type": "boolean", "operation": "true" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "direct" } ] }, "options": { "fallbackOutput": "extra" } }, "id": "818a4c33-e150-423e-a333-adf9843aac05", "name": "Route ID Check", "type": "n8n-nodes-base.switch", "typeVersion": 3.2, "position": [ 900, 300 ] }, { "parameters": { "command": "={{ $json.dockerCommand }}" }, "id": "83cbcda1-68c5-46ee-bbb6-2eb5f1ae9077", "name": "Query Docker", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [ 1120, 400 ] }, { "parameters": { "jsCode": "// Find container by name\nconst dockerOutput = $input.item.json.stdout;\nconst data = $('Check Container ID').item.json;\nconst containerName = data.containerName.toLowerCase();\n\n// Parse Docker response\nlet containers;\ntry {\n containers = JSON.parse(dockerOutput);\n} catch (e) {\n throw new Error('Failed to parse Docker response');\n}\n\n// Normalize name function\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '')\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '')\n .toLowerCase();\n}\n\n// Find exact match\nconst container = containers.find(c => normalizeName(c.Names[0]) === containerName);\n\nif (!container) {\n throw new Error(`Container \"${containerName}\" not found`);\n}\n\nreturn {\n json: {\n ...data,\n containerId: container.Id,\n containerName: normalizeName(container.Names[0])\n }\n};" }, "id": "52dd705b-dd3b-4fdc-8484-276845857ad0", "name": "Find Container", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1340, 400 ] }, { "parameters": { "jsCode": "// Build Docker logs command\nconst data = $json;\nconst containerId = data.containerId;\nconst lineCount = data.lineCount;\n\nconst cmd = `curl -s --max-time 10 \"http://docker-socket-proxy:2375/v1.47/containers/${containerId}/logs?stdout=1&stderr=1&tail=${lineCount}×tamps=1\"`;\n\nreturn {\n json: {\n ...data,\n logsCommand: cmd\n }\n};" }, "id": "86619651-82f0-459b-a969-d210d2dd6361", "name": "Build Logs Command", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1560, 300 ] }, { "parameters": { "command": "={{ $json.logsCommand }}" }, "id": "b602549b-e526-4207-a41d-74a936060a25", "name": "Execute Logs", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [ 1780, 300 ] }, { "parameters": { "jsCode": "// Format logs output for Telegram\nconst rawOutput = $input.item.json.stdout || '';\nconst data = $('Build Logs Command').item.json;\n\n// HTML escape function\nfunction escapeHtml(text) {\n return text.replace(/&/g, '&').replace(//g, '>');\n}\n\n// Handle empty logs\nif (!rawOutput || rawOutput.trim() === '') {\n return {\n json: {\n success: true,\n message: `No logs available for ${data.containerName}`,\n containerName: data.containerName,\n lineCount: 0\n }\n };\n}\n\n// Strip Docker binary headers and process lines\nconst lines = rawOutput.split('\\n')\n .filter(line => line.length > 0)\n .map(line => {\n // Check if line starts with binary header (8-byte Docker stream header)\n if (line.length > 8 && line.charCodeAt(0) <= 2) {\n return line.substring(8);\n }\n return line;\n })\n .join('\\n');\n\n// Truncate for Telegram (4096 char limit, leave room for header)\nconst maxLen = 3800;\nconst truncated = lines.length > maxLen\n ? lines.substring(0, maxLen) + '\\n... (truncated)'\n : lines;\n\n// Escape HTML entities\nconst escaped = escapeHtml(truncated);\n\nconst lineCount = lines.split('\\n').length;\nconst header = `Logs for ${data.containerName} (last ${lineCount} lines):\\n\\n`;\nconst formatted = header + '
' + escaped + '';\n\nreturn {\n json: {\n success: true,\n message: formatted,\n containerName: data.containerName,\n lineCount: lineCount\n }\n};" }, "id": "e423b026-e619-4e3a-abd9-563e935cd74d", "name": "Format Logs", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2000, 300 ] } ], "connections": { "Execute Workflow Trigger": { "main": [ [ { "node": "Parse Input", "type": "main", "index": 0 } ] ] }, "Parse Input": { "main": [ [ { "node": "Check Container ID", "type": "main", "index": 0 } ] ] }, "Check Container ID": { "main": [ [ { "node": "Route ID Check", "type": "main", "index": 0 } ] ] }, "Route ID Check": { "main": [ [ { "node": "Build Logs Command", "type": "main", "index": 0 } ], [ { "node": "Query Docker", "type": "main", "index": 0 } ] ] }, "Query Docker": { "main": [ [ { "node": "Find Container", "type": "main", "index": 0 } ] ] }, "Find Container": { "main": [ [ { "node": "Build Logs Command", "type": "main", "index": 0 } ] ] }, "Build Logs Command": { "main": [ [ { "node": "Execute Logs", "type": "main", "index": 0 } ] ] }, "Execute Logs": { "main": [ [ { "node": "Format Logs", "type": "main", "index": 0 } ] ] } }, "active": true, "settings": { "executionOrder": "v1" }, "versionId": "c2a9969f-2928-41f9-be03-9692ae242751", "meta": { "instanceId": "unraid-docker-manager" }, "tags": [] }