Workflow: Backup workflows to git repository on Gitea
Integrations: code, httpRequest, if, n8n, scheduleTrigger, set, splitInBatches, stickyNote

{"id": "Ef2uEM6H19K2DGUO", "meta": {"instanceId": "workflow-02e2d419", "versionId": "1.0.0", "createdAt": "2025-09-29T07:07:43.328879", "updatedAt": "2025-09-29T07:07:43.328889", "owner": "n8n-user", "license": "MIT", "category": "automation", "status": "active", "priority": "high", "environment": "production"}, "name": "Backup workflows to git repository on Gitea", "tags": ["automation", "n8n", "production-ready", "excellent", "optimized"], "nodes": [{"id": "639582ef-f13e-4844-bd10-647718079121", "name": "Globals", "type": "n8n-nodes-base.set", "position": [600, 240], "parameters": {"values": {"string": [{"name": "repo.url", "value": "{{ $env.WEBHOOK_URL }}"}, {"name": "repo.name", "value": "workflows"}, {"name": "repo.owner", "value": "n8n"}]}, "options": {}}, "typeVersion": 1, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "9df89713-220e-43b9-b234-b8f5612629cf", "name": "n8n", "type": "n8n-nodes-base.n8n", "position": [840, 240], "parameters": {"filters": {}, "requestOptions": {}}, "credentials": {"n8nApi": {"id": "ZjfxOLTTHX2CzbKa", "name": "Main N8N Account"}}, "typeVersion": 1, "notes": "This n8n node performs automated tasks as part of the workflow."}, {"id": "4b2d375c-a339-404c-babd-555bd2fc4091", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "position": [380, 240], "parameters": {"rule": {"interval": [{"field": "minutes", "minutesInterval": 45}]}}, "typeVersion": 1.2, "notes": "This scheduleTrigger node performs automated tasks as part of the workflow."}, {"id": "ea026e96-0db1-41fd-b003-2f2bf4662696", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [2620, 300], "parameters": {"height": 80, "content": "Workflow changes committed to the repository"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "9c402daa-6d03-485d-b8a0-58f1b65d396d", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [2260, 180], "parameters": {"height": 80, "content": "Check if there are any changes in the workflow"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "1d9216d9-bf8d-4945-8a58-22fb1ffc9be8", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [1800, 580], "parameters": {"height": 80, "content": "Create a new file for the workflow"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "60a3953b-d9f1-4afd-b299-e314116b96c6", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [1300, 200], "parameters": {"height": 80, "content": "Check if file exists in the repository"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "f2340ad0-71a1-4c74-8d90-bcb974b8b305", "name": "Sticky Note5", "type": "n8n-nodes-base.stickyNote", "position": [780, 180], "parameters": {"height": 80, "content": "Get all workflows"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "617bea19-341a-4e9d-b6fd-6b417e58d756", "name": "Sticky Note6", "type": "n8n-nodes-base.stickyNote", "position": [500, 180], "parameters": {"height": 80, "content": "Set variables"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "72f806d7-e30a-470b-9ba2-37fdc35de3c8", "name": "SetDataUpdateNode", "type": "n8n-nodes-base.set", "position": [1920, 240], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "0a6b769a-c66d-4784-92c7-a70caa28e1ba", "name": "item", "type": "object", "value": "={{ $node[\"ForEach\"].json }}"}]}}, "typeVersion": 3.4, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "bca5e2c4-7aa3-48df-9e5f-b31977970c28", "name": "SetDataCreateNode", "type": "n8n-nodes-base.set", "position": [1220, 640], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "0a6b769a-c66d-4784-92c7-a70caa28e1ba", "name": "item", "type": "object", "value": "={{ $node[\"ForEach\"].json }}"}]}}, "typeVersion": 3.4, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "bf74b1ea-e066-462b-9c3d-ed4a44a09a33", "name": "Base64EncodeUpdate", "type": "n8n-nodes-base.code", "position": [2140, 240], "parameters": {"language": "python", "pythonCode": "import json\nimport base64\nfrom js import Object\n\n# Assuming _input.all() returns a JavaScript object\njs_object = _input.all()\n\n# Convert the JsProxy object to a Python dictionary\ndef js_to_py(js_obj):\n    if isinstance(js_obj, (str, int, float, bool)) or js_obj is None:\n        # Base types are already Python-compatible\n        return js_obj\n    elif isinstance(js_obj, list):\n        # Convert lists recursively\n        return [js_to_py(item) for item in js_obj]\n    elif hasattr(js_obj, \"__iter__\") and not isinstance(js_obj, str):\n        # Handle JsProxy objects (JavaScript objects or arrays)\n        if hasattr(js_obj, \"keys\"):\n            # If it has keys, treat it as a dictionary\n            return {key: js_to_py(js_obj[key]) for key in Object.keys(js_obj)}\n        else:\n            # Otherwise, treat it as a list\n            return [js_to_py(item) for item in js_obj]\n    else:\n        # Fallback for other types\n        return js_obj\n\n# Convert the JavaScript object to a Python dictionary\ninput_dict = js_to_py(js_object)\n\n# Step 0: get the correct data set of the workflow\ninner_data = input_dict[0].get('json').get('item')\n\n# Step 1: Convert the dictionary to a pretty-printed JSON string\njson_string = json.dumps(inner_data, indent=4)\n\n# Step 2: Encode the JSON string to bytes\njson_bytes = json_string.encode('utf-8')\n\n# Step 3: Convert the bytes to a base64 string\nbase64_string = base64.b64encode(json_bytes).decode('utf-8')\n\n# Step 5: Create the return object with the base64 string and its SHA-256 hash\nreturn_object = {\n    \"item\": base64_string\n}\n\n# Return the object\nreturn return_object"}, "typeVersion": 2, "notes": "This code node performs automated tasks as part of the workflow."}, {"id": "2d817c66-5aa0-45c9-b851-4b5e3dbecca4", "name": "Base64EncodeCreate", "type": "n8n-nodes-base.code", "position": [1520, 640], "parameters": {"language": "python", "pythonCode": "import json\nimport base64\nfrom js import Object\n\n# Assuming _input.all() returns a JavaScript object\njs_object = _input.all()\n\n# Convert the JsProxy object to a Python dictionary\ndef js_to_py(js_obj):\n    if isinstance(js_obj, (str, int, float, bool)) or js_obj is None:\n        # Base types are already Python-compatible\n        return js_obj\n    elif isinstance(js_obj, list):\n        # Convert lists recursively\n        return [js_to_py(item) for item in js_obj]\n    elif hasattr(js_obj, \"__iter__\") and not isinstance(js_obj, str):\n        # Handle JsProxy objects (JavaScript objects or arrays)\n        if hasattr(js_obj, \"keys\"):\n            # If it has keys, treat it as a dictionary\n            return {key: js_to_py(js_obj[key]) for key in Object.keys(js_obj)}\n        else:\n            # Otherwise, treat it as a list\n            return [js_to_py(item) for item in js_obj]\n    else:\n        # Fallback for other types\n        return js_obj\n\n# Convert the JavaScript object to a Python dictionary\ninput_dict = js_to_py(js_object)\n\n# Step 0: get the correct data set of the workflow\ninner_data = input_dict[0].get('json').get('item')\n\n# Step 1: Convert the dictionary to a pretty-printed JSON string\njson_string = json.dumps(inner_data, indent=4)\n\n# Step 2: Encode the JSON string to bytes\njson_bytes = json_string.encode('utf-8')\n\n# Step 3: Convert the bytes to a base64 string\nbase64_string = base64.b64encode(json_bytes).decode('utf-8')\n\n# Step 4: Create the return object with the base64 string in 'item'\nreturn_object = {\n    \"item\": base64_string\n}\n\n# Return the object\nreturn return_object"}, "typeVersion": 2, "notes": "This code node performs automated tasks as part of the workflow."}, {"id": "41a7da89-1c8c-4100-8c30-d0788962efc1", "name": "Exist", "type": "n8n-nodes-base.if", "position": [1640, 260], "parameters": {"options": {"ignoreCase": false}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "or", "conditions": [{"id": "16a9182d-059d-4774-ba95-654fb4293fdb", "operator": {"type": "object", "operation": "notExists", "singleValue": true}, "leftValue": "={{ $json.error }}", "rightValue": 404}]}}, "executeOnce": false, "typeVersion": 2.2, "alwaysOutputData": false, "notes": "This if node performs automated tasks as part of the workflow."}, {"id": "ab9246eb-a253-4d76-b33b-5f8f12342542", "name": "Changed", "type": "n8n-nodes-base.if", "position": [2360, 240], "parameters": {"options": {}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "e0c66624-429a-4f1f-bf7b-1cc1b32bad7b", "operator": {"type": "string", "operation": "notEquals"}, "leftValue": "={{ $json.item }}", "rightValue": "={{ $('GetGitea').item.json.content }}"}]}}, "typeVersion": 2.2, "notes": "This if node performs automated tasks as part of the workflow."}, {"id": "4278a176-6496-4817-82f8-591539619673", "name": "PutGitea", "type": "n8n-nodes-base.httpRequest", "position": [2700, 360], "parameters": {"url": "{{ $env.BASE_URL }}", "method": "PUT", "options": {}, "sendBody": true, "authentication": "{{ $credentials.genericCredentialType }}", "bodyParameters": {"parameters": [{"name": "content", "value": "={{ $('Base64EncodeUpdate').item.json.item }}"}, {"name": "sha", "value": "={{ $('GetGitea').item.json.sha }}"}]}, "genericAuthType": "httpHeaderAuth"}, "credentials": {"httpHeaderAuth": {"id": "gTvBAgkOmqhl5Nmr", "name": "Gitea Token"}}, "typeVersion": 4.2, "notes": "This httpRequest node performs automated tasks as part of the workflow."}, {"id": "12307a61-e7cc-42f9-a7c7-8abbcab9e3ab", "name": "GetGitea", "type": "n8n-nodes-base.httpRequest", "onError": "continueRegularOutput", "position": [1380, 260], "parameters": {"url": "{{ $env.BASE_URL }}", "options": {}, "authentication": "{{ $credentials.genericCredentialType }}", "genericAuthType": "httpHeaderAuth"}, "credentials": {"httpHeaderAuth": {"id": "gTvBAgkOmqhl5Nmr", "name": "Gitea Token"}}, "typeVersion": 4.2, "notes": "This httpRequest node performs automated tasks as part of the workflow."}, {"id": "24fda439-bb23-4392-a297-d8070907f9e6", "name": "PostGitea", "type": "n8n-nodes-base.httpRequest", "position": [1920, 640], "parameters": {"url": "{{ $env.BASE_URL }}", "method": "POST", "options": {}, "sendBody": true, "authentication": "{{ $credentials.genericCredentialType }}", "bodyParameters": {"parameters": [{"name": "content", "value": "={{ $json.item }}"}]}, "genericAuthType": "httpHeaderAuth"}, "credentials": {"httpHeaderAuth": {"id": "gTvBAgkOmqhl5Nmr", "name": "Gitea Token"}}, "typeVersion": 4.2, "notes": "This httpRequest node performs automated tasks as part of the workflow."}, {"id": "43a60315-d381-4ac4-be4c-f6a158651a00", "name": "ForEach", "type": "n8n-nodes-base.splitInBatches", "position": [1060, 240], "parameters": {"options": {}}, "executeOnce": false, "typeVersion": 3, "notes": "This splitInBatches node performs automated tasks as part of the workflow."}, {"id": "88578dc4-2398-48d0-b0ba-2198b35bb994", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [380, 440], "parameters": {"width": 560, "height": 1620, "content": "### **\ud83d\udccc Setup Guide for Backup Workflows to Git Repository on Gitea**\n\n#### **\ud83d\udd27 1. Configure Global Variables**\nGo to the **Globals** node and update the following:\n- **`repo.url`** \u2192 `{{ $env.WEBHOOK_URL }}` *(Replace with your actual Gitea URL)*\n- **`repo.name`** \u2192 `workflows` *(Repository name where backups will be stored)*\n- **`repo.owner`** \u2192 `octoleo` *(Gitea account that owns the repository)*\n\n\ud83d\udccc **These settings define where workflows will be backed up.**\n\n---\n\n#### **\ud83d\udd11 2. Set Up Gitea Authentication**\n1\ufe0f\u20e3 **In Gitea:**\n- Generate a **Personal Access Token** under **Settings \u2192 Applications \u2192 Generate Token**\n- Ensure the token has **repo read/write permissions**\n\n2\ufe0f\u20e3 **In the Credentials Manager:**\n- Create a new **Gitea Token** credential\n- Set the **Name** as `Authorization`\n- Set the **Value** as:\n```\nBearer YOUR_PERSONAL_ACCESS_TOKEN\n```\n\ud83d\udccc **Ensure there is a space after `Bearer` before the token!**\n\n---\n\n#### **\ud83d\udd17 3. Connect Gitea Credentials to Git Nodes**\n- Open each of these **three Git nodes**:\n- **GetGitea** \u2192 Retrieves existing repository data\n- **PutGitea** \u2192 Updates workflows\n- **PostGitea** \u2192 Adds new workflows\n\n- Assign the **Gitea Token** credential to each node.\n\n\ud83d\udccc **These nodes handle pushing your workflows to Gitea.**\n\n---\n\n#### **\ud83c\udf10 4. Set Up API Credentials for Workflow Retrieval**\n- Locate the API request node that **fetches workflows**.\n- Add your **API authentication credentials** (Token or Basic Auth).\n\n\ud83d\udccc **This ensures the workflow can fetch all available workflows from your system.**\n\n---\n\n#### **\ud83d\udee0\ufe0f 5. Test & Activate the Workflow**\n\u2705 **Run the workflow manually** \u2192 Check that workflows are being backed up correctly.\n\u2705 **Review the Gitea repository** \u2192 Ensure the files are updated.\n\u2705 **Enable the scheduled trigger** \u2192 Automates backups at defined intervals.\n\n\ud83d\udccc **The workflow automatically checks for changes before committing updates!**\n\n---\n\n### **\ud83d\ude80 Done! Your Workflows Are Now Backed Up Securely!**\n\ud83d\udcac Have issues? **Reach out on the forum for help!**"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}], "active": false, "pinData": {}, "settings": {"executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner", "errorWorkflow": null, "timezone": "UTC", "executionTimeout": 3600, "maxExecutions": 1000, "retryOnFail": true, "retryCount": 3, "retryDelay": 1000}, "versionId": "84ba3f3f-fbc8-4792-8e28-198f515fef4e", "staticData": {"node:Schedule Trigger": {"recurrenceRules": []}}, "connections": {"4278a176-6496-4817-82f8-591539619673": {"main": [[{"node": "error-handler-4278a176-6496-4817-82f8-591539619673", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-2cbce554", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-f00568fc", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-888ce4ef", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-19be108e", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-72ed8aed", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-81d2b0ca", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-b6d46bfe", "type": "main", "index": 0}], [{"node": "error-handler-4278a176-6496-4817-82f8-591539619673-f9562ad7", "type": "main", "index": 0}]]}, "12307a61-e7cc-42f9-a7c7-8abbcab9e3ab": {"main": [[{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-6e5dec9d", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-27cc3ddd", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-b9703373", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-600045c3", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-70a14a30", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-11ce7326", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-a0fc2f93", "type": "main", "index": 0}], [{"node": "error-handler-12307a61-e7cc-42f9-a7c7-8abbcab9e3ab-f4ed9c46", "type": "main", "index": 0}]]}, "24fda439-bb23-4392-a297-d8070907f9e6": {"main": [[{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-bcb85834", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-e0713352", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-33896229", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-74296b88", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-2db51312", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-1b85a121", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-6b24a1d0", "type": "main", "index": 0}], [{"node": "error-handler-24fda439-bb23-4392-a297-d8070907f9e6-3ada7ba8", "type": "main", "index": 0}]]}}, "triggerCount": 1, "description": "Automated workflow: Backup workflows to git repository on Gitea. This workflow integrates 9 different services: stickyNote, httpRequest, code, scheduleTrigger, n8n. It contains 26 nodes and follows best practices for error handling and security.", "notes": "Excellent quality workflow: Backup workflows to git repository on Gitea. This workflow has been optimized for production use with comprehensive error handling, security, and documentation."}