feat(backup-monitor): fill credential/datatable IDs and reimport workflows
This commit is contained in:
parent
cb2d6f6525
commit
3b1bfeada1
|
|
@ -44,7 +44,7 @@ receiver = {
|
||||||
webhookId: SecureRandom.uuid,
|
webhookId: SecureRandom.uuid,
|
||||||
credentials: {
|
credentials: {
|
||||||
httpHeaderAuth: {
|
httpHeaderAuth: {
|
||||||
id: 'REPLACE_WITH_BACKUP_CREDENTIAL_ID',
|
id: 'qOYMJr5ENhFJZNDe',
|
||||||
name: 'backup-monitor-token'
|
name: 'backup-monitor-token'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +56,7 @@ receiver = {
|
||||||
dataTableId: {
|
dataTableId: {
|
||||||
'__rl' => true,
|
'__rl' => true,
|
||||||
mode: 'list',
|
mode: 'list',
|
||||||
value: 'REPLACE_WITH_DATATABLE_ID',
|
value: 'cWeJhlybF9eV43DB',
|
||||||
cachedResultName: 'backup_runs'
|
cachedResultName: 'backup_runs'
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
|
@ -157,7 +157,7 @@ reporter = {
|
||||||
dataTableId: {
|
dataTableId: {
|
||||||
'__rl' => true,
|
'__rl' => true,
|
||||||
mode: 'list',
|
mode: 'list',
|
||||||
value: 'REPLACE_WITH_DATATABLE_ID',
|
value: 'cWeJhlybF9eV43DB',
|
||||||
cachedResultName: 'backup_runs'
|
cachedResultName: 'backup_runs'
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
|
|
@ -209,7 +209,7 @@ reporter = {
|
||||||
genericAuthType: 'httpHeaderAuth',
|
genericAuthType: 'httpHeaderAuth',
|
||||||
sendBody: true,
|
sendBody: true,
|
||||||
specifyBody: 'json',
|
specifyBody: 'json',
|
||||||
jsonBody: '={{ { from: "REPLACE_WITH_MAILPACE_FROM_ADDRESS", to: "jaredmswanson@gmail.com", subject: $json.subject, text_body: $json.body } }}',
|
jsonBody: '={{ { from: "backups@swansoncloud.com", to: "jaredmswanson@gmail.com", subject: $json.subject, text_body: $json.body } }}',
|
||||||
options: {}
|
options: {}
|
||||||
},
|
},
|
||||||
id: SecureRandom.uuid,
|
id: SecureRandom.uuid,
|
||||||
|
|
@ -219,7 +219,7 @@ reporter = {
|
||||||
position: [1340, 300],
|
position: [1340, 300],
|
||||||
credentials: {
|
credentials: {
|
||||||
httpHeaderAuth: {
|
httpHeaderAuth: {
|
||||||
id: 'REPLACE_WITH_MAILPACE_CREDENTIAL_ID',
|
id: 'tC1vdhAHGVwAZxcg',
|
||||||
name: 'mailpace-token'
|
name: 'mailpace-token'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"responseMode": "responseNode",
|
"responseMode": "responseNode",
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "336a3720-38df-4431-8218-3574c634a23a",
|
"id": "700da0a0-c2a5-4b9b-839a-10b7d0f37749",
|
||||||
"name": "Webhook",
|
"name": "Webhook",
|
||||||
"type": "n8n-nodes-base.webhook",
|
"type": "n8n-nodes-base.webhook",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
|
|
@ -17,10 +17,10 @@
|
||||||
240,
|
240,
|
||||||
300
|
300
|
||||||
],
|
],
|
||||||
"webhookId": "21ee456d-da9f-4c4a-b9ed-cecf1f082ede",
|
"webhookId": "bdb7bf40-9f87-4483-b58e-5f2f6fa0c8a5",
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"httpHeaderAuth": {
|
"httpHeaderAuth": {
|
||||||
"id": "REPLACE_WITH_BACKUP_CREDENTIAL_ID",
|
"id": "qOYMJr5ENhFJZNDe",
|
||||||
"name": "backup-monitor-token"
|
"name": "backup-monitor-token"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
"dataTableId": {
|
"dataTableId": {
|
||||||
"__rl": true,
|
"__rl": true,
|
||||||
"mode": "list",
|
"mode": "list",
|
||||||
"value": "REPLACE_WITH_DATATABLE_ID",
|
"value": "cWeJhlybF9eV43DB",
|
||||||
"cachedResultName": "backup_runs"
|
"cachedResultName": "backup_runs"
|
||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "4a4d9d99-c1f4-4440-9998-9649afff9fed",
|
"id": "3d251557-1841-466b-9ea6-895af4b6731f",
|
||||||
"name": "Insert to backup_runs",
|
"name": "Insert to backup_runs",
|
||||||
"type": "n8n-nodes-base.dataTable",
|
"type": "n8n-nodes-base.dataTable",
|
||||||
"typeVersion": 1,
|
"typeVersion": 1,
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
"responseCode": 200
|
"responseCode": 200
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "8683674c-4010-4c69-bba7-e79a38454863",
|
"id": "6448fef7-dd0f-4508-b896-4bd7c83b24bf",
|
||||||
"name": "Respond 200",
|
"name": "Respond 200",
|
||||||
"type": "n8n-nodes-base.respondToWebhook",
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
"typeVersion": 1.1,
|
"typeVersion": 1.1,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "2595c8cc-eb3f-43f7-93d6-efdb74576d1b",
|
"id": "ed22d29f-35f9-45ac-a7c7-b97ffa926736",
|
||||||
"name": "Every Friday 8 AM",
|
"name": "Every Friday 8 AM",
|
||||||
"type": "n8n-nodes-base.scheduleTrigger",
|
"type": "n8n-nodes-base.scheduleTrigger",
|
||||||
"typeVersion": 1.2,
|
"typeVersion": 1.2,
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
"assignments": {
|
"assignments": {
|
||||||
"assignments": [
|
"assignments": [
|
||||||
{
|
{
|
||||||
"id": "982fc183-fb43-4846-80c5-93027efdcf80",
|
"id": "212c8f16-8900-4cbf-93bb-4802ad9c3f33",
|
||||||
"name": "sources",
|
"name": "sources",
|
||||||
"value": "={{ [{\"machine\":\"desktop\",\"destination\":\"synology\"},{\"machine\":\"desktop\",\"destination\":\"backblaze-b2\"},{\"machine\":\"vps-ovh-prod-01\",\"destination\":\"local\"}] }}",
|
"value": "={{ [{\"machine\":\"desktop\",\"destination\":\"synology\"},{\"machine\":\"desktop\",\"destination\":\"backblaze-b2\"},{\"machine\":\"vps-ovh-prod-01\",\"destination\":\"local\"}] }}",
|
||||||
"type": "arrayValue"
|
"type": "arrayValue"
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "bb24025c-7a59-44d2-8a48-f468955715e1",
|
"id": "3c3c5660-6075-4f73-9bb1-a7a70c91bfd6",
|
||||||
"name": "Set sources",
|
"name": "Set sources",
|
||||||
"type": "n8n-nodes-base.set",
|
"type": "n8n-nodes-base.set",
|
||||||
"typeVersion": 3.4,
|
"typeVersion": 3.4,
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
"dataTableId": {
|
"dataTableId": {
|
||||||
"__rl": true,
|
"__rl": true,
|
||||||
"mode": "list",
|
"mode": "list",
|
||||||
"value": "REPLACE_WITH_DATATABLE_ID",
|
"value": "cWeJhlybF9eV43DB",
|
||||||
"cachedResultName": "backup_runs"
|
"cachedResultName": "backup_runs"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "fccd484f-0383-4581-a385-8ba3187d8218",
|
"id": "474f055d-08e6-4a47-8a1f-5f18818e7daf",
|
||||||
"name": "Get backup_runs (last 7d)",
|
"name": "Get backup_runs (last 7d)",
|
||||||
"type": "n8n-nodes-base.dataTable",
|
"type": "n8n-nodes-base.dataTable",
|
||||||
"typeVersion": 1,
|
"typeVersion": 1,
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
"language": "javaScript",
|
"language": "javaScript",
|
||||||
"jsCode": "// Runs once for all items. References upstream nodes by exact name.\nconst sources = $('Set sources').first().json.sources;\nconst runs = $('Get backup_runs (last 7d)').all().map(item => item.json);\nconst weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);\n\nconst results = sources.map(source => {\n const sourceRuns = runs.filter(r =>\n r.machine === source.machine &&\n r.destination === source.destination &&\n new Date(r.timestamp) >= weekAgo\n );\n const hasSuccess = sourceRuns.some(r => r.status === 'success');\n const hasAnyRun = sourceRuns.length > 0;\n const status = hasSuccess ? 'green' : hasAnyRun ? 'yellow' : 'red';\n const lastSuccess = sourceRuns\n .filter(r => r.status === 'success')\n .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))[0] || null;\n return { ...source, status, runs: sourceRuns, lastSuccess };\n});\n\nconst overallStatus = results.some(r => r.status === 'red') ? 'red'\n : results.some(r => r.status === 'yellow') ? 'yellow' : 'green';\n\nreturn [{ json: { results, overallStatus } }];\n"
|
"jsCode": "// Runs once for all items. References upstream nodes by exact name.\nconst sources = $('Set sources').first().json.sources;\nconst runs = $('Get backup_runs (last 7d)').all().map(item => item.json);\nconst weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);\n\nconst results = sources.map(source => {\n const sourceRuns = runs.filter(r =>\n r.machine === source.machine &&\n r.destination === source.destination &&\n new Date(r.timestamp) >= weekAgo\n );\n const hasSuccess = sourceRuns.some(r => r.status === 'success');\n const hasAnyRun = sourceRuns.length > 0;\n const status = hasSuccess ? 'green' : hasAnyRun ? 'yellow' : 'red';\n const lastSuccess = sourceRuns\n .filter(r => r.status === 'success')\n .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))[0] || null;\n return { ...source, status, runs: sourceRuns, lastSuccess };\n});\n\nconst overallStatus = results.some(r => r.status === 'red') ? 'red'\n : results.some(r => r.status === 'yellow') ? 'yellow' : 'green';\n\nreturn [{ json: { results, overallStatus } }];\n"
|
||||||
},
|
},
|
||||||
"id": "6786e632-14ed-4a58-ae22-5b4ffcaf4cc5",
|
"id": "4211f3b0-1878-4979-a5be-eb01bdec2d63",
|
||||||
"name": "Evaluate Sources",
|
"name": "Evaluate Sources",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
"language": "javaScript",
|
"language": "javaScript",
|
||||||
"jsCode": "// Builds subject + body from Evaluate Sources output.\n// Uses an array of lines joined at the end to avoid \\n escape issues in JSON storage.\nconst { results, overallStatus } = $input.first().json;\nconst icon = { green: '✓', yellow: '⚠', red: '✗' };\nconst label = { green: 'Backups healthy', yellow: 'Backup warning', red: 'Backup failure' };\nconst weekOf = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\nconst subject = icon[overallStatus] + ' ' + label[overallStatus] + ' — week of ' + weekOf;\n\nconst fmt = ts => ts\n ? new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })\n : 'unknown';\n\nconst L = [];\n\nif (overallStatus === 'green') {\n L.push('All backup sources ran successfully this week.');\n L.push('');\n results.forEach(r =>\n L.push('✓ ' + r.machine + ' / ' + r.destination +\n ' (last success: ' + fmt(r.lastSuccess && r.lastSuccess.timestamp) + ')')\n );\n} else {\n const problems = results.filter(r => r.status !== 'green');\n const healthy = results.filter(r => r.status === 'green');\n\n L.push(problems.length + ' source(s) need attention:');\n L.push('');\n\n problems.forEach(r => {\n L.push(icon[r.status] + ' ' + r.machine + ' / ' + r.destination);\n\n if (r.status === 'yellow') {\n L.push(' Ran this week but never succeeded.');\n r.runs.slice(0, 3).forEach(run => {\n var line = ' ' + fmt(run.timestamp) + ' [' + run.status + ']';\n if (run.log_path) line += ' log: ' + run.log_path;\n L.push(line);\n if (run.notes) L.push(' notes: ' + (run.notes || '').slice(0, 300));\n });\n L.push('');\n L.push(' Next steps:');\n L.push(' 1. Check the log at the path shown above');\n L.push(' 2. For Synology failures: verify LAN reachability (ssh synology) — disks need ~30s to spin up');\n L.push(' 3. Re-run manually: bash ~/.local/bin/backup.sh');\n L.push(' 4. One-off skip (power outage, travel) is expected — yellow is a heads-up, not an alarm');\n L.push('');\n }\n\n if (r.status === 'red') {\n L.push(' No runs reported all week — silent failure.');\n if (r.lastSuccess) {\n L.push(' Last known good run: ' + fmt(r.lastSuccess.timestamp));\n if (r.lastSuccess.log_path) L.push(' Last log: ' + r.lastSuccess.log_path);\n } else {\n L.push(' No history found in the datatable.');\n }\n L.push('');\n L.push(' Next steps:');\n L.push(' 1. Check systemd timer: systemctl --user status restic-backup.timer');\n L.push(' 2. Verify backup.sh calls notify_backup.rb at the end');\n L.push(' 3. Check n8n Workflow 1 execution log for webhook errors');\n L.push(' 4. Run manually: bash ~/.local/bin/backup.sh');\n L.push('');\n }\n });\n\n if (healthy.length > 0) {\n L.push('Healthy sources:');\n healthy.forEach(r => L.push('✓ ' + r.machine + ' / ' + r.destination));\n }\n}\n\nreturn [{ json: { subject, body: L.join('\\n') } }];\n"
|
"jsCode": "// Builds subject + body from Evaluate Sources output.\n// Uses an array of lines joined at the end to avoid \\n escape issues in JSON storage.\nconst { results, overallStatus } = $input.first().json;\nconst icon = { green: '✓', yellow: '⚠', red: '✗' };\nconst label = { green: 'Backups healthy', yellow: 'Backup warning', red: 'Backup failure' };\nconst weekOf = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\nconst subject = icon[overallStatus] + ' ' + label[overallStatus] + ' — week of ' + weekOf;\n\nconst fmt = ts => ts\n ? new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })\n : 'unknown';\n\nconst L = [];\n\nif (overallStatus === 'green') {\n L.push('All backup sources ran successfully this week.');\n L.push('');\n results.forEach(r =>\n L.push('✓ ' + r.machine + ' / ' + r.destination +\n ' (last success: ' + fmt(r.lastSuccess && r.lastSuccess.timestamp) + ')')\n );\n} else {\n const problems = results.filter(r => r.status !== 'green');\n const healthy = results.filter(r => r.status === 'green');\n\n L.push(problems.length + ' source(s) need attention:');\n L.push('');\n\n problems.forEach(r => {\n L.push(icon[r.status] + ' ' + r.machine + ' / ' + r.destination);\n\n if (r.status === 'yellow') {\n L.push(' Ran this week but never succeeded.');\n r.runs.slice(0, 3).forEach(run => {\n var line = ' ' + fmt(run.timestamp) + ' [' + run.status + ']';\n if (run.log_path) line += ' log: ' + run.log_path;\n L.push(line);\n if (run.notes) L.push(' notes: ' + (run.notes || '').slice(0, 300));\n });\n L.push('');\n L.push(' Next steps:');\n L.push(' 1. Check the log at the path shown above');\n L.push(' 2. For Synology failures: verify LAN reachability (ssh synology) — disks need ~30s to spin up');\n L.push(' 3. Re-run manually: bash ~/.local/bin/backup.sh');\n L.push(' 4. One-off skip (power outage, travel) is expected — yellow is a heads-up, not an alarm');\n L.push('');\n }\n\n if (r.status === 'red') {\n L.push(' No runs reported all week — silent failure.');\n if (r.lastSuccess) {\n L.push(' Last known good run: ' + fmt(r.lastSuccess.timestamp));\n if (r.lastSuccess.log_path) L.push(' Last log: ' + r.lastSuccess.log_path);\n } else {\n L.push(' No history found in the datatable.');\n }\n L.push('');\n L.push(' Next steps:');\n L.push(' 1. Check systemd timer: systemctl --user status restic-backup.timer');\n L.push(' 2. Verify backup.sh calls notify_backup.rb at the end');\n L.push(' 3. Check n8n Workflow 1 execution log for webhook errors');\n L.push(' 4. Run manually: bash ~/.local/bin/backup.sh');\n L.push('');\n }\n });\n\n if (healthy.length > 0) {\n L.push('Healthy sources:');\n healthy.forEach(r => L.push('✓ ' + r.machine + ' / ' + r.destination));\n }\n}\n\nreturn [{ json: { subject, body: L.join('\\n') } }];\n"
|
||||||
},
|
},
|
||||||
"id": "09476b39-8adb-490b-b667-8b2e36bb141c",
|
"id": "99ace456-148e-493b-af7d-174ea62c1448",
|
||||||
"name": "Build Email",
|
"name": "Build Email",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
|
|
@ -119,10 +119,10 @@
|
||||||
"genericAuthType": "httpHeaderAuth",
|
"genericAuthType": "httpHeaderAuth",
|
||||||
"sendBody": true,
|
"sendBody": true,
|
||||||
"specifyBody": "json",
|
"specifyBody": "json",
|
||||||
"jsonBody": "={{ { from: \"REPLACE_WITH_MAILPACE_FROM_ADDRESS\", to: \"jaredmswanson@gmail.com\", subject: $json.subject, text_body: $json.body } }}",
|
"jsonBody": "={{ { from: \"backups@swansoncloud.com\", to: \"jaredmswanson@gmail.com\", subject: $json.subject, text_body: $json.body } }}",
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "ecb35506-d3cf-4d3b-b055-fd04d695446a",
|
"id": "cab8c0f5-8ebb-468f-ac83-8032c1020913",
|
||||||
"name": "Send via MailPace",
|
"name": "Send via MailPace",
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
],
|
],
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"httpHeaderAuth": {
|
"httpHeaderAuth": {
|
||||||
"id": "REPLACE_WITH_MAILPACE_CREDENTIAL_ID",
|
"id": "tC1vdhAHGVwAZxcg",
|
||||||
"name": "mailpace-token"
|
"name": "mailpace-token"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue