{ "name": "NFS Mount Watchdog + Auto-Heal v1.1", "active": true, "isArchived": false, "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 5 } ] } }, "id": "8c466a67-a0fa-42dc-8ed8-0918834fc3a7", "name": "Every 5 Minutes", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [ 0, 0 ] }, { "parameters": { "command": "mountpoint -q /mnt/unraid/data/media && echo MEDIA_OK || echo MEDIA_MISSING; mountpoint -q /mnt/unraid/data/photos && echo PHOTOS_OK || echo PHOTOS_MISSING" }, "id": "f5b2fee9-d67a-4da7-80dd-f72e6078aed9", "name": "Probe Mounts", "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [ 224, 0 ], "credentials": { "sshPrivateKey": { "id": "SETUP_REQUIRED", "name": "PD SSH" } }, "continueOnFail": true }, { "parameters": { "jsCode": "\nconst out = ($input.first().json.stdout || '') + ($input.first().json.stderr || '');\nconst mediaOk = out.includes('MEDIA_OK');\nconst photosOk = out.includes('PHOTOS_OK');\nconst missing = [];\nif (!mediaOk) missing.push({ mount: '/mnt/unraid/data/media', label: 'media',\n containers: ['plex','audiobookshelf'] });\nif (!photosOk) missing.push({ mount: '/mnt/unraid/data/photos', label: 'photos',\n containers: ['immich-server','immich-microservices','immich-machine-learning'] });\nreturn [{ json: { mediaOk, photosOk, anyMissing: missing.length > 0, anyMissingStr: missing.length > 0 ? 'yes' : 'no', missing } }];\n" }, "id": "25bb3239-49cb-4570-b706-4022b9aa751c", "name": "Parse Mount Status", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 448, 0 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }, "conditions": [ { "id": "70384f84-9d15-4e45-b295-67202a39a227", "leftValue": "={{ $json.anyMissingStr }}", "rightValue": "yes", "operator": { "type": "string", "operation": "equals" } } ], "combinator": "and" } }, "id": "68b74832-85a7-4193-9382-a0c22a666e66", "name": "Any Missing?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 672, 0 ] }, { "parameters": { "command": "sudo bash /mnt/tank/docker/scripts/mount-unraid-nfs.sh 2>&1" }, "id": "b18c7537-0c2f-4122-bfe5-6469d6be6853", "name": "Run Mount Script", "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [ 896, -120 ], "credentials": { "sshPrivateKey": { "id": "SETUP_REQUIRED", "name": "PD SSH" } }, "continueOnFail": true }, { "parameters": { "resume": "timeInterval", "unit": "seconds", "value": 10 }, "id": "2e927f83-9ddc-4816-8ee6-76d21ce21a5e", "name": "Wait 10s", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ 1120, -120 ], "webhookId": "4e89e8d0-7f08-4378-a2b7-63756665b039" }, { "parameters": { "command": "mountpoint -q /mnt/unraid/data/media && echo MEDIA_OK || echo MEDIA_MISSING; mountpoint -q /mnt/unraid/data/photos && echo PHOTOS_OK || echo PHOTOS_MISSING" }, "id": "d15c0c90-e94f-404a-a787-75f1b1b5befa", "name": "Re-probe Mounts", "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [ 1344, -120 ], "credentials": { "sshPrivateKey": { "id": "SETUP_REQUIRED", "name": "PD SSH" } }, "continueOnFail": true }, { "parameters": { "jsCode": "\nconst out = ($input.first().json.stdout || '') + ($input.first().json.stderr || '');\nconst mediaOk = out.includes('MEDIA_OK');\nconst photosOk = out.includes('PHOTOS_OK');\n\n// What was originally missing (from step 3)\nconst originalMissing = $('Parse Mount Status').first().json.missing || [];\n\n// Which of the originally-missing mounts are now up?\nconst healed = originalMissing.filter(m =>\n (m.label === 'media' && mediaOk) ||\n (m.label === 'photos' && photosOk)\n);\nconst stillDown = originalMissing.filter(m =>\n (m.label === 'media' && !mediaOk) ||\n (m.label === 'photos' && !photosOk)\n);\n\n// Build flat container list to restart (only for healed mounts)\nconst containersToRestart = [...new Set(healed.flatMap(m => m.containers))];\nconst restartCmd = containersToRestart.length > 0\n ? `sudo docker restart ${containersToRestart.join(' ')}`\n : 'echo NO_RESTART_NEEDED';\n\nreturn [{ json: {\n allHealed: stillDown.length === 0, allHealedStr: stillDown.length === 0 ? 'yes' : 'no',\n healed, stillDown,\n containersToRestart,\n restartCmd,\n originalMissing\n}}];\n" }, "id": "48de38c2-df08-406f-ad61-809ca6e306a1", "name": "Parse Heal Result", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1568, -120 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }, "conditions": [ { "id": "32b2dfd5-9958-45d4-94fc-410ce4d9543d", "leftValue": "={{ $json.allHealedStr }}", "rightValue": "yes", "operator": { "type": "string", "operation": "equals" } } ], "combinator": "and" } }, "id": "29a1b544-97b3-433f-9821-2bf5a8af95d2", "name": "Healed?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 1792, -120 ] }, { "parameters": { "command": "={{ $json.restartCmd }}" }, "id": "8d791509-9e18-44c1-bf58-ed3027927710", "name": "Restart Containers", "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [ 2016, -240 ], "credentials": { "sshPrivateKey": { "id": "SETUP_REQUIRED", "name": "PD SSH" } }, "continueOnFail": true }, { "parameters": { "jsCode": "\nconst d = $('Parse Heal Result').first().json;\nconst mountList = d.healed.map(m => ` \u2022 ${m.mount}`).join('\\n');\nconst containers = d.containersToRestart.join(', ') || 'none';\nconst body = `NFS mounts were missing and have been automatically remounted.\\n\\nRemounted:\\n${mountList}\\n\\nContainers restarted: ${containers}`;\nreturn [{ json: { title: '\ud83d\udd17 NFS Auto-Healed', message: body, priority: 5 } }];\n" }, "id": "75c8976f-d622-43cd-a332-72c32f0c7c60", "name": "Format Healed Notify", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2240, -240 ] }, { "parameters": { "message": "={{ $json.message }}", "additionalFields": { "priority": "={{ $json.priority }}", "title": "={{ $json.title }}" }, "options": { "contentType": "text/plain" } }, "id": "38705cd2-775b-4ef9-b470-4a7709f132e5", "name": "Gotify (Healed)", "type": "n8n-nodes-base.gotify", "typeVersion": 1, "position": [ 2464, -240 ], "credentials": { "gotifyApi": { "id": "SETUP_REQUIRED", "name": "Uptime / Infra" } } }, { "parameters": { "jsCode": "\nconst d = $('Parse Heal Result').first().json;\nconst downList = d.stillDown.map(m => ` \u2022 ${m.mount}`).join('\\n');\nconst healedList = d.healed.length > 0\n ? `\\nPartially healed:\\n${d.healed.map(m => ' \u2022 ' + m.mount).join('\\n')}`\n : '';\nconst body = `NFS mount script ran but the following paths are still unavailable. Manual intervention required.\\n\\nStill down:\\n${downList}${healedList}\\n\\nCheck Serenity (TrueNAS) is up and NFS service is running.`;\nreturn [{ json: { title: '\ud83d\udea8 NFS Down \u2014 Manual Fix Needed', message: body, priority: 9 } }];\n" }, "id": "074b984f-7fc2-45bc-b97f-f5db4d8d589b", "name": "Format Escalation", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2016, -20 ] }, { "parameters": { "message": "={{ $json.message }}", "additionalFields": { "priority": "={{ $json.priority }}", "title": "={{ $json.title }}" }, "options": { "contentType": "text/plain" } }, "id": "f929dd77-6072-4ac9-9dc4-11246110efed", "name": "Gotify (Escalation)", "type": "n8n-nodes-base.gotify", "typeVersion": 1, "position": [ 2240, -20 ], "credentials": { "gotifyApi": { "id": "SETUP_REQUIRED", "name": "Uptime / Infra" } } } ], "connections": { "Every 5 Minutes": { "main": [ [ { "node": "Probe Mounts", "type": "main", "index": 0 } ] ] }, "Probe Mounts": { "main": [ [ { "node": "Parse Mount Status", "type": "main", "index": 0 } ] ] }, "Parse Mount Status": { "main": [ [ { "node": "Any Missing?", "type": "main", "index": 0 } ] ] }, "Any Missing?": { "main": [ [ { "node": "Run Mount Script", "type": "main", "index": 0 } ], [] ] }, "Run Mount Script": { "main": [ [ { "node": "Wait 10s", "type": "main", "index": 0 } ] ] }, "Wait 10s": { "main": [ [ { "node": "Re-probe Mounts", "type": "main", "index": 0 } ] ] }, "Re-probe Mounts": { "main": [ [ { "node": "Parse Heal Result", "type": "main", "index": 0 } ] ] }, "Parse Heal Result": { "main": [ [ { "node": "Healed?", "type": "main", "index": 0 } ] ] }, "Healed?": { "main": [ [ { "node": "Restart Containers", "type": "main", "index": 0 } ], [ { "node": "Format Escalation", "type": "main", "index": 0 } ] ] }, "Restart Containers": { "main": [ [ { "node": "Format Healed Notify", "type": "main", "index": 0 } ] ] }, "Format Healed Notify": { "main": [ [ { "node": "Gotify (Healed)", "type": "main", "index": 0 } ] ] }, "Format Escalation": { "main": [ [ { "node": "Gotify (Escalation)", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "binaryMode": "separate" }, "staticData": null, "meta": null, "pinData": {}, "tags": [ { "name": "monitoring" }, { "name": "infrastructure" }, { "name": "scheduled" } ], "id": "8587d430-3d53-4559-b610-214e12164c14" }