고급 n8n 워크플로우와 GitHub 동기화
고급
이것은DevOps, Multimodal AI분야의자동화 워크플로우로, 38개의 노드를 포함합니다.주로 If, N8n, Set, Code, Merge 등의 노드를 사용하며. GitHub를 활용한 지능형 변경 감지 자동화 워크플로우 백업
사전 요구사항
- •GitHub Personal Access Token
- •Telegram Bot Token
사용된 노드 (38)
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"id": "dmvTNU9rfNdgTeSp",
"meta": {
"instanceId": "e7cc7f71b8002726158f14502c7243f892bdf0befb8af4790197437e4666e71e"
},
"name": "Advanced n8n Workflow Sync with GitHub",
"tags": [],
"nodes": [
{
"id": "09450fb6-e815-4c7b-88e9-a92da4d70d16",
"name": "항목 반복 처리",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-368,
912
],
"parameters": {
"options": {}
},
"executeOnce": false,
"typeVersion": 3
},
{
"id": "34869a4d-b687-44f7-81fd-5f71e8679a0e",
"name": "파일 내용 업데이트 및 커밋",
"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": "일정 트리거",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2912,
864
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "34d67ff9-e25a-42a9-b751-dbde37ed575b",
"name": "모든 워크플로우 가져오기",
"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": "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": "변경 사항 결정",
"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": "기존 파일 삭제",
"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": "새 파일 생성(이름 변경)",
"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": "생성 후 병합(이름 변경)",
"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": "업데이트 후 병합",
"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": "파일 목록 조회",
"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": "병합",
"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": "스티커 메모",
"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": "스티커 메모1",
"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": "스티커 메모2",
"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": "스티커 메모3",
"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": "스티커 메모4",
"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": "요약 배열 구성",
"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": "요약 렌더링",
"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": "변경 사항 확인",
"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": "워크플로우 매개변수 추출",
"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": "파일 가져오기",
"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": "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": "메시지 전송",
"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": "새 파일 생성",
"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": "생성 후 병합",
"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": "스티커 메모5",
"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": "스티커 메모6",
"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": "작업 없음",
"type": "n8n-nodes-base.noOp",
"position": [
1056,
2976
],
"parameters": {},
"typeVersion": 1
},
{
"id": "7e069ed2-7303-4db5-b0b3-f18edbfb88da",
"name": "스티커 메모7",
"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": "중단 및 오류",
"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": "스티커 메모8",
"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": "라우터",
"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": "삭제 후 병합(이름 변경)",
"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": "구성",
"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": "스티커 메모10",
"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": "빈 구성 시 중단",
"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": "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
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - 데브옵스, 멀티모달 AI
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
GitHub 동기화 대시보드 - V2
提交 기록과 롤백 기능을 갖춘 GitHub 워크플로우 버전 관리 대시보드
If
N8n
Set
+
If
N8n
Set
94 노드Eduard
데브옵스
자동화된 n8n 워크플로우 백업至 GitHub 및 삭제 추적
삭제 추적이 포함된 GitHub 자동화 n8n 워크플로우 백업
If
N8n
Set
+
If
N8n
Set
31 노드Marcial Ambriz
데브옵스
완전히 맞춤화된 3통의 이메일 후속 작업이 포함된 GPT-4 기반 콜드 이메일 워크플로
GPT-4, Mailgun 및 Supabase를 사용한 개인 맞춤형 콜드 이메일 시퀀스 자동화
If
Set
Code
+
If
Set
Code
100 노드Paul
리드 육성
Google Drive, GitHub 및 메시지 알림을 사용한 자동화된 워크플로우 백업 시스템
Google Drive, GitHub, 메시지 알림을 활용한 자동화 워크플로우 백업 시스템
If
N8n
Set
+
If
N8n
Set
20 노드Khairul Muhtadin
콘텐츠 제작
Typebot 플로우와 GitHub 양방향 동기화, Typebot API 사용
Typebot API를 활용한 Typebot 플로우와 GitHub 양방향 동기화
If
Set
Code
+
If
Set
Code
31 노드Marcial Ambriz
데브옵스
완전한 B2B 판매 프로세스: Apollo 잠재 고객 생성, Mailgun 프로모션 및 AI 응답 관리
완전한 B2B 판매 프로세스: Apollo 잠재 고객 생성, Mailgun 확장 및 AI 응답 관리
If
Set
Code
+
If
Set
Code
116 노드Paul
콘텐츠 제작
워크플로우 정보
난이도
고급
노드 수38
카테고리2
노드 유형13
저자
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
외부 링크
n8n.io에서 보기 →
이 워크플로우 공유