{
  "id": "EyPTvUFPOkBXeelq",
  "name": "Proxmox Monitoring Report - VM Status, Host Resource Usage, Temperature Control - Sheduled with Telegram Export",
  "tags": [],
  "nodes": [
    {
      "id": "62234df5-c87e-40f7-8fca-0b2cb4346d31",
      "name": "Schedule Every 15min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -896,
        224
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "698e55da-cdf3-475f-b009-34e075274dca",
      "name": "Set Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        -672,
        224
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "var1",
              "name": "PROXMOX_IP",
              "type": "string",
              "value": "0.0.0.0"
            },
            {
              "id": "var2",
              "name": "PROXMOX_PORT",
              "type": "string",
              "value": "8006"
            },
            {
              "id": "var3",
              "name": "PROXMOX_NODE",
              "type": "string",
              "value": "pve"
            },
            {
              "id": "var4",
              "name": "TELEGRAM_CHAT_ID",
              "type": "string",
              "value": "YOUR_CHAT_ID"
            },
            {
              "id": "var5",
              "name": "PROXMOX_USER",
              "type": "string",
              "value": "root@pam"
            },
            {
              "id": "var6",
              "name": "PROXMOX_PASSWORD",
              "type": "string",
              "value": "your_password"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "b25669c5-a50b-4d0d-9b1b-fb763661bd4a",
      "name": "Proxmox Login",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -448,
        224
      ],
      "parameters": {
        "url": "=https://{{ $json.PROXMOX_IP }}:{{ $json.PROXMOX_PORT }}/api2/json/access/ticket",
        "method": "POST",
        "options": {
          "allowUnauthorizedCerts": true
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "username",
              "value": "={{ $json.PROXMOX_USER }}"
            },
            {
              "name": "password",
              "value": "={{ $json.PROXMOX_PASSWORD }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "25af5d5f-3a23-45cf-8387-c00d756cfcb6",
      "name": "Prepare Auth",
      "type": "n8n-nodes-base.set",
      "position": [
        -224,
        224
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ticket",
              "name": "ticket",
              "type": "string",
              "value": "={{ $json.data.ticket }}"
            },
            {
              "id": "csrf",
              "name": "csrf",
              "type": "string",
              "value": "={{ $json.data.CSRFPreventionToken }}"
            },
            {
              "id": "ip",
              "name": "PROXMOX_IP",
              "type": "string",
              "value": "={{ $('Set Variables').item.json.PROXMOX_IP }}"
            },
            {
              "id": "port",
              "name": "PROXMOX_PORT",
              "type": "string",
              "value": "={{ $('Set Variables').item.json.PROXMOX_PORT }}"
            },
            {
              "id": "node",
              "name": "PROXMOX_NODE",
              "type": "string",
              "value": "={{ $('Set Variables').item.json.PROXMOX_NODE }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "43ffeb7f-396a-4e69-a8cc-dc17b506321c",
      "name": "API - VM List",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        0,
        0
      ],
      "parameters": {
        "url": "=https://{{ $json.PROXMOX_IP }}:{{ $json.PROXMOX_PORT }}/api2/json/nodes/{{ $json.PROXMOX_NODE }}/qemu",
        "options": {
          "allowUnauthorizedCerts": true
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Cookie",
              "value": "=PVEAuthCookie={{ $json.ticket }}"
            },
            {
              "name": "CSRFPreventionToken",
              "value": "={{ $json.csrf }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "66371dcf-84e5-4467-a104-a466517ee645",
      "name": "API - Node Tasks",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        0,
        224
      ],
      "parameters": {
        "url": "=https://{{ $('Set Variables').item.json.PROXMOX_IP }}:{{ $('Set Variables').item.json.PROXMOX_PORT }}/api2/json/nodes/{{ $('Set Variables').item.json.PROXMOX_NODE }}/tasks?limit=100",
        "options": {
          "allowUnauthorizedCerts": true
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Cookie",
              "value": "=PVEAuthCookie={{ $('Prepare Auth').item.json.ticket }}"
            },
            {
              "name": "CSRFPreventionToken",
              "value": "={{ $json.csrf }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "fe91fc19-09e6-44c9-a0d2-403494e5273e",
      "name": "API - Node Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        0,
        448
      ],
      "parameters": {
        "url": "=https://{{ $('Set Variables').item.json.PROXMOX_IP }}:{{ $('Set Variables').item.json.PROXMOX_PORT }}/api2/json/nodes/{{ $('Set Variables').item.json.PROXMOX_NODE }}/status",
        "options": {
          "allowUnauthorizedCerts": true
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Cookie",
              "value": "=PVEAuthCookie={{ $('Prepare Auth').item.json.ticket }}"
            },
            {
              "name": "CSRFPreventionToken",
              "value": "={{ $json.csrf }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "958b184a-aade-42c1-aa6b-26f4bc3d0aa1",
      "name": "SSH - Get Sensors",
      "type": "n8n-nodes-base.ssh",
      "position": [
        304,
        224
      ],
      "parameters": {
        "command": "=# Temperature sensors\nsensors 2>/dev/null | grep -E 'Package|Core' || echo 'No temperature data available'\n\n# Detailed uptime\nuptime"
      },
      "credentials": {
        "sshPassword": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5d2150c2-34b8-4cbc-8b08-263dc9a2b583",
      "name": "Process Data",
      "type": "n8n-nodes-base.code",
      "position": [
        528,
        112
      ],
      "parameters": {
        "jsCode": "// Get data from previous nodes by name\nconst sshData = $input.first().json.stdout;\nconst vmData = $('API - VM List').first().json.data;\nconst nodeData = $('API - Node Status').first().json.data;\nconst tasksData = $('API - Node Tasks').first().json.data;\n\nif (!vmData) {\n  throw new Error('VM data not received from API - VM List node');\n}\nif (!nodeData) {\n  throw new Error('Node status data not received from API - Node Status node');\n}\nif (!tasksData) {\n  throw new Error('Tasks data not received from API - Node Tasks node');\n}\n\n// Parse SSH output for temperature\nconst sshLines = (sshData || '').split('\\n');\nconst tempLines = sshLines.filter(l => l.includes('Package') || l.includes('Core'));\n\nlet temperatureDisplay = 'No temperature data available';\nlet hasTempWarning = false;\n\nif (tempLines.length > 0) {\n  const tempData = [];\n  let hasHighTemp = false;\n  \n  for (const line of tempLines) {\n    const currentMatch = line.match(/([+\\-]?[0-9.]+)\u00b0C/);\n    const highMatch = line.match(/high\\s*=\\s*([+\\-]?[0-9.]+)\u00b0C/);\n    \n    if (currentMatch && highMatch) {\n      const current = parseFloat(currentMatch[1]);\n      const high = parseFloat(highMatch[1]);\n      const label = line.split(':')[0].trim();\n      \n      tempData.push({ label, current, high, line: line.trim() });\n      \n      if (current >= high) {\n        hasHighTemp = true;\n        hasTempWarning = true;\n      }\n    }\n  }\n  \n  if (hasHighTemp) {\n    temperatureDisplay = tempData.map(t => t.line).join('\\n');\n  } else {\n    const avgTemp = (tempData.reduce((sum, t) => sum + t.current, 0) / tempData.length).toFixed(1);\n    const maxTemp = Math.max(...tempData.map(t => t.current)).toFixed(1);\n    const minTemp = Math.min(...tempData.map(t => t.current)).toFixed(1);\n    temperatureDisplay = `Average: ${avgTemp}\u00b0C (Min: ${minTemp}\u00b0C, Max: ${maxTemp}\u00b0C)`;\n  }\n}\n\n// Parse uptime from SSH\nlet uptimeString = 'N/A';\nconst uptimeLine = sshLines.find(l => l.includes('up'));\nif (uptimeLine) {\n  uptimeString = uptimeLine.trim();\n}\n\n// VM statistics\nconst totalVMs = vmData.length;\nconst runningVMs = vmData.filter(vm => vm.status === 'running').length;\nconst stoppedVMs = totalVMs - runningVMs;\n\n// Find recently stopped VMs from task log\nconst now = Math.floor(Date.now() / 1000);\nconst fifteenMinutesAgo = now - 900;\n\nconst recentStopTasks = tasksData.filter(task => {\n  const isStopTask = task.type === 'qmstop' || task.type === 'qmshutdown';\n  const taskTime = task.starttime || task.endtime || 0;\n  return isStopTask && taskTime >= fifteenMinutesAgo;\n});\n\nconst recentlyStoppedVMs = recentStopTasks.map(task => {\n  let vmid = 'N/A';\n  vmid = task.id;\n  \n  const vm = vmData.find(v => String(v.vmid) === String(vmid));\n  const vmName = vm ? vm.name : 'Unknown';\n  \n  const taskTime = task.starttime || task.endtime || now;\n  const minutesAgo = Math.floor((now - taskTime) / 60);\n  \n  return {\n    id: vmid,\n    name: vmName,\n    minutesAgo: minutesAgo,\n    status: task.status || 'stopped'\n  };\n});\n\nconst recentlyStopped = recentlyStoppedVMs.length;\n\n// Node statistics from API\nconst cpuUsage = nodeData.cpu ? (nodeData.cpu * 100).toFixed(2) + '%' : 'N/A';\nconst memTotal = nodeData.memory ? (nodeData.memory.total / (1024**3)).toFixed(2) + ' GB' : 'N/A';\nconst memUsed = nodeData.memory ? (nodeData.memory.used / (1024**3)).toFixed(2) + ' GB' : 'N/A';\nconst memPercent = nodeData.memory ? ((nodeData.memory.used / nodeData.memory.total) * 100).toFixed(2) + '%' : 'N/A';\nconst uptimeSeconds = nodeData.uptime || 0;\nconst uptimeDays = Math.floor(uptimeSeconds / 86400);\nconst uptimeHours = Math.floor((uptimeSeconds % 86400) / 3600);\nconst uptimeFormatted = `${uptimeDays}d ${uptimeHours}h`;\n\nreturn {\n  timestamp: new Date().toISOString(),\n  vms: {\n    total: totalVMs,\n    running: runningVMs,\n    stopped: stoppedVMs,\n    recentlyStopped: recentlyStopped,\n    recentlyStoppedDetails: recentlyStoppedVMs\n  },\n  host: {\n    cpu: cpuUsage,\n    memoryUsed: memUsed,\n    memoryTotal: memTotal,\n    memoryPercent: memPercent,\n    uptime: uptimeFormatted,\n    uptimeDetailed: uptimeString,\n    temperature: temperatureDisplay,\n    tempWarning: hasTempWarning\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "3396072a-4d74-4b52-a8a4-55ba02f75bc2",
      "name": "Generate Formatted Message",
      "type": "n8n-nodes-base.code",
      "position": [
        528,
        352
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\n\nconst tempWarningEmoji = data.host.tempWarning ? '\ud83d\udd25 ' : '';\nconst tempTitle = data.host.tempWarning ? 'Temperature Warning' : 'Temperature Data';\n\nlet recentlyStoppedSection = '';\nif (data.vms.recentlyStopped > 0 && data.vms.recentlyStoppedDetails && data.vms.recentlyStoppedDetails.length > 0) {\n  const vmList = data.vms.recentlyStoppedDetails\n    .map(vm => `  \u2022 VM ${vm.id} - ${vm.name} (${vm.minutesAgo} min ago)`)\n    .join('\\n');\n  recentlyStoppedSection = `\\n\\n<b>Recently Stopped VMs Details:</b>\\n${vmList}`;\n}\n\nconst message = `<b>Proxmox Monitoring Report</b>\n<i>Generated: ${new Date(data.timestamp).toLocaleString('en-US')}</i>\n\n<b>\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501</b>\n<b>Virtual Machines</b>\n<b>\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501</b>\n\n<b>Total VMs:</b> ${data.vms.total}\n<b>Running:</b> ${data.vms.running} \u2705\n<b>Stopped:</b> ${data.vms.stopped} \u26d4\n<b>Recently Stopped:</b> ${data.vms.recentlyStopped} \u26a0\ufe0f${recentlyStoppedSection}\n\n<b>\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501</b>\n<b>Host Resources</b>\n<b>\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501</b>\n\n<b>CPU Usage:</b> ${data.host.cpu}\n<b>Memory:</b> ${data.host.memoryUsed} / ${data.host.memoryTotal}\n           (${data.host.memoryPercent})\n<b>Uptime:</b> ${data.host.uptime}\n\n<b>\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501</b>\n<b>${tempWarningEmoji}${tempTitle}</b>\n<b>\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501</b>\n\n<code>${data.host.temperature}</code>`;\n\nreturn { message };"
      },
      "typeVersion": 2
    },
    {
      "id": "81a4878f-35a2-44d1-81a2-ca209d6e70e3",
      "name": "Send Telegram Report",
      "type": "n8n-nodes-base.telegram",
      "position": [
        816,
        224
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "={{ $('Set Variables').first().json.TELEGRAM_CHAT_ID }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "31ae9494-5e80-40be-8aea-4ca982960dbc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1184,
        16
      ],
      "parameters": {
        "width": 380,
        "height": 180,
        "content": "## Proxmox Monitoring Workflow\n\nMonitors your Proxmox VE server every 15 minutes and sends automated reports via Telegram.\n\nTracks VM status, host resources, temperature sensors, and recently stopped VMs."
      },
      "typeVersion": 1
    },
    {
      "id": "467412ef-997f-4666-81e2-01c3fb23202f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -784,
        400
      ],
      "parameters": {
        "width": 320,
        "height": 292,
        "content": "## CONFIGURATION REQUIRED\n\n1. Set your Proxmox IP, port, and node name\n2. Enter Proxmox username (with realm, e.g. root@pam)\n3. Add your Proxmox password\n4. Set your Telegram Chat ID\n\nRequires: lm-sensors installed on Proxmox host"
      },
      "typeVersion": 1
    },
    {
      "id": "9f4196b9-741d-4536-b487-89be23dfa9e3",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -432,
        -16
      ],
      "parameters": {
        "width": 300,
        "height": 204,
        "content": "## Authentication Flow\n\nLogs into Proxmox API to obtain:\n- Session ticket (valid 15 minutes)\n- CSRF prevention token\n\nBoth are required for subsequent API calls"
      },
      "typeVersion": 1
    },
    {
      "id": "23393f4f-3ba3-4900-aec5-7952f9b64586",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        608
      ],
      "parameters": {
        "width": 300,
        "height": 272,
        "content": "## Data Collection\n\nGathers data from Proxmox API:\n- VM list and status\n- Recent task log (stop/shutdown events)\n- Node resources (CPU, memory, uptime)\n\nRuns sequentially to ensure authenticated requests"
      },
      "typeVersion": 1
    },
    {
      "id": "7be5a2e6-d39a-4b4f-9cfd-888eab5261ce",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -16
      ],
      "parameters": {
        "width": 300,
        "height": 220,
        "content": "## SSH Temperature Check\n\nConnects via SSH to read temperature sensors using the sensors command.\n\nRequires SSH Password credential configured with your Proxmox host details."
      },
      "typeVersion": 1
    },
    {
      "id": "af79ac1a-e92a-47aa-b541-4d038175f28e",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        -128
      ],
      "parameters": {
        "width": 320,
        "height": 264,
        "content": "## Data Processing\n\nAnalyzes all collected data:\n- Counts running/stopped VMs\n- Detects VMs stopped in last 15 minutes\n- Calculates resource usage percentages\n- Evaluates temperature thresholds\n- Formats uptime statistics\n\nGenerates structured report with HTML formatting for Telegram"
      },
      "typeVersion": 1
    },
    {
      "id": "e4bc84d3-85ba-4cbe-bb21-8832642ddf82",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        816,
        384
      ],
      "parameters": {
        "width": 300,
        "height": 256,
        "content": "## TELEGRAM CONFIGURATION\n\n1. Create bot via @BotFather\n2. Get bot token and add as credential\n3. Chat ID already set in Set Variables node\n\nMessages use HTML parse mode for formatting"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "2c9715a6-bb36-4d9d-8476-cd3c18a1fac0",
  "connections": {
    "Prepare Auth": {
      "main": [
        [
          {
            "node": "API - VM List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Data": {
      "main": [
        [
          {
            "node": "Generate Formatted Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API - VM List": {
      "main": [
        [
          {
            "node": "API - Node Tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Proxmox Login": {
      "main": [
        [
          {
            "node": "Prepare Auth",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Variables": {
      "main": [
        [
          {
            "node": "Proxmox Login",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API - Node Tasks": {
      "main": [
        [
          {
            "node": "API - Node Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API - Node Status": {
      "main": [
        [
          {
            "node": "SSH - Get Sensors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SSH - Get Sensors": {
      "main": [
        [
          {
            "node": "Process Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Every 15min": {
      "main": [
        [
          {
            "node": "Set Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Formatted Message": {
      "main": [
        [
          {
            "node": "Send Telegram Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}