{ "name": "Genome: ingest", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "forgejo-push", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ 1040, 240 ], "id": "9cc1b02e-6885-4a19-af34-ed2783ae99bf", "name": "Webhook", "webhookId": "bb518834-da85-46bb-bb72-97ba21a78faa" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 }, "conditions": [ { "id": "cc000000-0000-4000-8000-000000000001", "leftValue": "={{ $json.body.ref }}", "rightValue": "refs/heads/develop", "operator": { "type": "string", "operation": "equals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 1264, 240 ], "id": "b2dd46aa-cdc3-4103-ad05-c728d9bd14ee", "name": "IF: ref == develop" }, { "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": {} }, "type": "n8n-nodes-base.executeWorkflow", "typeVersion": 1.3, "position": [ 1488, 240 ], "id": "e10f6af4-73ac-4689-b9f4-9c656d7c0cc4", "name": "Power Manager - ensure-on" }, { "parameters": { "authentication": "privateKey", "command": "=ssh vm101 'pi changed-raw {{ $('Webhook').item.json.body.repository.name }} {{ $('Webhook').item.json.body.before }} {{ $('Webhook').item.json.body.after }}'" }, "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [ 1712, 240 ], "id": "479d2e9d-0fde-417a-9122-d9780cc5dcba", "name": "SSH: changed-raw", "executeOnce": true, "credentials": { "sshPrivateKey": { "id": "GJQjKzte7Hjdfz89", "name": "n8n container -> n8n-runner@nexus" } } }, { "parameters": { "jsCode": "// run-once-for-all: parse changed-raw (JSON intero) -> 1 item per raw.\n// I nomi raw con spazi o caratteri non sicuri romperebbero il trasporto SSH\n// (lo spazio e' separatore di token nel comando 'pi ingest g raw'). Quindi qui\n// valido i nomi: quelli problematici NON vengono ingeriti, ma emettono un item\n// di tipo 'badname' che a valle diventa un ntfy 'rinomina il file'.\nconst out = ($input.first().json.stdout || '').toString().trim();\nlet d;\ntry { d = JSON.parse(out); }\ncatch (e) { return [{ json: { _kind: 'error', reason: 'changed-raw non parsabile', raw: out } }]; }\nif (!d.files || d.files.length === 0) return []; // niente raw -> stop silenzioso\n\n// regola 'non rompicoglioni': consentiti lettere, numeri, punto, slash, trattino, underscore.\n// VIETATI: spazi e tutto il resto (che spezzano SSH o gli slug downstream).\nconst SAFE = /^[A-Za-z0-9._\\/-]+$/;\nconst out_items = [];\nfor (const raw of d.files) {\n if (SAFE.test(raw)) {\n out_items.push({ json: { _kind: 'ingest', genome: d.genome, raw } });\n } else {\n out_items.push({ json: { _kind: 'badname', genome: d.genome, raw,\n hint: raw.replace(/[^A-Za-z0-9._\\/-]+/g, '-').toLowerCase() } });\n }\n}\nreturn out_items;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1920, 240 ], "id": "d540e454-4648-475c-8dce-5111ef876f75", "name": "Split raw files" }, { "parameters": { "authentication": "privateKey", "command": "=ssh vm101 'pi ingest {{ $json.genome }} {{ $json.raw }}'" }, "type": "n8n-nodes-base.ssh", "typeVersion": 1, "position": [ 2144, 240 ], "id": "7e30e055-7bc5-484a-a405-d29ea06ff175", "name": "SSH: pi ingest", "credentials": { "sshPrivateKey": { "id": "GJQjKzte7Hjdfz89", "name": "n8n container -> n8n-runner@nexus" } } }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "// per-item: ultima riga JSON di run-ingest.sh\nconst out = ($json.stdout || '').trim();\nconst line = out.split('\\n').filter(l => l.trim().startsWith('{')).pop();\nif (!line) return { status: 'error', reason: 'nessuna riga JSON run-ingest', raw: out };\ntry { return JSON.parse(line); } catch (e) { return { status: 'error', reason: 'JSON non parsabile', raw: line }; }" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2368, 240 ], "id": "f60878f4-8cca-43d0-b8b3-0aa1a422237b", "name": "Parse ingest" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const d = $json;\nlet n;\nif (d.status === 'ok') {\n n = { title: `Ingest ${d.slug}: PR aperta`, priority: 'default', tags: 'inbox_tray',\n body: `\\u2705 ${d.slug}: PR aperta (lint ${d.lint_clean ? 'clean' : 'KO'}${d.conflict ? ', CONFLITTO' : ''})\\n\\n\\ud83d\\udd17 ${d.pr_url}` };\n} else if (d.status === 'pr_failed') {\n n = { title: `Ingest ${d.slug}: PR FALLITA`, priority: 'high', tags: 'warning',\n body: `\\u26a0\\ufe0f ${d.slug}: semantic/lint ok ma PR non aperta.\\n\\n${(d.detail || '').split('\\n')[0]}` };\n} else {\n n = { title: 'Ingest: ERRORE', priority: 'high', tags: 'rotating_light',\n body: `\\u274c ${d.reason || 'errore'}\\n\\n${(d.raw || '').slice(0,300)}` };\n}\nreturn n;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2592, 240 ], "id": "9018dff6-b314-4ca8-b8ff-fd5423818816", "name": "Build ntfy" }, { "parameters": { "method": "POST", "url": "http://ntfy/homelab-genome", "authentication": "genericCredentialType", "genericAuthType": "httpBearerAuth", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "Title", "value": "={{ $json.title }}" }, { "name": "Priority", "value": "={{ $json.priority }}" }, { "name": "Tags", "value": "={{ $json.tags }}" } ] }, "sendBody": true, "contentType": "raw", "rawContentType": "Raw / Text", "body": "={{ $json.body }}", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 2800, 240 ], "id": "1f572cb3-741b-46bc-87fa-1e23ade5a9be", "name": "ntfy: send notification", "credentials": { "httpHeaderAuth": { "id": "TBPXSWOF63k9mvm8", "name": "ntfy-token" }, "httpBearerAuth": { "id": "nCv4CUN7Ef086Ewj", "name": "Bearer Auth account" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 }, "conditions": [ { "id": "dd000000-0000-4000-8000-000000000001", "leftValue": "={{ $json._kind }}", "rightValue": "ingest", "operator": { "type": "string", "operation": "equals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 3168, 240 ], "id": "d2d2e2b2-9bd7-446b-b6b8-0a865d49c601", "name": "IF: nome valido" }, { "parameters": { "mode": "runOnceForEachItem", "jsCode": "const d=$json;\nreturn {\n title: 'Ingest: nome file non valido',\n priority: 'high',\n tags: 'warning',\n body: `\\u26a0\\ufe0f \"${d.raw}\" ha spazi o caratteri non ammessi e non e' stato ingerito.\\n\\nRinominalo in: ${d.hint}`\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 3392, 400 ], "id": "3b69aa97-170a-4666-8b0e-4b51b48b2817", "name": "Build ntfy badname" } ], "pinData": {}, "connections": { "Webhook": { "main": [ [ { "node": "IF: ref == develop", "type": "main", "index": 0 } ] ] }, "IF: ref == develop": { "main": [ [ { "node": "Power Manager - ensure-on", "type": "main", "index": 0 } ] ] }, "Power Manager - ensure-on": { "main": [ [ { "node": "SSH: changed-raw", "type": "main", "index": 0 } ] ] }, "SSH: changed-raw": { "main": [ [ { "node": "Split raw files", "type": "main", "index": 0 } ] ] }, "Split raw files": { "main": [ [ { "node": "IF: nome valido", "type": "main", "index": 0 } ] ] }, "SSH: pi ingest": { "main": [ [ { "node": "Parse ingest", "type": "main", "index": 0 } ] ] }, "Parse ingest": { "main": [ [ { "node": "Build ntfy", "type": "main", "index": 0 } ] ] }, "Build ntfy": { "main": [ [ { "node": "ntfy: send notification", "type": "main", "index": 0 } ] ] }, "IF: nome valido": { "main": [ [ { "node": "SSH: pi ingest", "type": "main", "index": 0 } ], [ { "node": "Build ntfy badname", "type": "main", "index": 0 } ] ] }, "Build ntfy badname": { "main": [ [ { "node": "ntfy: send notification", "type": "main", "index": 0 } ] ] } }, "active": true, "settings": { "executionOrder": "v1", "binaryMode": "separate" }, "versionId": "2115dd9f-e2b6-4acb-8de0-4a166eb9729a", "meta": { "instanceId": "96b2f0ec76a4400bbd481c617b24b3b87024cc7a913efacccaf9fc85722e7417" }, "id": "mUJUuQxcDiiPWcUE", "tags": [] }