326 lines
No EOL
11 KiB
JSON
326 lines
No EOL
11 KiB
JSON
{
|
|
"name": "Genome: prune",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "forgejo-push-prune",
|
|
"options": {}
|
|
},
|
|
"id": "d31388b9-c6d6-4f28-9a6c-b381922bf5e0",
|
|
"name": "Webhook prune",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
1232,
|
|
-64
|
|
],
|
|
"webhookId": "d6ac11900058434e"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Gate: proceed ONLY on develop pushes that REMOVED at least one file under raw/.\n// Additions/modifications are handled by the ingest flow; this flow reacts to deletions only.\nconst item = $input.first().json;\nconst b = item.body || item;\nconst ref = String(b.ref || '');\nconst genome = String((b.repository?.name) || '').toLowerCase().trim();\n\n// Branch filter\nif (ref !== 'refs/heads/develop') return [];\n\n// Genome name validation (DNS-like: lowercase alphanum + hyphen, 1-64 chars)\nif (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(genome)) return [];\n\n// Collect removed paths safely\nconst removed = [];\nfor (const c of (b.commits || [])) {\n if (!c || !Array.isArray(c.removed)) continue;\n for (const p of c.removed) {\n if (typeof p === 'string' && p.startsWith('raw/')) {\n removed.push(p);\n }\n }\n}\n\n// Gate: stop if nothing under raw/ was removed\nif (removed.length === 0) return [];\n\nreturn [{ json: { genome, removedCount: removed.length } }];"
|
|
},
|
|
"id": "84848a31-d099-459e-bd03-67abc2cf2b77",
|
|
"name": "Gate prune",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1456,
|
|
-64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"workflowId": {
|
|
"__rl": true,
|
|
"value": "zbtRXWsLt56nEIfz",
|
|
"mode": "list",
|
|
"cachedResultUrl": "/workflow/zbtRXWsLt56nEIfz",
|
|
"cachedResultName": "Power Manager"
|
|
},
|
|
"workflowInputs": {
|
|
"mappingMode": "defineBelow",
|
|
"value": {
|
|
"mode": "ensure-on"
|
|
},
|
|
"matchingColumns": [
|
|
"mode"
|
|
],
|
|
"schema": [
|
|
{
|
|
"id": "mode",
|
|
"displayName": "mode",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
}
|
|
],
|
|
"attemptToConvertTypes": false,
|
|
"convertFieldsToString": true
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "175e4191-eb1b-4e5d-8d82-c39205753152",
|
|
"name": "Power Manager - ensure-on",
|
|
"type": "n8n-nodes-base.executeWorkflow",
|
|
"typeVersion": 1.3,
|
|
"position": [
|
|
1680,
|
|
-64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"authentication": "privateKey",
|
|
"command": "=ssh vm101 'pi orphan-wiki {{ $('Gate prune').first().json.genome }}'"
|
|
},
|
|
"id": "598f20f8-d668-48da-90e3-1bfada3ace92",
|
|
"name": "SSH: orphan-wiki",
|
|
"type": "n8n-nodes-base.ssh",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1904,
|
|
-64
|
|
],
|
|
"credentials": {
|
|
"sshPrivateKey": {
|
|
"id": "GJQjKzte7Hjdfz89",
|
|
"name": "n8n container -> n8n-runner@nexus"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Gate: proceed to prune only if orphan-wiki actually found orphans.\n// run-prune re-derives independently anyway (no detected-vs-pruned race);\n// this gate just avoids taking the lock for nothing.\nconst out = String($input.first().json.stdout || '').trim();\nlet d;\n\ntry {\n d = JSON.parse(out);\n} catch (e) {\n // Malformed JSON from orphan-wiki — log and stop\n return [{ json: { _gate: 'parse-error', raw: out.substring(0, 500) } }];\n}\n\n// Strict validation: d must be object with numeric count > 0\nif (!d || typeof d !== 'object' || typeof d.count !== 'number' || d.count <= 0) {\n return []; // 0 orphans or missing count -> stop silently\n}\n\nreturn [{ json: { genome: d.genome, count: d.count } }];"
|
|
},
|
|
"id": "3b644d61-26d8-4024-baed-bcb4ad169a6a",
|
|
"name": "Orfani?",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2112,
|
|
-64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"authentication": "privateKey",
|
|
"command": "=ssh vm101 'pi prune {{ $json.genome }}'"
|
|
},
|
|
"id": "a8cae2c2-6f2f-4ef6-add9-287195aa84b5",
|
|
"name": "SSH: prune",
|
|
"type": "n8n-nodes-base.ssh",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
2336,
|
|
-64
|
|
],
|
|
"credentials": {
|
|
"sshPrivateKey": {
|
|
"id": "GJQjKzte7Hjdfz89",
|
|
"name": "n8n container -> n8n-runner@nexus"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"jsCode": "// Extract the last JSON line from SSH stdout (the command may print logs before/after).\n// Run Once for Each Item: $json is the current SSH result item.\nconst out = String($json.stdout || '').trim();\nconst jsonLines = out\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.startsWith('{') && l.endsWith('}'));\n\nconst line = jsonLines.pop(); // last JSON object line (command prints JSON last)\n\nlet r;\ntry {\n r = line ? JSON.parse(line) : { status: 'error', reason: 'nessuna riga JSON trovata in stdout' };\n} catch (e) {\n r = { status: 'error', reason: 'JSON non parsabile', rawLine: line?.substring(0, 1000) };\n}\n\n// Ensure consistent shape for downstream nodes\nreturn {\n status: r.status || 'error',\n reason: r.reason || 'errore sconosciuto',\n count: r.count,\n pr_url: r.pr_url,\n genome: r.genome,\n _raw: line?.substring(0, 500)\n};"
|
|
},
|
|
"id": "da1ab42c-32e1-4c4d-82a1-925fcee1a098",
|
|
"name": "Parse prune",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2560,
|
|
-64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"jsCode": "// Build ntfy notification for genome pruning.\n// Run Once for Each Item: $json is the parsed prune result.\nconst d = $json;\nconst genome = d.genome || 'unknown';\n\nlet n;\nif (d.status === 'ok') {\n const pm = (d.pr_url || '').match(/\\/pulls\\/(\\d+)/);\n const num = pm ? `#${pm[1]}` : '';\n n = {\n topic: 'genome-ingest',\n title: `${genome} \\u00b7 potatura ${num}`.replace(/\\s+/g, ' ').trim(),\n priority: 'default',\n tags: 'broom',\n click: d.pr_url || '',\n actions: d.pr_url ? `view, Apri la PR, ${d.pr_url}` : '',\n body: `${d.count} sorgente/i orfane proposte per la rimozione. **Approva la PR** per potare, oppure chiudila da Forgejo per annullare.`\n };\n} else {\n n = {\n topic: 'genome-ingest',\n title: `${genome} \\u00b7 errore potatura`.trim(),\n priority: 'high',\n tags: 'rotating_light',\n click: '',\n actions: '',\n body: `${d.reason || 'errore sconosciuto durante la potatura'}.`\n };\n}\n\nreturn n;"
|
|
},
|
|
"id": "ebe99407-6038-4f8f-a73f-7dc7b0a011e0",
|
|
"name": "Build ntfy",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2784,
|
|
-64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "=http://ntfy/{{ $json.topic }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpBearerAuth",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Title",
|
|
"value": "={{ $json.title }}"
|
|
},
|
|
{
|
|
"name": "Priority",
|
|
"value": "={{ $json.priority }}"
|
|
},
|
|
{
|
|
"name": "Tags",
|
|
"value": "={{ $json.tags }}"
|
|
},
|
|
{
|
|
"name": "Click",
|
|
"value": "={{ $json.click }}"
|
|
},
|
|
{
|
|
"name": "Actions",
|
|
"value": "={{ $json.actions }}"
|
|
},
|
|
{
|
|
"name": "Markdown",
|
|
"value": "yes"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"contentType": "raw",
|
|
"rawContentType": "Raw / Text",
|
|
"body": "={{ $json.body }}",
|
|
"options": {
|
|
"timeout": 15000
|
|
}
|
|
},
|
|
"id": "0bd3654e-a73d-4c3a-83ed-9f57ca4aad24",
|
|
"name": "ntfy: send",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
2992,
|
|
-64
|
|
],
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "TBPXSWOF63k9mvm8",
|
|
"name": "ntfy-token"
|
|
},
|
|
"httpBearerAuth": {
|
|
"id": "nCv4CUN7Ef086Ewj",
|
|
"name": "Bearer Auth account"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"pinData": {},
|
|
"connections": {
|
|
"Webhook prune": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gate prune",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gate prune": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Power Manager - ensure-on",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Power Manager - ensure-on": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "SSH: orphan-wiki",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"SSH: orphan-wiki": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Orfani?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Orfani?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "SSH: prune",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"SSH: prune": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Parse prune",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Parse prune": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Build ntfy",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Build ntfy": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "ntfy: send",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"active": true,
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate",
|
|
"timeSavedMode": "fixed",
|
|
"errorWorkflow": "7Vws3gCX3QnjM3oD",
|
|
"callerPolicy": "workflowsFromSameOwner",
|
|
"availableInMCP": false
|
|
},
|
|
"versionId": "999f640c-aae6-42aa-9a95-aba26987e9d0",
|
|
"meta": {
|
|
"instanceId": "96b2f0ec76a4400bbd481c617b24b3b87024cc7a913efacccaf9fc85722e7417"
|
|
},
|
|
"id": "smH5Qrv7CQnTtdAF",
|
|
"tags": []
|
|
} |