Some checks failed
CD Pipeline / deploy (push) Failing after 59s
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml) - 部署模式: rsync Python 檔案至 188 → docker restart (volume mount) - Dockerfile/requirements 變動時自動重建 Docker image - 部署通知: Telegram (開始/成功/失敗) - 健康檢查: https://mo.wooo.work/health (最多 5 次重試) - 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
431 lines
13 KiB
JSON
431 lines
13 KiB
JSON
{
|
|
"name": "Ollama / Open WebUI 健康監控 (含自動修復)",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 5
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"id": "schedule-trigger",
|
|
"name": "每 5 分鐘執行",
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [240, 300]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "http://192.168.0.188:11434/api/tags",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"id": "check-ollama-api",
|
|
"name": "檢查 Ollama API",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [460, 200],
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "http://192.168.0.188/",
|
|
"options": {
|
|
"timeout": 10000
|
|
}
|
|
},
|
|
"id": "check-open-webui",
|
|
"name": "檢查 Open WebUI",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [460, 400],
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict"
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "ollama-check",
|
|
"leftValue": "={{ $('檢查 Ollama API').first().json.models !== undefined }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "equals"
|
|
}
|
|
},
|
|
{
|
|
"id": "webui-check",
|
|
"leftValue": "={{ $('檢查 Open WebUI').first().binary !== undefined || $('檢查 Open WebUI').first().json !== undefined }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
}
|
|
},
|
|
"id": "check-health",
|
|
"name": "服務健康?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2,
|
|
"position": [680, 300]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// 分析失敗原因\nconst ollamaResult = $('檢查 Ollama API').first();\nconst webuiResult = $('檢查 Open WebUI').first();\n\nlet issues = [];\nlet needsDockerRestart = false;\nlet needsContainerRestart = false;\n\n// 檢查 Ollama API\nif (!ollamaResult.json.models) {\n issues.push({\n service: 'Ollama API',\n status: 'down',\n error: ollamaResult.json.error || 'API 無回應'\n });\n}\n\n// 檢查 Open WebUI\nif (webuiResult.json && webuiResult.json.error) {\n const errorMsg = webuiResult.json.error || '';\n \n if (errorMsg.includes('502') || errorMsg.includes('Bad Gateway')) {\n issues.push({\n service: 'Open WebUI',\n status: '502',\n error: '502 Bad Gateway - Docker 網路失效'\n });\n needsDockerRestart = true;\n } else if (errorMsg.includes('ECONNREFUSED') || errorMsg.includes('timeout')) {\n issues.push({\n service: 'Open WebUI',\n status: 'down',\n error: '連線失敗或超時'\n });\n needsContainerRestart = true;\n } else {\n issues.push({\n service: 'Open WebUI',\n status: 'error',\n error: errorMsg\n });\n }\n} else if (!webuiResult.binary && !webuiResult.json) {\n issues.push({\n service: 'Open WebUI',\n status: 'no_response',\n error: '無回應'\n });\n needsContainerRestart = true;\n}\n\nreturn {\n timestamp: new Date().toISOString(),\n issues: issues,\n issueCount: issues.length,\n needsDockerRestart: needsDockerRestart,\n needsContainerRestart: needsContainerRestart,\n summary: issues.map(i => `${i.service}: ${i.error}`).join('\\n')\n};"
|
|
},
|
|
"id": "analyze-failure",
|
|
"name": "分析失敗原因",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [900, 400]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict"
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "docker-restart",
|
|
"leftValue": "={{ $json.needsDockerRestart }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
}
|
|
},
|
|
"id": "needs-docker-restart",
|
|
"name": "需要重啟 Docker?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2,
|
|
"position": [1120, 400]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "sshPassword",
|
|
"resource": "command",
|
|
"command": "sudo systemctl restart docker && sleep 60 && curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/"
|
|
},
|
|
"id": "restart-docker",
|
|
"name": "重啟 Docker Daemon",
|
|
"type": "n8n-nodes-base.ssh",
|
|
"typeVersion": 1,
|
|
"position": [1340, 300],
|
|
"credentials": {
|
|
"sshPassword": {
|
|
"id": "ollama-ssh",
|
|
"name": "Ollama SSH"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "sshPassword",
|
|
"resource": "command",
|
|
"command": "docker restart open-webui && sleep 30 && curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/"
|
|
},
|
|
"id": "restart-container",
|
|
"name": "重啟 Open WebUI 容器",
|
|
"type": "n8n-nodes-base.ssh",
|
|
"typeVersion": 1,
|
|
"position": [1340, 500],
|
|
"credentials": {
|
|
"sshPassword": {
|
|
"id": "ollama-ssh",
|
|
"name": "Ollama SSH"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"chatId": "5619078117",
|
|
"text": "=🔴 <b>Ollama / Open WebUI 服務異常</b>\n\n{{ $('分析失敗原因').item.json.summary }}\n\n<b>自動修復中...</b>\n時間: {{ new Date().toLocaleString('zh-TW', {timeZone: 'Asia/Taipei'}) }}",
|
|
"additionalFields": {
|
|
"parse_mode": "HTML"
|
|
}
|
|
},
|
|
"id": "alert-failure",
|
|
"name": "發送異常告警",
|
|
"type": "n8n-nodes-base.telegram",
|
|
"typeVersion": 1.2,
|
|
"position": [1120, 600],
|
|
"credentials": {
|
|
"telegramApi": {
|
|
"id": "telegram-bot",
|
|
"name": "Telegram Bot"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const dockerResult = $('重啟 Docker Daemon').first();\nconst containerResult = $('重啟 Open WebUI 容器').first();\n\nlet repairStatus = 'unknown';\nlet repairAction = '';\nlet httpCode = '000';\n\nif (dockerResult && dockerResult.json) {\n repairAction = '重啟 Docker Daemon';\n httpCode = dockerResult.json.stdout || '000';\n} else if (containerResult && containerResult.json) {\n repairAction = '重啟 Open WebUI 容器';\n httpCode = containerResult.json.stdout || '000';\n}\n\nif (httpCode === '200') {\n repairStatus = 'success';\n} else {\n repairStatus = 'failed';\n}\n\nreturn {\n repairStatus: repairStatus,\n repairAction: repairAction,\n httpCode: httpCode\n};"
|
|
},
|
|
"id": "check-repair-result",
|
|
"name": "檢查修復結果",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [1560, 400]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict"
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "repair-success",
|
|
"leftValue": "={{ $json.repairStatus }}",
|
|
"rightValue": "success",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
}
|
|
},
|
|
"id": "repair-success-check",
|
|
"name": "修復成功?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2,
|
|
"position": [1780, 400]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"chatId": "5619078117",
|
|
"text": "=🟢 <b>Ollama / Open WebUI 已恢復</b>\n\n動作: {{ $('檢查修復結果').item.json.repairAction }}\n結果: HTTP {{ $('檢查修復結果').item.json.httpCode }}\n時間: {{ new Date().toLocaleString('zh-TW', {timeZone: 'Asia/Taipei'}) }}",
|
|
"additionalFields": {
|
|
"parse_mode": "HTML"
|
|
}
|
|
},
|
|
"id": "notify-success",
|
|
"name": "通知修復成功",
|
|
"type": "n8n-nodes-base.telegram",
|
|
"typeVersion": 1.2,
|
|
"position": [2000, 300],
|
|
"credentials": {
|
|
"telegramApi": {
|
|
"id": "telegram-bot",
|
|
"name": "Telegram Bot"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"chatId": "5619078117",
|
|
"text": "=🔴 <b>Ollama / Open WebUI 修復失敗</b>\n\n嘗試動作: {{ $('檢查修復結果').item.json.repairAction }}\n結果: HTTP {{ $('檢查修復結果').item.json.httpCode }}\n\n<b>需要人工介入處理</b>\nSSH: ssh ollama@192.168.0.188\n\n時間: {{ new Date().toLocaleString('zh-TW', {timeZone: 'Asia/Taipei'}) }}",
|
|
"additionalFields": {
|
|
"parse_mode": "HTML"
|
|
}
|
|
},
|
|
"id": "notify-failure",
|
|
"name": "通知修復失敗",
|
|
"type": "n8n-nodes-base.telegram",
|
|
"typeVersion": 1.2,
|
|
"position": [2000, 500],
|
|
"credentials": {
|
|
"telegramApi": {
|
|
"id": "telegram-bot",
|
|
"name": "Telegram Bot"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {},
|
|
"id": "no-op-healthy",
|
|
"name": "服務正常,無需操作",
|
|
"type": "n8n-nodes-base.noOp",
|
|
"typeVersion": 1,
|
|
"position": [900, 200]
|
|
}
|
|
],
|
|
"connections": {
|
|
"每 5 分鐘執行": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "檢查 Ollama API",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "檢查 Open WebUI",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"檢查 Ollama API": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "服務健康?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"檢查 Open WebUI": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "服務健康?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"服務健康?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "服務正常,無需操作",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "分析失敗原因",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"分析失敗原因": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "需要重啟 Docker?",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "發送異常告警",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"需要重啟 Docker?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "重啟 Docker Daemon",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "重啟 Open WebUI 容器",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"重啟 Docker Daemon": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "檢查修復結果",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"重啟 Open WebUI 容器": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "檢查修復結果",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"檢查修復結果": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "修復成功?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"修復成功?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "通知修復成功",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "通知修復失敗",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"active": true,
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
},
|
|
"tags": [
|
|
{
|
|
"name": "monitoring"
|
|
},
|
|
{
|
|
"name": "auto-repair"
|
|
}
|
|
],
|
|
"pinData": {},
|
|
"meta": {
|
|
"instanceId": "momo-pro-system"
|
|
}
|
|
}
|