{
  "id": "d503b7d073584c49a8425ca094782c9a",
  "name": "Manin Studio — приватный GitHub backup n8n",
  "nodes": [
    {
      "id": "29ad5da1-43c5-4f1f-a2ba-cd410d8e3b9b",
      "name": "Каждый день",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 3,
              "triggerAtMinute": 0
            }
          ]
        }
      }
    },
    {
      "id": "590f96f7-7ab4-451f-927e-af3b3c35416e",
      "name": "Настройки",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ],
      "parameters": {
        "jsCode": "// Замените значения, затем подключите отдельные credentials к HTTP-узлам.\nconst config={ n8nBase:'https://n8n.example.invalid', owner:'REPLACE_OWNER', repo:'REPLACE_PRIVATE_REPO', branch:'main', path:'n8n/workflows.json', chatId:'REPLACE_CHAT_ID' };\nif (!/^https:\\/\\//.test(config.n8nBase) || config.n8nBase.includes('.invalid') || [config.owner,config.repo,config.chatId].some(x=>x.startsWith('REPLACE_'))) throw new Error('Заполните Настройки перед запуском.');\nif (!/^[A-Za-z0-9_.-]+$/.test(config.owner) || !/^[A-Za-z0-9_.-]+$/.test(config.repo)) throw new Error('Некорректные owner/repo.');\nreturn [{json:config}];"
      }
    },
    {
      "id": "8ef5e6a2-46d2-4e24-8b09-f3ba8f362870",
      "name": "Проверить приватность репозитория",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        440,
        300
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://api.github.com/repos/' + $json.owner + '/' + $json.repo }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 10000
        }
      }
    },
    {
      "id": "0f4fe44f-d8cf-4d30-92a8-014861a3b43c",
      "name": "Только приватный репозиторий",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        300
      ],
      "parameters": {
        "jsCode": "const repo=$input.first().json; if(repo.private !== true) throw new Error('Остановка: репозиторий GitHub должен быть приватным.'); return [{json:{private:true}}];"
      }
    },
    {
      "id": "4f67ea2f-5858-4e73-b22d-287d812987f7",
      "name": "Получить workflow из n8n",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        880,
        300
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ $('Настройки').first().json.n8nBase + '/api/v1/workflows?limit=100' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 15000,
          "pagination": {
            "pagination": {
              "paginationMode": "updateAParameterInEachRequest",
              "parameters": {
                "parameters": [
                  {
                    "type": "qs",
                    "name": "cursor",
                    "value": "={{ $response.body.nextCursor }}"
                  }
                ]
              },
              "paginationCompleteWhen": "other",
              "completeExpression": "={{ !$response.body.nextCursor }}",
              "limitPagesFetched": true,
              "maxRequests": 100
            }
          }
        }
      }
    },
    {
      "id": "f97933e7-7f2e-4a6d-9e1c-ab56e631e45f",
      "name": "Очистить и собрать архив",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        300
      ],
      "parameters": {
        "jsCode": "const input=$input.all();\nif (input.at(-1)?.json?.nextCursor) throw new Error('Пагинация не завершена: резервная копия не заменена.');\nconst all=input.flatMap(x=>Array.isArray(x.json.data)?x.json.data:(x.json.nodes?[x.json]:[]));\nif(!all.length) throw new Error('Нет workflow или неожиданный ответ API: архив не заменён.');\nconst sensitive=/password|passwd|secret|token|api.?key|authorization|cookie|private.?key|credential/i;\nfunction clean(value,key=''){\n if(sensitive.test(key)) return '[REDACTED]';\n if(Array.isArray(value)) return value.map(v=>clean(v));\n if(value && typeof value==='object'){const out={}; for(const k of Object.keys(value).sort()) { if(k==='credentials'||k==='pinData'||k==='staticData')continue; out[k]=clean(value[k],k); } return out;}\n if(typeof value==='string' && /(bearer\\s+[a-z0-9._-]{12,}|gh[pousr]_[A-Za-z0-9_]{16,}|sk-[A-Za-z0-9_-]{16,})/i.test(value)) return '[REDACTED]';\n return value;\n}\nconst workflows=all.map(w=>clean({id:w.id,name:w.name,nodes:w.nodes,connections:w.connections,settings:w.settings,active:false})).sort((a,b)=>String(a.name).localeCompare(String(b.name)));\nconst content=JSON.stringify(workflows,null,2)+'\\n';\nreturn [{json:{content,contentBase64:Buffer.from(content,'utf8').toString('base64'),count:workflows.length}}];"
      }
    },
    {
      "id": "23114062-bcb3-4c34-b683-9fedf3d9a4df",
      "name": "Найти предыдущую копию",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1320,
        300
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://api.github.com/repos/' + $('Настройки').first().json.owner + '/' + $('Настройки').first().json.repo + '/contents/' + $('Настройки').first().json.path + '?ref=' + encodeURIComponent($('Настройки').first().json.branch) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 10000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      }
    },
    {
      "id": "63fc80d0-0419-4ab2-9717-04cfb2bd7b33",
      "name": "Сравнить содержимое",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1540,
        300
      ],
      "parameters": {
        "jsCode": "const res=$input.first().json; const data=$('Очистить и собрать архив').first().json;\nconst status=Number(res.statusCode); if(status!==200 && status!==404) throw new Error('GitHub API вернул HTTP '+status);\nconst old=status===200 ? Buffer.from(String(res.body?.content||'').replace(/\\s/g,''),'base64').toString('utf8') : null;\nreturn [{json:{...data,sha:status===200?String(res.body?.sha||''):'',changed:old!==data.content}}];"
      }
    },
    {
      "id": "719940a7-bbfa-48f5-836f-9e57ef0cd888",
      "name": "Есть изменения?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1760,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "d09397ba-6ab1-4158-aefb-eb646a1e4346",
              "leftValue": "={{ $json.changed }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "7e44caba-d926-4e06-bf3d-3d1718bacfbd",
      "name": "Сохранить версию в GitHub",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1980,
        220
      ],
      "parameters": {
        "method": "PUT",
        "url": "={{ 'https://api.github.com/repos/' + $('Настройки').first().json.owner + '/' + $('Настройки').first().json.repo + '/contents/' + $('Настройки').first().json.path }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 15000
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({message:'Update n8n workflows backup',content:$json.contentBase64,branch:$('Настройки').first().json.branch,...($json.sha ? {sha:$json.sha}:{})}) }}"
      }
    },
    {
      "id": "288b7a62-6cbd-475e-a97c-2cf36348c0ec",
      "name": "Сообщить о новой версии",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2200,
        220
      ],
      "parameters": {
        "resource": "message",
        "operation": "sendMessage",
        "chatId": "={{ $('Настройки').first().json.chatId }}",
        "text": "Резервная копия n8n обновлена в приватном репозитории.",
        "additionalFields": {
          "appendAttribution": false
        }
      }
    },
    {
      "id": "4f7bb624-f394-49d0-8e24-2b6b87ec4801",
      "name": "Сообщить без изменений",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1980,
        400
      ],
      "parameters": {
        "resource": "message",
        "operation": "sendMessage",
        "chatId": "={{ $('Настройки').first().json.chatId }}",
        "text": "Резервная копия n8n: изменений нет.",
        "additionalFields": {
          "appendAttribution": false
        }
      }
    },
    {
      "id": "6b3c93e1-0b74-47a8-a3a4-7cc72be9bb9b",
      "name": "Ошибка резервного копирования",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        0,
        560
      ],
      "parameters": {}
    },
    {
      "id": "690d8af3-7bcf-4f57-aae6-25453c7be935",
      "name": "Безопасное уведомление об ошибке",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        560
      ],
      "parameters": {
        "jsCode": "return [{json:{message:'Резервное копирование n8n завершилось ошибкой. Подробности проверьте в журнале n8n.'}}];"
      }
    },
    {
      "id": "3e955fec-e21b-4964-bbef-5697be59898e",
      "name": "Уведомить об ошибке",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        440,
        560
      ],
      "parameters": {
        "resource": "message",
        "operation": "sendMessage",
        "chatId": "REPLACE_WITH_YOUR_CHAT_ID",
        "text": "={{ $json.message }}",
        "additionalFields": {
          "appendAttribution": false
        }
      }
    }
  ],
  "connections": {
    "Каждый день": {
      "main": [
        [
          {
            "node": "Настройки",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Настройки": {
      "main": [
        [
          {
            "node": "Проверить приватность репозитория",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Проверить приватность репозитория": {
      "main": [
        [
          {
            "node": "Только приватный репозиторий",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Только приватный репозиторий": {
      "main": [
        [
          {
            "node": "Получить workflow из n8n",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Получить workflow из n8n": {
      "main": [
        [
          {
            "node": "Очистить и собрать архив",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Очистить и собрать архив": {
      "main": [
        [
          {
            "node": "Найти предыдущую копию",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Найти предыдущую копию": {
      "main": [
        [
          {
            "node": "Сравнить содержимое",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Сравнить содержимое": {
      "main": [
        [
          {
            "node": "Есть изменения?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Есть изменения?": {
      "main": [
        [
          {
            "node": "Сохранить версию в GitHub",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Сообщить без изменений",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Сохранить версию в GitHub": {
      "main": [
        [
          {
            "node": "Сообщить о новой версии",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ошибка резервного копирования": {
      "main": [
        [
          {
            "node": "Безопасное уведомление об ошибке",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Безопасное уведомление об ошибке": {
      "main": [
        [
          {
            "node": "Уведомить об ошибке",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "none",
    "saveDataSuccessExecution": "none"
  },
  "active": false,
  "pinData": {}
}
