The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"updatedAt": "2026-06-06T13:13:51.709Z",
"createdAt": "2026-05-12T12:45:59.814Z",
"id": "mlARU7JA0uMUNCeu",
"name": "HomeLab",
"active": true,
"isArchived": false,
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
272,
96
],
"id": "3a3ce204-dc48-4372-9575-1578da7093ea",
"name": "Schedule Trigger"
},
{
"parameters": {
"authentication": "webhook",
"content": "={{ $json.message }}",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [
944,
96
],
"id": "b90830c9-3e22-463a-b046-cd8804094b7f",
"name": "Discord",
"credentials": {
"discordWebhookApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const output = $('update system').first().json.stdout\nlet message = \"\";\nlet status = \"\u2705\";\nconst logSnippet = output.slice(-1000);\nconst userId = \"664803448629493816\"; // Ganti dengan user Discord yang ingin ditag\nconst mention = `<@${userId}>`;\n\nif (output.toLowerCase().includes(\"error\") || output.toLowerCase().includes(\"failed\")) {\n status = \"\u274c\";\n message = `\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n\u274c Update failed!\\n${mention}`;\n} else if (output.toLowerCase().includes(\"a reboot is required\")) {\n status = \"\ud83d\udd04\";\n message = `\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n\ud83d\udd04 Update completed but reboot is required.\\n${mention}`;\n} else {\n status = \"\u2705\";\n message = `\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n\u2705 System updated successfully!`;\n}\n\nreturn [{\n json: {\n status,\n message\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
96
],
"id": "23da27bb-e451-49dd-8b35-889832b37161",
"name": "Code"
},
{
"parameters": {
"authentication": "webhook",
"content": "={{ $json.content }}\n-----------------------------------------",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [
944,
736
],
"id": "cd86d97a-7f43-44b7-83da-676664693848",
"name": "Discord1",
"credentials": {
"discordWebhookApi": {
"name": "<your credential>"
}
},
"disabled": true
},
{
"parameters": {
"jsCode": "const rawLog = $input.first().json.stdout; // log input dari node sebelumnya\n\nconst updatedImages = [];\nconst failedImages = [];\n\nconst lines = rawLog.split(\"\\n\");\n\nfor (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n // Cek baris \"Pulling image\"\n if (line.startsWith(\"[*] Pulling image:\")) {\n const image = line.replace(\"[*] Pulling image: \", \"\").trim();\n const statusLine = lines[i + 3] || \"\"; // Baris ke-3 setelah ini biasanya status\n const errorLine = lines[i + 1] || \"\"; // Jika error, muncul setelah ini\n\n if (statusLine.includes(\"Image is up to date\")) {\n // Lewatkan (tidak perlu dicatat)\n } else if (errorLine.includes(\"Error response from daemon\")) {\n failedImages.push(`${image} \u274c`);\n } else {\n updatedImages.push(`${image} \u2705`);\n }\n }\n}\n\n// Format pesan\nconst startedAt = rawLog.match(/Started at (.+?) ===/i)?.[1] || \"Unknown\";\nconst completedAt = rawLog.match(/Completed at (.+?) ===/i)?.[1] || \"Unknown\";\n\nlet message = `\ud83d\udee0\ufe0f **Docker Auto Update Summary**\\n\\n`;\nmessage += `\ud83d\udd53 **Started**: ${startedAt}\\n\u2705 **Completed**: ${completedAt}\\n\\n`;\n\nif (updatedImages.length > 0) {\n message += `\ud83d\udd04 **Updated Images:**\\n${updatedImages.map(i => `\u2022 ${i}`).join(\"\\n\")}\\n\\n`;\n}\n\nif (failedImages.length > 0) {\n message += `\u2757 **Failed Pulls:**\\n${failedImages.map(i => `\u2022 ${i}`).join(\"\\n\")}\\n\\n`;\n}\n\nif (updatedImages.length === 0 && failedImages.length === 0) {\n message += `\ud83d\udce6 All containers already up to date.\\n`;\n}\n\nreturn [\n {\n json: {\n content: message // Output ke Discord node\n }\n }\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
736
],
"id": "febfead7-8bc1-4430-98bb-080a8d3d92d7",
"name": "Code1",
"disabled": true
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 3
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
272,
736
],
"id": "4576a847-01cf-47d1-a18f-bfdffc4fff76",
"name": "Schedule Trigger1",
"disabled": true
},
{
"parameters": {
"command": "sudo nala update && sudo nala full-upgrade -y"
},
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
"position": [
496,
96
],
"id": "03f12906-1b79-4238-822a-2fdf8acaf085",
"name": "update system",
"credentials": {
"sshPassword": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"command": "sudo ./autoupdate.sh",
"cwd": "/home/n8nbot/dockerUpdate"
},
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
"position": [
496,
736
],
"id": "4306cfba-5e46-4c77-a06e-f8abe4079c64",
"name": "update docker",
"credentials": {
"sshPassword": {
"name": "<your credential>"
}
},
"disabled": true
},
{
"parameters": {
"content": "# OS Update\n",
"height": 80,
"width": 208
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
240,
-16
],
"id": "c8667314-4492-41ee-a887-24a31759300e",
"name": "Sticky Note"
},
{
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
272,
384
],
"id": "a5eb5164-cc62-475a-a7f6-01cb5b3a8e24",
"name": "Schedule Trigger2"
},
{
"parameters": {
"authentication": "webhook",
"content": "={{ $json.message }}",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [
944,
384
],
"id": "17ceecc3-f435-4d0b-b3ba-ad535626fc88",
"name": "Discord2",
"credentials": {
"discordWebhookApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Pastikan node 'update system1' menjalankan perintah: sudo docker image prune -f\nconst output = $('update system1').first().json.stdout;\nlet message = \"\";\nlet status = \"\u2705\";\n\n// Mengambil snippet log, membatasi panjang agar tidak melebihi limit pesan Discord\nconst logSnippet = output.slice(-1000);\nconst userId = \"664803448629493816\";\nconst mention = `<@${userId}>`;\n\n// Logika deteksi hasil prune\nconst isError = output.toLowerCase().includes(\"error\") || output.toLowerCase().includes(\"failed\");\n\nif (isError) {\n status = \"\u274c\";\n message = `\u274c **Docker Prune Failed**\\n\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n${mention}`;\n} else {\n // Docker prune biasanya memberikan output \"Total reclaimed space: ...\"\n status = \"\u2705\";\n message = `\u2705 **Docker Images Pruned Successfully**\\n\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n`;\n}\n\nreturn [{\n json: {\n status,\n message\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
384
],
"id": "9150ba4a-08fa-4778-a65f-96d2f18decb4",
"name": "Code2"
},
{
"parameters": {
"command": "sudo docker image prune -f"
},
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
"position": [
496,
384
],
"id": "59b90333-ba6b-404a-b518-ec22703b30d6",
"name": "update system1",
"credentials": {
"sshPassword": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "# Docker Cleaner\n",
"height": 80,
"width": 288
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
240,
288
],
"id": "0861dd72-59a1-45ae-a4ef-b4be85bc2eb5",
"name": "Sticky Note1"
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "update system",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Discord",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Discord1",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger1": {
"main": [
[
{
"node": "update docker",
"type": "main",
"index": 0
}
]
]
},
"update system": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"update docker": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger2": {
"main": [
[
{
"node": "update system1",
"type": "main",
"index": 0
}
]
]
},
"Code2": {
"main": [
[
{
"node": "Discord2",
"type": "main",
"index": 0
}
]
]
},
"update system1": {
"main": [
[
{
"node": "Code2",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"binaryMode": "separate",
"timeSavedMode": "fixed",
"errorWorkflow": "PWFdEg6jEbv43mqe",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"staticData": {
"node:Schedule Trigger": {
"recurrenceRules": []
},
"node:Schedule Trigger2": {
"recurrenceRules": []
}
},
"meta": {
"templateCredsSetupCompleted": true
},
"versionId": "5c4c83e9-d76b-470b-97be-dcbd0b080507",
"activeVersionId": "5c4c83e9-d76b-470b-97be-dcbd0b080507",
"triggerCount": 2,
"shared": [
{
"updatedAt": "2026-05-12T12:45:59.817Z",
"createdAt": "2026-05-12T12:45:59.817Z",
"role": "workflow:owner",
"workflowId": "mlARU7JA0uMUNCeu",
"projectId": "UoStXPqNC0kdV8zl"
}
],
"activeVersion": {
"updatedAt": "2026-06-06T13:14:59.000Z",
"createdAt": "2026-06-06T13:13:51.710Z",
"versionId": "5c4c83e9-d76b-470b-97be-dcbd0b080507",
"workflowId": "mlARU7JA0uMUNCeu",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
272,
96
],
"id": "3a3ce204-dc48-4372-9575-1578da7093ea",
"name": "Schedule Trigger"
},
{
"parameters": {
"authentication": "webhook",
"content": "={{ $json.message }}",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [
944,
96
],
"id": "b90830c9-3e22-463a-b046-cd8804094b7f",
"name": "Discord",
"webhookId": "e77127a2-4c0d-4222-8dfc-7e5429c03e5b",
"credentials": {
"discordWebhookApi": {
"id": "5TSz1fTb1yQHXwaY",
"name": "n8n server"
}
}
},
{
"parameters": {
"jsCode": "const output = $('update system').first().json.stdout\nlet message = \"\";\nlet status = \"\u2705\";\nconst logSnippet = output.slice(-1000);\nconst userId = \"664803448629493816\"; // Ganti dengan user Discord yang ingin ditag\nconst mention = `<@${userId}>`;\n\nif (output.toLowerCase().includes(\"error\") || output.toLowerCase().includes(\"failed\")) {\n status = \"\u274c\";\n message = `\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n\u274c Update failed!\\n${mention}`;\n} else if (output.toLowerCase().includes(\"a reboot is required\")) {\n status = \"\ud83d\udd04\";\n message = `\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n\ud83d\udd04 Update completed but reboot is required.\\n${mention}`;\n} else {\n status = \"\u2705\";\n message = `\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n\u2705 System updated successfully!`;\n}\n\nreturn [{\n json: {\n status,\n message\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
96
],
"id": "23da27bb-e451-49dd-8b35-889832b37161",
"name": "Code"
},
{
"parameters": {
"authentication": "webhook",
"content": "={{ $json.content }}\n-----------------------------------------",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [
944,
736
],
"id": "cd86d97a-7f43-44b7-83da-676664693848",
"name": "Discord1",
"webhookId": "8b2ed5c9-d278-4f0f-8c37-ec7f632b9e86",
"credentials": {
"discordWebhookApi": {
"id": "5TSz1fTb1yQHXwaY",
"name": "n8n server"
}
},
"disabled": true
},
{
"parameters": {
"jsCode": "const rawLog = $input.first().json.stdout; // log input dari node sebelumnya\n\nconst updatedImages = [];\nconst failedImages = [];\n\nconst lines = rawLog.split(\"\\n\");\n\nfor (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n\n // Cek baris \"Pulling image\"\n if (line.startsWith(\"[*] Pulling image:\")) {\n const image = line.replace(\"[*] Pulling image: \", \"\").trim();\n const statusLine = lines[i + 3] || \"\"; // Baris ke-3 setelah ini biasanya status\n const errorLine = lines[i + 1] || \"\"; // Jika error, muncul setelah ini\n\n if (statusLine.includes(\"Image is up to date\")) {\n // Lewatkan (tidak perlu dicatat)\n } else if (errorLine.includes(\"Error response from daemon\")) {\n failedImages.push(`${image} \u274c`);\n } else {\n updatedImages.push(`${image} \u2705`);\n }\n }\n}\n\n// Format pesan\nconst startedAt = rawLog.match(/Started at (.+?) ===/i)?.[1] || \"Unknown\";\nconst completedAt = rawLog.match(/Completed at (.+?) ===/i)?.[1] || \"Unknown\";\n\nlet message = `\ud83d\udee0\ufe0f **Docker Auto Update Summary**\\n\\n`;\nmessage += `\ud83d\udd53 **Started**: ${startedAt}\\n\u2705 **Completed**: ${completedAt}\\n\\n`;\n\nif (updatedImages.length > 0) {\n message += `\ud83d\udd04 **Updated Images:**\\n${updatedImages.map(i => `\u2022 ${i}`).join(\"\\n\")}\\n\\n`;\n}\n\nif (failedImages.length > 0) {\n message += `\u2757 **Failed Pulls:**\\n${failedImages.map(i => `\u2022 ${i}`).join(\"\\n\")}\\n\\n`;\n}\n\nif (updatedImages.length === 0 && failedImages.length === 0) {\n message += `\ud83d\udce6 All containers already up to date.\\n`;\n}\n\nreturn [\n {\n json: {\n content: message // Output ke Discord node\n }\n }\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
736
],
"id": "febfead7-8bc1-4430-98bb-080a8d3d92d7",
"name": "Code1",
"disabled": true
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 3
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
272,
736
],
"id": "4576a847-01cf-47d1-a18f-bfdffc4fff76",
"name": "Schedule Trigger1",
"disabled": true
},
{
"parameters": {
"command": "sudo nala update && sudo nala full-upgrade -y"
},
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
"position": [
496,
96
],
"id": "03f12906-1b79-4238-822a-2fdf8acaf085",
"name": "update system",
"credentials": {
"sshPassword": {
"id": "N1m1tTJEIrNETZuL",
"name": "SSH Password account"
}
}
},
{
"parameters": {
"command": "sudo ./autoupdate.sh",
"cwd": "/home/n8nbot/dockerUpdate"
},
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
"position": [
496,
736
],
"id": "4306cfba-5e46-4c77-a06e-f8abe4079c64",
"name": "update docker",
"credentials": {
"sshPassword": {
"id": "N1m1tTJEIrNETZuL",
"name": "SSH Password account"
}
},
"disabled": true
},
{
"parameters": {
"content": "# OS Update\n",
"height": 80,
"width": 208
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
240,
-16
],
"id": "c8667314-4492-41ee-a887-24a31759300e",
"name": "Sticky Note"
},
{
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
272,
384
],
"id": "a5eb5164-cc62-475a-a7f6-01cb5b3a8e24",
"name": "Schedule Trigger2"
},
{
"parameters": {
"authentication": "webhook",
"content": "={{ $json.message }}",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [
944,
384
],
"id": "17ceecc3-f435-4d0b-b3ba-ad535626fc88",
"name": "Discord2",
"webhookId": "e77127a2-4c0d-4222-8dfc-7e5429c03e5b",
"credentials": {
"discordWebhookApi": {
"id": "5TSz1fTb1yQHXwaY",
"name": "n8n server"
}
}
},
{
"parameters": {
"jsCode": "// Pastikan node 'update system1' menjalankan perintah: sudo docker image prune -f\nconst output = $('update system1').first().json.stdout;\nlet message = \"\";\nlet status = \"\u2705\";\n\n// Mengambil snippet log, membatasi panjang agar tidak melebihi limit pesan Discord\nconst logSnippet = output.slice(-1000);\nconst userId = \"664803448629493816\";\nconst mention = `<@${userId}>`;\n\n// Logika deteksi hasil prune\nconst isError = output.toLowerCase().includes(\"error\") || output.toLowerCase().includes(\"failed\");\n\nif (isError) {\n status = \"\u274c\";\n message = `\u274c **Docker Prune Failed**\\n\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n${mention}`;\n} else {\n // Docker prune biasanya memberikan output \"Total reclaimed space: ...\"\n status = \"\u2705\";\n message = `\u2705 **Docker Images Pruned Successfully**\\n\\`\\`\\`\\n${logSnippet}\\n\\`\\`\\`\\n`;\n}\n\nreturn [{\n json: {\n status,\n message\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
384
],
"id": "9150ba4a-08fa-4778-a65f-96d2f18decb4",
"name": "Code2"
},
{
"parameters": {
"command": "sudo docker image prune -f"
},
"type": "n8n-nodes-base.ssh",
"typeVersion": 1,
"position": [
496,
384
],
"id": "59b90333-ba6b-404a-b518-ec22703b30d6",
"name": "update system1",
"credentials": {
"sshPassword": {
"id": "N1m1tTJEIrNETZuL",
"name": "SSH Password account"
}
}
},
{
"parameters": {
"content": "# Docker Cleaner\n",
"height": 80,
"width": 288
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
240,
288
],
"id": "0861dd72-59a1-45ae-a4ef-b4be85bc2eb5",
"name": "Sticky Note1"
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "update system",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Discord",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Discord1",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger1": {
"main": [
[
{
"node": "update docker",
"type": "main",
"index": 0
}
]
]
},
"update system": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"update docker": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger2": {
"main": [
[
{
"node": "update system1",
"type": "main",
"index": 0
}
]
]
},
"Code2": {
"main": [
[
{
"node": "Discord2",
"type": "main",
"index": 0
}
]
]
},
"update system1": {
"main": [
[
{
"node": "Code2",
"type": "main",
"index": 0
}
]
]
}
},
"nodeGroups": [],
"authors": "Muhammad Bintang Panji Kusuma",
"name": "Version 5c4c83e9",
"description": "",
"autosaved": true
},
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
discordWebhookApisshPassword
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
HomeLab. Uses scheduleTrigger, discord, ssh. Scheduled trigger; 8 nodes.
Source: https://github.com/SamVivan1/n8n-Workflows-Backup/blob/main/homelab-X5baTUVFHhvSO1SC.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Absen Otomatis. Uses discord, ssh, scheduleTrigger, executeWorkflowTrigger. Scheduled trigger; 16 nodes.
Absen Nami. Uses ssh, discord, scheduleTrigger. Scheduled trigger; 13 nodes.
Wait Schedule. Uses spotify, supabase, compareDatasets, noOp. Scheduled trigger; 54 nodes.