From 73105c77944f25699926d3cbea5834c977badad9 Mon Sep 17 00:00:00 2001 From: Fizzlepoof Date: Mon, 25 May 2026 02:41:07 +0000 Subject: [PATCH] n8n: add calibre-web to nfs watchdog --- n8n-workflows/16-nfs-watchdog-v1.3.json | 495 ++++++++++++++++++++++++ n8n-workflows/README.md | 4 +- n8n-workflows/TESTING-STATUS.md | 4 +- 3 files changed, 499 insertions(+), 4 deletions(-) create mode 100644 n8n-workflows/16-nfs-watchdog-v1.3.json diff --git a/n8n-workflows/16-nfs-watchdog-v1.3.json b/n8n-workflows/16-nfs-watchdog-v1.3.json new file mode 100644 index 0000000..6a921ec --- /dev/null +++ b/n8n-workflows/16-nfs-watchdog-v1.3.json @@ -0,0 +1,495 @@ +[ + { + "updatedAt": "2026-05-25T02:38:06.723Z", + "createdAt": "2026-05-09T20:46:23.974Z", + "id": "vdv7fILmnhQd9qL2", + "name": "NFS Mount Watchdog + Auto-Heal v1.3", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "minutes" + } + ] + } + }, + "id": "d902559a-9099-4437-acf8-251ddc822dd8", + "name": "Every 5 Minutes", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + -2016, + 240 + ] + }, + { + "parameters": { + "command": "mountpoint -q /mnt/unraid/data && echo DATA_OK || echo DATA_MISSING; mountpoint -q /mnt/unraid/immich && echo IMMICH_OK || echo IMMICH_MISSING" + }, + "id": "81396ba5-a882-45c2-82f9-7838ce420d7f", + "name": "Probe Mounts", + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + -1792, + 240 + ], + "credentials": { + "sshPassword": { + "id": "0irY4e4WHwyMXKhA", + "name": "SSH Password account" + } + }, + "continueOnFail": true + }, + { + "parameters": { + "jsCode": "const out = ($input.first().json.stdout || '') + ($input.first().json.stderr || '');\nconst dataOk = out.includes('DATA_OK');\nconst immichOk = out.includes('IMMICH_OK');\nconst missing = [];\nif (!dataOk) missing.push({ mount: '/mnt/unraid/data', label: 'data',\n containers: ['ix-plex-plex-1', 'ix-audiobookshelf-audiobookshelf-1', 'calibre-web-automated'] });\nif (!immichOk) missing.push({ mount: '/mnt/unraid/immich', label: 'immich',\n containers: ['ix-immich-immich-server-1', 'ix-immich-immich-machine-learning-1'] });\nreturn [{ json: { dataOk, immichOk, anyMissing: missing.length > 0, anyMissingStr: missing.length > 0 ? 'yes' : 'no', missing } }];\n" + }, + "id": "cbc19d0e-ca0c-41a8-8db6-a5f9754d68bf", + "name": "Parse Mount Status", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -1568, + 240 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 1 + }, + "conditions": [ + { + "id": "70384f84-9d15-4e45-b295-67202a39a227", + "leftValue": "={{ $json.anyMissingStr }}", + "rightValue": "yes", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "6c0f9f30-a2d3-4f85-a17d-99b215178652", + "name": "Any Missing?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + -1344, + 240 + ] + }, + { + "parameters": { + "command": "sudo /usr/bin/bash /mnt/tank/docker/scripts/mount-unraid-nfs.sh" + }, + "id": "764c3d6c-f5fb-4ce7-9291-20718ad3b598", + "name": "Run Mount Script", + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + -1120, + 128 + ], + "credentials": { + "sshPassword": { + "id": "0irY4e4WHwyMXKhA", + "name": "SSH Password account" + } + }, + "continueOnFail": true + }, + { + "parameters": {}, + "id": "1510ac5b-873f-46d8-b86b-071ab0d6ca26", + "name": "Wait 10s", + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + -896, + 128 + ], + "webhookId": "4e89e8d0-7f08-4378-a2b7-63756665b039" + }, + { + "parameters": { + "command": "mountpoint -q /mnt/unraid/data && echo DATA_OK || echo DATA_MISSING; mountpoint -q /mnt/unraid/immich && echo IMMICH_OK || echo IMMICH_MISSING" + }, + "id": "4c4bbe24-d496-435e-ba81-02a91f1fb0b7", + "name": "Re-probe Mounts", + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + -672, + 128 + ], + "credentials": { + "sshPassword": { + "id": "0irY4e4WHwyMXKhA", + "name": "SSH Password account" + } + }, + "continueOnFail": true + }, + { + "parameters": { + "jsCode": "const out = ($input.first().json.stdout || '') + ($input.first().json.stderr || '');\nconst dataOk = out.includes('DATA_OK');\nconst immichOk = out.includes('IMMICH_OK');\n\nconst originalMissing = $('Parse Mount Status').first().json.missing || [];\n\nconst healed = originalMissing.filter(m =>\n (m.label === 'data' && dataOk) ||\n (m.label === 'immich' && immichOk)\n);\nconst stillDown = originalMissing.filter(m =>\n (m.label === 'data' && !dataOk) ||\n (m.label === 'immich' && !immichOk)\n);\n\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,\n allHealedStr: stillDown.length === 0 ? 'yes' : 'no',\n healed,\n stillDown,\n containersToRestart,\n restartCmd,\n originalMissing\n} }];\n" + }, + "id": "66a1d989-f0a2-4b5a-b7e0-1cb767d010e6", + "name": "Parse Heal Result", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -448, + 128 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 1 + }, + "conditions": [ + { + "id": "32b2dfd5-9958-45d4-94fc-410ce4d9543d", + "leftValue": "={{ $json.allHealedStr }}", + "rightValue": "yes", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "49d9ff7a-9227-405d-9887-a7537ce2b844", + "name": "Healed?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "position": [ + -224, + 128 + ] + }, + { + "parameters": { + "command": "={{ $json.restartCmd }}" + }, + "id": "69951482-d5e3-400c-9d3a-3790f671c225", + "name": "Restart Containers", + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + 0, + 0 + ], + "credentials": { + "sshPassword": { + "id": "0irY4e4WHwyMXKhA", + "name": "SSH Password account" + } + }, + "continueOnFail": true + }, + { + "parameters": { + "jsCode": "const d = $('Parse Heal Result').first().json;\nconst mountList = d.healed.map(m => ` • ${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: '🔗 NFS Auto-Healed', message: body, priority: 5 } }];\n" + }, + "id": "c109df43-532a-4a49-af80-4897bb0b7c1a", + "name": "Format Healed Notify", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 224, + 0 + ] + }, + { + "parameters": { + "message": "={{ $json.message }}", + "additionalFields": { + "priority": "={{ $json.priority }}", + "title": "={{ $json.title }}" + }, + "options": { + "contentType": "text/plain" + } + }, + "id": "c8414235-8840-4302-a8e4-6644c3beadb9", + "name": "Gotify (Healed)", + "type": "n8n-nodes-base.gotify", + "typeVersion": 1, + "position": [ + 448, + 0 + ], + "credentials": { + "gotifyApi": { + "id": "K4p3mFrQrjDutIOU", + "name": "Infrastructure" + } + } + }, + { + "parameters": { + "jsCode": "const d = $('Parse Heal Result').first().json;\nconst downList = d.stillDown.map(m => ` • ${m.mount}`).join('\\n');\nconst healedList = d.healed.length > 0\n ? `\\nPartially healed:\\n${d.healed.map(m => ' • ' + 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 (10.5.30.5) is up and NFS service is running.`;\nreturn [{ json: { title: '🚨 NFS Down — Manual Fix Needed', message: body, priority: 9 } }];\n" + }, + "id": "ff65b3f1-ff81-48dd-a1e8-fd8adcef55d1", + "name": "Format Escalation", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 0, + 224 + ] + }, + { + "parameters": { + "message": "={{ $json.message }}", + "additionalFields": { + "priority": "={{ $json.priority }}", + "title": "={{ $json.title }}" + }, + "options": { + "contentType": "text/plain" + } + }, + "id": "39d131f2-f933-4253-92ce-9e312f881b16", + "name": "Gotify (Escalation)", + "type": "n8n-nodes-base.gotify", + "typeVersion": 1, + "position": [ + 224, + 224 + ], + "credentials": { + "gotifyApi": { + "id": "K4p3mFrQrjDutIOU", + "name": "Infrastructure" + } + } + } + ], + "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": { + "node:Every 5 Minutes": { + "recurrenceRules": [] + } + }, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": {}, + "versionId": "68cca20d-5a60-446f-863f-abef2a12639f", + "activeVersionId": "68cca20d-5a60-446f-863f-abef2a12639f", + "versionCounter": 45, + "triggerCount": 1, + "tags": [ + { + "updatedAt": "2026-05-06T22:38:51.660Z", + "createdAt": "2026-05-06T22:38:51.660Z", + "id": "0ETpkL5jJ5wwgt8k", + "name": "monitoring" + }, + { + "updatedAt": "2026-05-09T18:01:37.295Z", + "createdAt": "2026-05-09T18:01:37.295Z", + "id": "PYzt74cSdu6cC6P1", + "name": "scheduled" + }, + { + "updatedAt": "2026-05-09T18:06:13.298Z", + "createdAt": "2026-05-09T18:06:13.298Z", + "id": "urlD2YH1LZ32LY2d", + "name": "infrastructure" + } + ], + "shared": [ + { + "updatedAt": "2026-05-09T20:46:23.974Z", + "createdAt": "2026-05-09T20:46:23.974Z", + "role": "workflow:owner", + "workflowId": "vdv7fILmnhQd9qL2", + "projectId": "dxCRnBdX5uJizCGa", + "project": { + "updatedAt": "2026-05-06T01:10:37.484Z", + "createdAt": "2026-05-06T01:08:07.721Z", + "id": "dxCRnBdX5uJizCGa", + "name": "Wilfred Fizzlepoof ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "47b2227f-2fc2-4a08-bd35-ea157b92df0d" + } + } + ], + "versionMetadata": { + "name": "NFS Mount Watchdog + Auto-Heal v1.3", + "description": null + } + } +] \ No newline at end of file diff --git a/n8n-workflows/README.md b/n8n-workflows/README.md index 1eaadff..613c8de 100644 --- a/n8n-workflows/README.md +++ b/n8n-workflows/README.md @@ -15,7 +15,7 @@ Active workflows after cleanup: - Class Transcript Ingest v1.1 - Paperless Inbox Reminder - n8n Self-Health Monitor -- NFS Mount Watchdog + Auto-Heal v1.2 +- NFS Mount Watchdog + Auto-Heal v1.3 - Paperless Intake Triage → Doris Review Queue - Telegram School Intake → Postgres → Paperless v1.2 - Paperless School Intake Metadata Enrichment v1.1 @@ -36,7 +36,7 @@ Operational note: the live `Paperless Inbox Reminder` workflow needed its auth h | 08 | Class Recording → RAG | `POST /webhook/class/upload` | Transcribes uploaded .wav, extracts key notes, ingests into RAG by class, saves notes to Paperless, notifies via Gotify | | 14 | Paperless Inbox Reminder | Scheduled (Monday 9 AM) | Queries Paperless for untagged documents; sends Gotify reminder with oldest items listed, or a ✅ clear if inbox is empty | | 15 | n8n Self-Health Monitor | Scheduled (every 15 min) | Checks n8n execution history for failures in the past hour; deduplicates via Postgres; pushes Gotify alert with workflow name and error summary | -| 16 | NFS Mount Watchdog | Scheduled (every 5 min) | Probes NFS mount points on PD; if missing: runs mount script, re-probes, restarts affected containers (Plex, Audiobookshelf, Immich); notifies via Gotify on heal or escalation | +| 16 | NFS Mount Watchdog | Scheduled (every 5 min) | Probes NFS mount points on PD; if missing: runs mount script, re-probes, restarts affected containers (Plex, Audiobookshelf, Calibre-Web, Immich); notifies via Gotify on heal or escalation | | 17 | Paperless Intake Triage → Doris Review Queue | `POST /webhook/paperless/intake-triage` | Fetches a Paperless document by `document_id`, runs conservative AI triage, writes a Doris Dashboard review queue JSON file, and keeps safe auto-apply disabled unless explicitly enabled | | 18 | Telegram School Intake → Postgres → Paperless v1.2 | `POST /webhook/school/intake/upload` | Accepts multipart schoolwork uploads plus class/assignment metadata, uploads into Paperless with deterministic `intake_id` in the filename/title, and returns the resulting task/document context | | 19 | Paperless School Intake Metadata Enrichment v1.1 | `POST /webhook/school/intake/paperless-enrich` | Handles Paperless post-consumption webhooks, resolves metadata from deterministic filename/title, then applies deterministic title/tags and optional correspondent mapping | diff --git a/n8n-workflows/TESTING-STATUS.md b/n8n-workflows/TESTING-STATUS.md index d7aafb1..16e1932 100644 --- a/n8n-workflows/TESTING-STATUS.md +++ b/n8n-workflows/TESTING-STATUS.md @@ -1,5 +1,5 @@ # n8n Workflow Testing Status -_Last updated: 2026-05-18_ +_Last updated: 2026-05-25_ --- @@ -12,7 +12,7 @@ _Last updated: 2026-05-18_ | 09 | Class Transcript Ingest | v1.1 | Gotify confirmed | | 14 | Paperless Inbox Reminder | current live alias + v1.2 artifact | Live auth header corrected again on 2026-05-18; repo artifact already matched `Token` auth | | 15 | n8n Health Monitor | v1.2 | Fixed: workflow names (was showing IDs); Postgres dedup working | -| 16 | NFS Watchdog + Auto-Heal | v1.2 | Confirmed working | +| 16 | NFS Watchdog + Auto-Heal | v1.3 | Confirmed working; added calibre-web-automated to the /mnt/unraid/data recovery restart set | | 07 | Git Commit → AI Summary → Gotify | v1.0 | Confirmed working | ## ⏸ Retired