{
  "name": "Home LAB",
  "nodes": [
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.prompt }}",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "Identity: You are Sentinel-1, an autonomous SRE responsible for maintaining Docker services on the Arcellite Home Lab server and monitoring storage health on the Arcellite NAS.\n\nScope of control:\n\nYou MAY monitor, restart, or suggest updates for these 10 containers:\n- plexinc/pms-docker:latest (name: plex)\n- eclipse-mosquitto:2 (name: mosquitto-mosquitto-1)\n- prom/prometheus:latest (name: prometheus-arcellite)\n- grafana/grafana:latest (name: grafana-arcellite)\n- gcr.io/cadvisor/cadvisor (name: monitoring-cadvisor-1)\n- portainer/portainer-ce (name: portainer)\n- offen/docker-volume-backup (name: arcellite-backup)\n- containrrr/watchtower:latest (name: watchtower)\n- ghcr.io/home-assistant/home-assistant:stable (name: homeassistant)\n- postgres:15 (name: postgres-arcellite)\n\nNEW: You MUST also monitor Arcellite NAS storage health (disk usage, mount stability, and hardware status).\n\nHard exclusion:\nYou MUST NEVER stop, restart, update, or modify the container:\nn8nio/n8n:latest (name: n8n-n8n-1)\nTreat this container as your control plane. If a command would impact n8n directly or indirectly, refuse it and respond with [RED] and an explanation.\n\nCore objectives:\n\nHEALTH CHECKS:\nUse the Server workflow tool for Docker:\n- docker ps\n- docker inspect <container>\n- docker logs --tail 100 <container>\n\nUse the Nas workflow tool for Storage:\n- df -h (Check capacity)\n- mount | grep /mnt (Ensure storage is linked to Arcellite)\n- lsblk (Check physical drive presence)\n\nSummarize status for each managed container: running/stopped, restarts, last errors. \nSummarize NAS health: disk space used, mount status, and drive health.\n\nIMPORTANT: Check container STATUS field. If a container shows \"Up X minutes/hours\", it has been stable. Only flag as new issue if status changed recently (Up < 5 minutes suggests recent restart/issue).\n\nDistinguish between:\nOngoing stable issues: Container in same state for hours (e.g., \"Exited 18 hours ago\") → Don't notify\nNew/changing issues: Container just failed, restarted, or showing new errors → Notify if critical\n\nCORRECTIVE ACTIONS:\nYou may propose and execute:\ndocker restart <container>\ndocker pull <image> && docker compose up -d (when explicitly requested or clearly needed).\nBefore any action, explain what you intend to do and why.\nAfter action, verify with docker ps or logs and report the result.\n\nSAFETY RULES:\nNever run docker system prune, docker rm -f or docker volume rm without explicit human confirmation.\nNever edit docker-compose.yml files unless specifically instructed in the chat.\nPrefer soft fixes (restart single service, rotate logs) over disruptive changes.\n\nREPORTING STYLE:\nStart every response with a status tag: [GREEN] (all good), [AMBER] (minor issues), or [RED] (critical).\nUse short Markdown sections:\nSummary – high level state of the lab and NAS.\nFindings – bullet list per container and NAS status.\nActions – commands you executed.\nNext steps – optional suggestions.\n\nNOTIFICATION RULES (CRITICAL): Default behavior: Send_notification = false\nOnly set Send_notification: true when:\n- RED alert: Any core service is DOWN (including homeassistant/postgres), or NAS storage is UNMOUNTED/FULL (>95%)\n- You took corrective action: Restarted a container, pulled updates, etc.\n- Service state changed: Something that was working is now broken\n- Requires human intervention: Issue cannot auto-resolve and needs manual action\n\nAlways set Send_notification: false when:\n- GREEN alert: All services and NAS running normally\n- AMBER with non-critical issues: Informational warnings, log noise, or non-critical service exited (e.g. watchtower)\n- Stable degraded state: Container/NAS has been in same state for hours\n- No action needed: Issue is cosmetic or self-resolving\n\nDecision tree for AMBER alerts:\n- Is a CORE service (plex, prometheus, grafana, mosquitto, homeassistant, postgres) DOWN or is NAS UNMOUNTED? → Send notification: true\n- Is a CORE service running but showing warnings/errors in logs? → Send notification: false\n- Did you restart or fix something? → Send notification: true\n- Is this the same issue as last check? → Send notification: false\n\nSpecific cases:\n\"watchtower exited\" → false\n\"plex libusb warning but healthy\" → false\n\"arcellite-backup failing\" → false\n\"plex container STOPPED\" → true\n\"mosquitto crashed\" → true\n\"NAS storage > 90% full\" → true (Critical for storage stability)\n\nFINAL RESPONSE REQUIRED: When analysis complete (max 6 tool calls), output your findings DIRECTLY as valid JSON.\n\nTool usage:\n- Commands for Arcellite server go to the 'Server' tool.\n- Commands for NAS health go to the 'Nas' tool.\n- Limit to 6 tool calls MAX."
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        -288,
        -112
      ],
      "id": "35f0046c-81f3-4891-93bb-ee61239cbbe3",
      "name": "AI Agent"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 2
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -912,
        -112
      ],
      "id": "91f75a35-7774-4f4c-b826-0c9fcb372493",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "84090d62-86c2-468b-9433-f50671144489",
              "name": "prompt",
              "value": "Perform a full system audit of the Arcellite Home Lab. Check the status of the 10 managed Docker containers (plex, mosquitto-mosquitto-1, homeassistant, postgres-arcellite, prometheus-arcellite, grafana-arcellite, monitoring-cadvisor-1, portainer, arcellite-backup, and watchtower) via the Server tool. Then, check the Arcellite NAS storage health, capacity, and mount status via the Nas tool. Resolve any minor Docker issues found and report on storage integrity according to your operational protocol.",
              "type": "string"
            },
            {
              "id": "f1ff930d-8d48-4e53-8ca9-70bdbe3d1dab",
              "name": "chatid",
              "value": "message",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -688,
        -112
      ],
      "id": "efea5867-4b42-4fb9-b96b-970abfe6beb1",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "resource": "message",
        "guildId": {
          "__rl": true,
          "value": "1428914820652204075",
          "mode": "list",
          "cachedResultName": "Arcellite server",
          "cachedResultUrl": "https://discord.com/channels/1428914820652204075"
        },
        "channelId": {
          "__rl": true,
          "value": "1432208532760956928",
          "mode": "list",
          "cachedResultName": "approve",
          "cachedResultUrl": "https://discord.com/channels/1428914820652204075/1432208532760956928"
        },
        "content": "={{ $('Code in JavaScript').first().json.discord_message || $json.output || 'Status check complete - no issues' }}\n",
        "options": {}
      },
      "type": "n8n-nodes-base.discord",
      "typeVersion": 2,
      "position": [
        704,
        -112
      ],
      "id": "2c638ca1-8100-47ad-83a3-4dbf2d1d3ad8",
      "name": "Send a message",
      "webhookId": "424e0c42-7fc8-4be7-bf2a-cf46159ed9c5",
      "credentials": {
        "discordBotApi": {
          "id": "PvtdtvlzpxNxA1b8",
          "name": "Discord Bot account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Arcellite Home Lab Discord Formatter\nconst item = $input.first().json;\nconst data = item.output || item;\n\n// Robust check for notification trigger\nconst sendNotification = data.Send_notification === true || \n                        data.Send_notification === 'true' || \n                        data.Send_notification === 'yes' ||\n                        data.Send_notification === 'Yes';\n\nif (!sendNotification) {\n  return [];\n}\n\nconst alertLevel = data.Alert_level || 'GREEN';\nconst dockersUp = data.Dockers_up || '0/10';\nconst nasStatus = data.NAS_status || 'Healthy';\nconst services = data.Services_summary || { healthy: 0, warning: 0, down: 0 };\nconst issues = data.Issues || [];\n\nlet message = `**Arcellite Home Lab Status**\\n`;\nmessage += `\\`Containers: ${dockersUp}\\` | \\`NAS: ${nasStatus}\\`\\n`;\nmessage += `Alert Level: **${alertLevel}**\\n\\n`;\n\nmessage += `**Service Summary**\\n`;\nmessage += `Healthy: ${services.healthy} | Warning: ${services.warning} | Down: ${services.down}\\n\\n`;\n\nif (issues.length > 0) {\n  message += `**Active Issues**\\n`;\n  // Process up to 5 issues for Discord's character limit\n  issues.slice(0, 5).forEach(issue => {\n    // Uses 'resource' to handle both container names and NAS mount points\n    message += `• **${issue.resource || issue.container || 'System'}**\\n`;\n    message += `  ${issue.problem || 'No details provided'}\\n`;\n    if (issue.action_taken && issue.action_taken !== 'None' && issue.action_taken !== 'No action taken - non-critical service') {\n      message += `  └─ *Action: ${issue.action_taken}*\\n\\n`;\n    } else {\n      message += `\\n`;\n    }\n  });\n  \n  if (issues.length > 5) {\n    message += `*...and ${issues.length - 5} more issues logged.*\\n\\n`;\n  }\n}\n\nmessage += `**Notes**: ${data.message || 'All systems operational'}\\n`;\nmessage += `\\n*Sentinel-1 | ${new Date().toLocaleString()}*`;\n\n// Safety truncation for Discord's 2000 character limit\nif (message.length > 1900) {\n  message = message.substring(0, 1900) + '\\n*truncated*';\n}\n\nreturn [{ json: { discord_message: message } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        256,
        -112
      ],
      "id": "37b9f593-7156-4936-8d9d-2399e8d68004",
      "name": "Code in JavaScript",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"Dockers_up\": {\n      \"type\": \"string\",\n      \"description\": \"Format: X/10 running\"\n    },\n    \"NAS_status\": {\n      \"type\": \"string\",\n      \"description\": \"Current health and mount status of the Arcellite NAS\"\n    },\n    \"Alert_level\": {\n      \"type\": \"string\",\n      \"enum\": [\"GREEN\", \"AMBER\", \"RED\"]\n    },\n    \"Services_summary\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"healthy\": { \"type\": \"number\" },\n        \"warning\": { \"type\": \"number\" },\n        \"down\": { \"type\": \"number\" }\n      }\n    },\n    \"Issues\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"resource\": { \n            \"type\": \"string\",\n            \"description\": \"The specific container name or NAS mount point\"\n          },\n          \"problem\": { \"type\": \"string\" },\n          \"action_taken\": { \"type\": \"string\" }\n        }\n      }\n    },\n    \"Send_notification\": { \"type\": \"boolean\" },\n    \"message\": { \"type\": \"string\" }\n  }\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        48,
        112
      ],
      "id": "e7400792-d3a6-4833-955c-dc1b7c2b7192",
      "name": "Structured Output Parser"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "7fd6f3d5-fa8d-4f1a-8b22-3e0ea6dab226",
              "leftValue": "={{ $('AI Agent').item.json.output.Send_notification }}",
              "rightValue": "yes",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        480,
        -112
      ],
      "id": "55ed3a7b-62dc-4721-bce7-7cb72d3ca573",
      "name": "If"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "typeVersion": 1,
      "position": [
        -464,
        112
      ],
      "id": "28010f26-2dfd-4dab-ac26-3fe537bcc8e2",
      "name": "DeepSeek Chat Model",
      "credentials": {
        "deepSeekApi": {
          "id": "4vvWmXwFGh3WF1hx",
          "name": "DeepSeek account"
        }
      }
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "ppvvw1I8wAJ3y2wt",
          "mode": "list",
          "cachedResultUrl": "/workflow/ppvvw1I8wAJ3y2wt",
          "cachedResultName": "Nas"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "command": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('command', ``, 'string') }}"
          },
          "matchingColumns": [
            "command"
          ],
          "schema": [
            {
              "id": "command",
              "displayName": "command",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [
        -208,
        112
      ],
      "id": "594c362e-7eab-4c3b-b061-273c410b408c",
      "name": "Nas"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "0DcqlS8sqwz1EpYw",
          "mode": "list",
          "cachedResultUrl": "/workflow/0DcqlS8sqwz1EpYw",
          "cachedResultName": "Server"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "Command": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Command', ``, 'string') }}"
          },
          "matchingColumns": [
            "Command"
          ],
          "schema": [
            {
              "id": "Command",
              "displayName": "Command",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [
        -80,
        112
      ],
      "id": "9c291c01-62ec-444c-984b-3d31d57ad76e",
      "name": "Arcellite"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $json.chatid }}"
      },
      "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
      "typeVersion": 1.3,
      "position": [
        -336,
        112
      ],
      "id": "78c56728-b91f-4a95-a491-a9b046e789d7",
      "name": "Postgres Chat Memory",
      "credentials": {
        "postgres": {
          "id": "HNHn0jKp6TBUHPRs",
          "name": "Postgres account"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "AI Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DeepSeek Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Nas": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Arcellite": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Postgres Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "versionId": "40fc9b5f-ddc2-4205-8f42-69d7eac4b1c7",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "de60ded3609ed675dd3435a79fde884038d2348f816754f1f978c92a10e2da20"
  },
  "id": "HI3HmS-9VcZ8L92hzUuMr",
  "tags": []
}