Flujo de trabajo avanzado de n8n con sincronización de GitHub
Este es unDevOps, Multimodal AIflujo de automatización del dominio deautomatización que contiene 38 nodos.Utiliza principalmente nodos como If, N8n, Set, Code, Merge. Copia de seguridad automatizada de flujos de trabajo con detección inteligente de cambios usando GitHub
- •Personal Access Token de GitHub
- •Bot Token de Telegram
Nodos utilizados (38)
Categoría
{
"id": "dmvTNU9rfNdgTeSp",
"meta": {
"instanceId": "e7cc7f71b8002726158f14502c7243f892bdf0befb8af4790197437e4666e71e"
},
"name": "Advanced n8n Workflow Sync with GitHub",
"tags": [],
"nodes": [
{
"id": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"name": "Iterar sobre elementos",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-368,
912
],
"parameters": {
"options": {}
},
"executeOnce": false,
"typeVersion": 3
},
{
"id": "34869a4d-b687-44f7-81fd-5f71e8679a0e",
"name": "Actualizar contenido de archivo y confirmar",
"type": "n8n-nodes-base.github",
"position": [
1584,
1872
],
"webhookId": "f2d754dd-b68d-41e8-a662-7e91c1c3aa95",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.newFile.path }}",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
"commitMessage": "=update: {{ $json.context.newFile.name }}"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "bbf77bc7-8c35-4177-be4f-44c91682fd1a",
"name": "Activador programado",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2912,
864
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "34d67ff9-e25a-42a9-b751-dbde37ed575b",
"name": "Obtener todos los flujos de trabajo",
"type": "n8n-nodes-base.n8n",
"position": [
-1328,
848
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "M9BEPZyx4jMbY5tY",
"name": "n8n account"
}
},
"typeVersion": 1
},
{
"id": "98fc6907-d188-4f9a-b68e-44592bfac95b",
"name": "Codificar flujos de trabajo de N8N",
"type": "n8n-nodes-base.code",
"position": [
-1104,
848
],
"parameters": {
"jsCode": "// Encode workflow data to base64 to prevent data pollution\nconst items = $input.all();\n\nfor (const item of items) {\n const originalWorkflow = item.json;\n\n item.json = {\n id: originalWorkflow.id,\n name: originalWorkflow.name,\n n8nWorkflowData: Buffer.from(JSON.stringify(originalWorkflow)).toString('base64')\n };\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "3576b4b3-6692-4d08-9453-697d44ac0f56",
"name": "Decidir cambios",
"type": "n8n-nodes-base.code",
"position": [
-112,
928
],
"parameters": {
"jsCode": "// Helper function to ensure stable JSON serialization for reliable comparison.\nfunction sortKeysDeep(obj) {\n if (obj === null || typeof obj !== 'object') return obj;\n if (Array.isArray(obj)) return obj.map(sortKeysDeep);\n const out = {};\n Object.keys(obj).sort().forEach(k => { out[k] = sortKeysDeep(obj[k]); });\n return out;\n}\n\nconst items = $input.all();\nconst WORKFLOWS_DIR = $node[\"Configuration\"].json.repo.path;\n\nfor (const item of items) {\n const src = item.json || {};\n const flags = {\n fileExists: false,\n nameChanged: false,\n shouldCommit: false\n };\n // Initialize the context container\n item.json.context = {\n oldFile: { path: '', name: '' },\n newFile: { path: '', name: '' },\n operation: ''\n };\n const context = item.json.context;\n\n // 1. Determine if the file exists on GitHub.\n const hasGithub = typeof src.githubWorkflowData === 'string' && src.githubWorkflowData.length > 0;\n flags.fileExists = hasGithub;\n\n // 2. Extract the current workflow name from the N8N data.\n const currentName = src.name || '';\n context.newFile.name = currentName;\n\n // 3. Detect renames and set file paths.\n if (typeof src.filePath === 'string' && src.filePath.length > 0) {\n const parts = src.filePath.split('/');\n const filename = parts.pop() || '';\n const githubName = filename.endsWith('.json') ? filename.slice(0, -5) : filename;\n \n flags.nameChanged = githubName !== currentName;\n context.oldFile.path = src.filePath;\n context.oldFile.name = githubName;\n \n const dirPath = parts.join('/');\n context.newFile.path = `${dirPath}/${currentName}.json`;\n\n } else {\n flags.nameChanged = false;\n context.newFile.path = `${WORKFLOWS_DIR}/${currentName}.json`.replace(/\\/+/g, '/');\n }\n\n // 4. Perform a stable comparison to see if a commit is needed.\n try {\n if (flags.fileExists) {\n const n8nJsonStr = Buffer.from(src.n8nWorkflowData, 'base64').toString('utf8');\n const githubJsonStr = Buffer.from(src.githubWorkflowData, 'base64').toString('utf8');\n const n8nObj = JSON.parse(n8nJsonStr);\n const githubObj = JSON.parse(githubJsonStr);\n const stableN8nStr = JSON.stringify(sortKeysDeep(n8nObj));\n const stableGithubStr = JSON.stringify(sortKeysDeep(githubObj));\n flags.shouldCommit = stableN8nStr !== stableGithubStr;\n } else {\n flags.shouldCommit = true; // New file, always commit.\n }\n } catch (e) {\n flags.shouldCommit = true; // If parsing or comparison fails, better to commit.\n }\n\n // 5. Determine the final operation type.\n if (flags.nameChanged) {\n context.operation = 'rename';\n } else if (!flags.fileExists) {\n context.operation = 'create';\n } else if (flags.shouldCommit) {\n context.operation = 'update';\n } else {\n context.operation = 'skip';\n }\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "1cc5a35b-8e52-46dd-ac69-03e320f66fc8",
"name": "Eliminar archivo antiguo",
"type": "n8n-nodes-base.github",
"position": [
1744,
1408
],
"webhookId": "1bd59af3-c8ee-4664-9cfd-df3ab4b6793d",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.oldFile.path }}",
"resource": "file",
"operation": "delete",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"commitMessage": "=rename: {{ $json.context.oldFile.name }} -> {{ $json.context.newFile.name }} (step 1/2: remove old)"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "c6464638-d7d4-4e36-b359-f70412db2bbb",
"name": "Crear nuevo archivo (renombrar)",
"type": "n8n-nodes-base.github",
"position": [
2144,
1408
],
"webhookId": "1b7a1463-d11a-4c0c-a596-a4b09e003d5a",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.newFile.path }}",
"resource": "file",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
"commitMessage": "=rename: {{ $json.context.oldFile.name }} -> {{ $json.context.newFile.name }} (step 2/2: create new)"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "b470dd47-762a-4c8a-90e0-e4f535e9b41e",
"name": "Fusionar después de crear (renombrar)",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
2384,
1392
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "be267762-77a4-4af5-ba26-7f3945d44b32",
"name": "Fusionar después de actualizar",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
1872,
1856
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "db1f95ae-42e5-4588-bb7d-5348011d9afe",
"name": "Listar archivos",
"type": "n8n-nodes-base.github",
"notes": "An edge case handling. Do not stop the whole workflow if there's no such folder.",
"onError": "continueErrorOutput",
"position": [
-1552,
1040
],
"webhookId": "2e1f9567-52d4-4047-980c-6b4a57d4bd40",
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $(\"Configuration\").item.json.repo.path }}",
"resource": "file",
"operation": "list",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
}
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1.1
},
{
"id": "8c6f381b-caed-4267-abb4-6672c93f45e9",
"name": "Fusionar",
"type": "n8n-nodes-base.merge",
"position": [
-880,
928
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "8d8f0963-0b37-47d9-b263-ffc7da06d118",
"name": "Nota adhesiva",
"type": "n8n-nodes-base.stickyNote",
"position": [
1680,
1168
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## Rename a file (two-step)\n1. Delete the old filename\n1. Create a new filename"
},
"typeVersion": 1
},
{
"id": "371212bd-c19c-4908-b3cb-fe75034a95fb",
"name": "Nota adhesiva1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1616,
752
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## Collect data\n- N8N workflows list\n- GitHub files list "
},
"typeVersion": 1
},
{
"id": "fb300a39-10b0-48cf-a793-a96e1ee3014f",
"name": "Nota adhesiva2",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
-2672,
592
],
"parameters": {
"color": 4,
"width": 326,
"height": 448,
"content": "## Set parameters\n### GitHub\n- Repo owner\n- Repo name\n- Repo folder to store workflow backups\n### Reports\n- Telegram Chat ID to send notifications to\n- Do you need a report each time or only if something changed"
},
"typeVersion": 1
},
{
"id": "80dabce7-e36e-4ea7-bd7d-67fb3b3096f8",
"name": "Nota adhesiva3",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
-3024,
592
],
"parameters": {
"color": 4,
"width": 326,
"height": 448,
"content": "## Tune the schedule\nYou could change the check interval here.\n\nDefault: every hour"
},
"typeVersion": 1
},
{
"id": "802494dc-8c76-4a34-97d3-c414cb04a179",
"name": "Nota adhesiva4",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
176,
0
],
"parameters": {
"color": 7,
"width": 1174,
"height": 272,
"content": "## Execution report\nYou could send this report to Telegram. See parameters in `Configuration` node for details.\nIf you don't need this -- delete this part."
},
"typeVersion": 1
},
{
"id": "d0af7c02-a586-4993-8c4b-8fb18c3053fe",
"name": "Construir arreglos de resumen",
"type": "n8n-nodes-base.code",
"position": [
480,
112
],
"parameters": {
"jsCode": "// Aggregate arrays and flags for summary (no rendering)\n\nfunction normalizeName(item) {\n return String(\n item.json?.name || 'unknown'\n );\n}\n\nconst items = $input.all();\n\nconst buckets = { create: [], update: [], rename: [], skip: [] };\n\nfor (const it of items) {\n const name = normalizeName(it);\n const op = it.json?.context.operation;\n const oldName = it.json?.context.oldFile.name || name;\n const newName = it.json?.context.newFile.name || name;\n\n if (op === 'rename') buckets.rename.push(`${oldName} -> ${newName}`);\n else if (op === 'create') buckets.create.push(name);\n else if (op === 'update') buckets.update.push(name);\n else buckets.skip.push(name);\n}\n\nconst isAnythingChanged = buckets.create.length > 0 || buckets.update.length > 0 || buckets.rename.length > 0;\n\nreturn [{ json: {\n isAnythingChanged,\n created: buckets.create,\n updated: buckets.update,\n renamed: buckets.rename,\n skipped: buckets.skip\n} }];"
},
"typeVersion": 2
},
{
"id": "0c401771-b522-4091-a14f-aba4a06d6158",
"name": "Renderizar resumen",
"type": "n8n-nodes-base.code",
"notes": "## Connecting to a messenger\n\nUse {{$json.message}} as message text. 'isAnythingChanged' controls whether to send.",
"position": [
928,
112
],
"parameters": {
"jsCode": "// Helper function to escape text for Telegram's MarkdownV2 parser\nconst escapeMarkdownV2 = (str) => {\n // For the full list of characters, see https://core.telegram.org/bots/api#markdownv2-style\n return String(str).replace(/([_\\[\\]()~`>#+=|{}.!*-])/g, '\\\\$1');\n};\n\nconst all = $input.all();\nconst data = (all[0] && all[0].json) || {};\n\nconst config = $('Configuration').first().json;\nconst repoOwner = config.repo.owner;\nconst repoName = config.repo.name;\nconst repoPath = config.repo.path;\n\n// Construct repository URL using /blob/-/ for a branch-agnostic link\nconst repoUrl = `https://github.com/${repoOwner}/${repoName}/blob/-/${repoPath}`;\n\n// The link's *text* must be escaped, but the URL must not be.\nconst repoLinkText = escapeMarkdownV2(`${repoOwner}/${repoName}/${repoPath}`);\nconst repoLink = `[${repoLinkText}](${repoUrl})`;\n\nconst getList = key => (Array.isArray(data[key]) ? data[key] : []);\nconst sortAsc = (a, b) => String(a).localeCompare(String(b));\n\nconst sections = [\n { key: 'created', title: 'Created' },\n { key: 'updated', title: 'Updated' },\n { key: 'renamed', title: 'Renamed' },\n { key: 'skipped', title: 'Skipped (no changes)' },\n];\n\nconst summaryParts = [\n `created ${getList('created').length}`,\n `updated ${getList('updated').length}`,\n `renamed ${getList('renamed').length}`,\n `skipped ${getList('skipped').length}`\n];\n\n// Construct the final message with the new header format\nconst messageLines = [\n '*Backup N8N workflows to GitHub*', // Main title\n '', // Blank line for spacing\n `Repo: ${repoLink}`,\n `Totals: ${escapeMarkdownV2(summaryParts.join(', '))}`\n];\n\n// Append detailed lists as before\nfor (const { key, title } of sections) {\n const list = [...getList(key)].sort(sortAsc);\n if (list.length) {\n messageLines.push('', `*${escapeMarkdownV2(title)}:*`);\n for (const item of list) {\n let line;\n if (key === 'renamed') {\n const [oldName, newName] = item.split(' -> ');\n line = `\\`${oldName}.json\\` ${escapeMarkdownV2('->')} \\`${newName}.json\\``;\n } else {\n line = `\\`${item}.json\\``;\n }\n messageLines.push(line);\n }\n }\n}\n\nconst message = messageLines.join('\\n');\nreturn [{ json: { message } }];"
},
"notesInFlow": false,
"typeVersion": 2
},
{
"id": "fdb5e4f7-8aa1-4273-8776-ac0248b893e4",
"name": "¿Algo cambió?",
"type": "n8n-nodes-base.if",
"position": [
704,
112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "8148a3ce-16fe-4074-9f57-c49072be8a8f",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $node[\"Configuration\"].json.report.verbose }}",
"rightValue": ""
},
{
"id": "ee4ef204-341f-444a-a26a-299aa0cde573",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.isAnythingChanged }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "13ba1e58-2bac-44dc-9142-b61efef7c4e1",
"name": "Extraer parámetros del flujo de trabajo",
"type": "n8n-nodes-base.code",
"position": [
-1104,
1040
],
"parameters": {
"jsCode": "let items = $input.all();\n\nfor (let item of items) {\n try {\n const contentBase64 = item.json.content;\n const path = item.json.path;\n const sha = item.json.sha;\n\n // Decode and parse GitHub file content to extract name\n const content = Buffer.from(contentBase64, 'base64').toString('utf8');\n const workflow = JSON.parse(content);\n\n // Keep only the fields we need from GitHub side, store as base64\n item.json = {\n id: workflow.id,\n name: workflow.name,\n filePath: path,\n githubWorkflowData: contentBase64, // Store as base64 to match N8N side\n sha: sha\n };\n\n } catch (error) {\n // Non-JSON or invalid workflow file\n console.log(`Error parsing file ${item.json.path}: ${error.message}`);\n item.json = {\n id: null,\n name: null,\n filePath: item.json.path,\n error: error.message\n };\n }\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "3464840a-599d-4432-aade-bfd5a2930461",
"name": "Obtener archivos",
"type": "n8n-nodes-base.github",
"notes": "An edge case handling. Do not stop the whole workflow if there's no such folder.",
"onError": "continueErrorOutput",
"position": [
-1328,
1040
],
"webhookId": "93c8a2dd-ddad-4837-a062-25473eee1208",
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.path }}",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1.1
},
{
"id": "d991e89f-d795-4559-9b3f-4ecce50723b5",
"name": "¿Está configurado Telegram?",
"type": "n8n-nodes-base.if",
"position": [
256,
112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "03191658-7c7b-4f85-a07b-35d9749d91f3",
"operator": {
"type": "number",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $(\"Configuration\").item.json.report.tg.chatID }}",
"rightValue": 0
},
{
"id": "3277a7ef-0895-4fbf-beb2-432f54cc8efc",
"operator": {
"type": "number",
"operation": "notEquals"
},
"leftValue": "={{ $(\"Configuration\").item.json.report.tg.chatID }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "0705a90f-00e1-46e6-b5ef-4a4efe5255cd",
"name": "Enviar un mensaje",
"type": "n8n-nodes-base.telegram",
"position": [
1152,
112
],
"webhookId": "ea0e343d-7ffa-4dd7-a3aa-0e7e45ad5753",
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $node[\"Configuration\"].json.report.tg.chatID }}",
"additionalFields": {
"parse_mode": "MarkdownV2",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "7sLcbl1lRhPnfzJI",
"name": "Telegram account"
}
},
"typeVersion": 1.2
},
{
"id": "fdef22d8-7960-48e2-a046-e69b3f48ce15",
"name": "Crear nuevo archivo",
"type": "n8n-nodes-base.github",
"position": [
1296,
2416
],
"webhookId": "66429ae4-4b7d-4fb5-8438-26cdf6c4faa8",
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.owner }}"
},
"filePath": "={{ $json.context.newFile.path }}",
"resource": "file",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $(\"Configuration\").item.json.repo.name }}"
},
"fileContent": "={{ JSON.stringify($json.n8nWorkflowData.base64Decode().parseJson(), null, 2) }}",
"commitMessage": "=create: {{ $json.context.newFile.name }}"
},
"credentials": {
"githubApi": {
"id": "7sCS6E9S2UWO6PFt",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "669101cb-2d23-41c7-8e07-6e282b556cdd",
"name": "Fusionar después de crear",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
1584,
2400
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "c5d3b5e4-9c2a-4cf8-8bae-fca50b9f884c",
"name": "Nota adhesiva5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1328,
1712
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## Update an existing file"
},
"typeVersion": 1
},
{
"id": "d58b3267-9510-4abd-b4df-14f5cc19f984",
"name": "Nota adhesiva6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1040,
2256
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## Create a new file"
},
"typeVersion": 1
},
{
"id": "2ababf2c-5315-4d6a-9f09-b2efaef4ab2c",
"name": "Sin operación, no hacer nada",
"type": "n8n-nodes-base.noOp",
"position": [
1056,
2976
],
"parameters": {},
"typeVersion": 1
},
{
"id": "7e069ed2-7303-4db5-b0b3-f18edbfb88da",
"name": "Nota adhesiva7",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
2800
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## Nothing to do"
},
"typeVersion": 1
},
{
"id": "0ac9743d-298a-49d2-b391-4c34b9d5122b",
"name": "Detener y error",
"type": "n8n-nodes-base.stopAndError",
"position": [
336,
3376
],
"parameters": {
"errorMessage": "=Invalid operation: \"{{ $json.context.operation }}\". You should look at the code in the \"Decide changes\" node."
},
"typeVersion": 1
},
{
"id": "f7a498d9-900f-41ab-9b5f-8e849b85c38c",
"name": "Nota adhesiva8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-512,
752
],
"parameters": {
"color": 7,
"width": 880,
"height": 448,
"content": "## Controller"
},
"typeVersion": 1
},
{
"id": "4d426ef2-c4db-4690-bd23-48c374fcf0e1",
"name": "Enrutador",
"type": "n8n-nodes-base.switch",
"position": [
128,
880
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "=rename",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5af9aafc-3ee1-4855-89b2-b0ceb83b3169",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "rename"
}
]
},
"renameOutput": true
},
{
"outputKey": "update",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "849881fc-2d0e-4154-b6c2-10ff6c2b5480",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "update"
}
]
},
"renameOutput": true
},
{
"outputKey": "create",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "85f4cce5-476e-4970-82b4-0b04cc67870f",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "create"
}
]
},
"renameOutput": true
},
{
"outputKey": "skip",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "51919025-e488-4557-9cd9-23f4be9bbf06",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.context.operation }}",
"rightValue": "skip"
}
]
},
"renameOutput": true
}
]
},
"options": {
"ignoreCase": true,
"fallbackOutput": "extra",
"renameFallbackOutput": "error"
}
},
"typeVersion": 3.2
},
{
"id": "921826cc-13da-4db8-8a18-fdffacbcf5bb",
"name": "Fusionar después de eliminar (renombrar)",
"type": "n8n-nodes-base.merge",
"notes": "Keeps the original context intact across the GitHub step. Prevents losing flags and fields.",
"position": [
1968,
1248
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"joinMode": "enrichInput1",
"fieldsToMatchString": "id"
},
"typeVersion": 3.2
},
{
"id": "bda0d23c-f6fe-435c-8732-c1f97c313d80",
"name": "Configuración",
"type": "n8n-nodes-base.set",
"position": [
-2576,
864
],
"parameters": {
"values": {
"number": [
{
"name": "report.tg.chatID",
"value": null
}
],
"string": [
{
"name": "repo.owner"
},
{
"name": "repo.name"
},
{
"name": "repo.path",
"value": "workflows/"
}
],
"boolean": [
{
"name": "report.verbose"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "a9074602-25a9-4fec-a464-caf1459dd3fb",
"name": "Nota adhesiva10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3024,
1072
],
"parameters": {
"width": 672,
"height": 960,
"content": "## Advanced n8n Workflow Sync with GitHub\n\nThis workflow automatically backs up your n8n workflows to a GitHub repository. It intelligently detects changes, handles workflow renames, and commits only when actual modifications occur, providing a clean version history.\n\n### ✨ Key Features:\n- **Intelligent Sync**: Reliable backup of n8n workflows to GitHub.\n- **Rename Support**: Automatically handles workflow renames.\n- **Efficient Commits**: Only commits real changes, keeping your repo clean.\n- **Clear History**: Informative commit messages (create, update, rename).\n\n### 🚀 Quick Setup:\n1. **Credentials**: Set up GitHub, n8n API, and optional Telegram credentials in n8n.\n2. **Configuration Node**: Open the `Configuration` node (green) and update:\n - `repo.owner`: Your GitHub username\n - `repo.name`: Your GitHub repository name\n - `repo.path`: Folder for backups (e.g., `workflows/`)\n - `report.tg.chatID` (Optional): Telegram chat ID, or `0` to disable.\n3. **Connect Credentials**: Link your created credentials to the respective GitHub, n8n, and Telegram nodes.\n4. **Schedule Trigger**: Adjust the backup frequency in the `Schedule Trigger` node.\n5. **Activate**: Save and activate the workflow.\n\n### ⚙️ How It Works (Simple Steps)\n\n1. **Get n8n Workflows**: The workflow starts by fetching all your current workflows from n8n.\n2. **Get GitHub Files**: At the same time, it lists all existing workflow files from your GitHub repository.\n3. **Compare & Decide**: It then compares each n8n workflow with its GitHub counterpart. It checks if anything changed, if it was renamed, or if it's new.\n4. **Take Action**:\n * If a workflow is **new**, it's created on GitHub.\n * If a workflow is **updated**, the file content is changed on GitHub.\n * If a workflow was **renamed**, the old file is deleted, and a new one is created.\n * If **nothing changed**, the workflow is skipped.\n5. **Send Report**: Finally, it can send a summary report to Telegram about what happened.\n\n### 💡 What's Next?\nFuture updates will include automatic archiving of inactive workflows and performance optimizations. Follow my profile for new workflow publications!"
},
"typeVersion": 1
},
{
"id": "999b6d5e-ba67-4b98-9637-1ca8c061113e",
"name": "Detener si configuración vacía",
"type": "n8n-nodes-base.stopAndError",
"position": [
-1920,
1024
],
"parameters": {
"errorMessage": "Incomplete GitHub configuration. Please check \"Configuration\" node."
},
"typeVersion": 1
},
{
"id": "99820b4c-3ad8-43ba-bafe-9ae5f919e39f",
"name": "Verificar configuración de GitHub",
"type": "n8n-nodes-base.if",
"notes": "Pre-provisioning safe fuse",
"position": [
-2160,
864
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "0b285299-edd5-41a0-85e8-3d94246e1cff",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.repo.owner }}",
"rightValue": ""
},
{
"id": "c9f894e0-cf42-45e1-87bd-13c2bd024b48",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.repo.name }}",
"rightValue": ""
},
{
"id": "f1591996-df67-4caf-8171-a049993268d2",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.repo.path }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "02eb2192-2fce-4283-90c0-616f2e6ced5d",
"connections": {
"8c6f381b-caed-4267-abb4-6672c93f45e9": {
"main": [
[
{
"node": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"type": "main",
"index": 0
}
]
]
},
"4d426ef2-c4db-4690-bd23-48c374fcf0e1": {
"main": [
[
{
"node": "1cc5a35b-8e52-46dd-ac69-03e320f66fc8",
"type": "main",
"index": 0
},
{
"node": "921826cc-13da-4db8-8a18-fdffacbcf5bb",
"type": "main",
"index": 0
}
],
[
{
"node": "34869a4d-b687-44f7-81fd-5f71e8679a0e",
"type": "main",
"index": 0
},
{
"node": "be267762-77a4-4af5-ba26-7f3945d44b32",
"type": "main",
"index": 0
}
],
[
{
"node": "fdef22d8-7960-48e2-a046-e69b3f48ce15",
"type": "main",
"index": 0
},
{
"node": "669101cb-2d23-41c7-8e07-6e282b556cdd",
"type": "main",
"index": 0
}
],
[
{
"node": "2ababf2c-5315-4d6a-9f09-b2efaef4ab2c",
"type": "main",
"index": 0
}
],
[
{
"node": "0ac9743d-298a-49d2-b391-4c34b9d5122b",
"type": "main",
"index": 0
}
]
]
},
"3464840a-599d-4432-aade-bfd5a2930461": {
"main": [
[
{
"node": "13ba1e58-2bac-44dc-9142-b61efef7c4e1",
"type": "main",
"index": 0
}
]
]
},
"db1f95ae-42e5-4588-bb7d-5348011d9afe": {
"main": [
[
{
"node": "3464840a-599d-4432-aade-bfd5a2930461",
"type": "main",
"index": 0
}
]
]
},
"bda0d23c-f6fe-435c-8732-c1f97c313d80": {
"main": [
[
{
"node": "99820b4c-3ad8-43ba-bafe-9ae5f919e39f",
"type": "main",
"index": 0
}
]
]
},
"3576b4b3-6692-4d08-9453-697d44ac0f56": {
"main": [
[
{
"node": "4d426ef2-c4db-4690-bd23-48c374fcf0e1",
"type": "main",
"index": 0
}
]
]
},
"0c401771-b522-4091-a14f-aba4a06d6158": {
"main": [
[
{
"node": "0705a90f-00e1-46e6-b5ef-4a4efe5255cd",
"type": "main",
"index": 0
}
]
]
},
"fdef22d8-7960-48e2-a046-e69b3f48ce15": {
"main": [
[
{
"node": "669101cb-2d23-41c7-8e07-6e282b556cdd",
"type": "main",
"index": 1
}
]
]
},
"1cc5a35b-8e52-46dd-ac69-03e320f66fc8": {
"main": [
[
{
"node": "921826cc-13da-4db8-8a18-fdffacbcf5bb",
"type": "main",
"index": 1
}
]
]
},
"09450fb6-e815-4c7b-88e9-a92da4d70d16": {
"main": [
[
{
"node": "d991e89f-d795-4559-9b3f-4ecce50723b5",
"type": "main",
"index": 0
}
],
[
{
"node": "3576b4b3-6692-4d08-9453-697d44ac0f56",
"type": "main",
"index": 0
}
]
]
},
"bbf77bc7-8c35-4177-be4f-44c91682fd1a": {
"main": [
[
{
"node": "bda0d23c-f6fe-435c-8732-c1f97c313d80",
"type": "main",
"index": 0
}
]
]
},
"fdb5e4f7-8aa1-4273-8776-ac0248b893e4": {
"main": [
[
{
"node": "0c401771-b522-4091-a14f-aba4a06d6158",
"type": "main",
"index": 0
}
]
]
},
"34d67ff9-e25a-42a9-b751-dbde37ed575b": {
"main": [
[
{
"node": "98fc6907-d188-4f9a-b68e-44592bfac95b",
"type": "main",
"index": 0
}
]
]
},
"669101cb-2d23-41c7-8e07-6e282b556cdd": {
"main": [
[
{
"node": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"type": "main",
"index": 0
}
]
]
},
"be267762-77a4-4af5-ba26-7f3945d44b32": {
"main": [
[
{
"node": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"type": "main",
"index": 0
}
]
]
},
"99820b4c-3ad8-43ba-bafe-9ae5f919e39f": {
"main": [
[
{
"node": "34d67ff9-e25a-42a9-b751-dbde37ed575b",
"type": "main",
"index": 0
},
{
"node": "db1f95ae-42e5-4588-bb7d-5348011d9afe",
"type": "main",
"index": 0
}
],
[
{
"node": "999b6d5e-ba67-4b98-9637-1ca8c061113e",
"type": "main",
"index": 0
}
]
]
},
"d0af7c02-a586-4993-8c4b-8fb18c3053fe": {
"main": [
[
{
"node": "fdb5e4f7-8aa1-4273-8776-ac0248b893e4",
"type": "main",
"index": 0
}
]
]
},
"98fc6907-d188-4f9a-b68e-44592bfac95b": {
"main": [
[
{
"node": "8c6f381b-caed-4267-abb4-6672c93f45e9",
"type": "main",
"index": 0
}
]
]
},
"d991e89f-d795-4559-9b3f-4ecce50723b5": {
"main": [
[
{
"node": "d0af7c02-a586-4993-8c4b-8fb18c3053fe",
"type": "main",
"index": 0
}
]
]
},
"c6464638-d7d4-4e36-b359-f70412db2bbb": {
"main": [
[
{
"node": "b470dd47-762a-4c8a-90e0-e4f535e9b41e",
"type": "main",
"index": 1
}
]
]
},
"2ababf2c-5315-4d6a-9f09-b2efaef4ab2c": {
"main": [
[
{
"node": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"type": "main",
"index": 0
}
]
]
},
"13ba1e58-2bac-44dc-9142-b61efef7c4e1": {
"main": [
[
{
"node": "8c6f381b-caed-4267-abb4-6672c93f45e9",
"type": "main",
"index": 1
}
]
]
},
"b470dd47-762a-4c8a-90e0-e4f535e9b41e": {
"main": [
[
{
"node": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"type": "main",
"index": 0
}
]
]
},
"921826cc-13da-4db8-8a18-fdffacbcf5bb": {
"main": [
[
{
"node": "b470dd47-762a-4c8a-90e0-e4f535e9b41e",
"type": "main",
"index": 0
},
{
"node": "c6464638-d7d4-4e36-b359-f70412db2bbb",
"type": "main",
"index": 0
}
]
]
},
"34869a4d-b687-44f7-81fd-5f71e8679a0e": {
"main": [
[
{
"node": "be267762-77a4-4af5-ba26-7f3945d44b32",
"type": "main",
"index": 1
}
]
]
}
}
}¿Cómo usar este flujo de trabajo?
Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.
¿En qué escenarios es adecuado este flujo de trabajo?
Avanzado - DevOps, IA Multimodal
¿Es de pago?
Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.
Flujos de trabajo relacionados recomendados
Maksym Brashenko
@j2h4uI’m a generalist engineer who thrives on messy systems, undocumented protocols, and problems no one wants to touch Over the past 6 years, I’ve built infrastructure for managing hardware, reverse-engineered flaky devices, automated workflows end to end, and connected product logic to business reality Use my link to book an initial consultation
Compartir este flujo de trabajo