Automatisation Gitea avec n8n : sauvegarde de workflows
Ce workflow n8n a pour objectif de sauvegarder automatiquement des workflows dans un dĂ©pĂŽt Git sur Gitea. Dans un contexte oĂč la gestion des workflows est cruciale pour les Ă©quipes de dĂ©veloppement, cette automatisation permet de garantir que toutes les modifications sont enregistrĂ©es et facilement accessibles. Les cas d'usage incluent la sauvegarde rĂ©guliĂšre des configurations de workflows, ce qui rĂ©duit le risque de perte de donnĂ©es et facilite la collaboration au sein des Ă©quipes.
- Ătape 1 : le dĂ©clencheur est un planificateur qui active le workflow Ă intervalles rĂ©guliers.
- Ătape 2 : les donnĂ©es des workflows sont prĂ©parĂ©es Ă l'aide de plusieurs nĆuds de type 'Sticky Note' pour stocker des informations pertinentes.
- Ătape 3 : les nĆuds 'Set' sont utilisĂ©s pour structurer les donnĂ©es avant leur envoi.
- Ătape 4 : le workflow vĂ©rifie si des modifications ont Ă©tĂ© apportĂ©es aux workflows existants grĂące Ă des conditions.
- Ătape 5 : en fonction des rĂ©sultats, les donnĂ©es sont encodĂ©es en Base64 et envoyĂ©es Ă Gitea via des requĂȘtes HTTP. Cette automatisation n8n offre un gain de temps significatif et assure une traçabilitĂ© des modifications, ce qui est essentiel pour toute organisation cherchant Ă optimiser ses processus de dĂ©veloppement.
Workflow n8n Gitea, sauvegarde, workflows : vue d'ensemble
SchĂ©ma des nĆuds et connexions de ce workflow n8n, gĂ©nĂ©rĂ© Ă partir du JSON n8n.
Workflow n8n Gitea, sauvegarde, workflows : dĂ©tail des nĆuds
Inscris-toi pour voir l'intégralité du workflow
Inscription gratuite
S'inscrire gratuitementBesoin d'aide ?{
"id": "Ef2uEM6H19K2DGUO",
"meta": {
"templateId": "2532",
"templateCredsSetupCompleted": true
},
"name": "Backup workflows to git repository on Gitea",
"tags": [
{
"id": "UWNX4AzSneYNvTQI",
"name": "Gitea",
"createdAt": "2025-01-28T23:10:06.823Z",
"updatedAt": "2025-01-28T23:10:06.823Z"
},
{
"id": "4b7Bs9T0Cagsg5tT",
"name": "Git",
"createdAt": "2025-01-28T23:10:26.545Z",
"updatedAt": "2025-01-28T23:10:26.545Z"
},
{
"id": "HiN3ehC2KkAp5kVs",
"name": "Backup",
"createdAt": "2025-01-28T23:10:38.878Z",
"updatedAt": "2025-01-28T23:10:38.878Z"
}
],
"nodes": [
{
"id": "639582ef-f13e-4844-bd10-647718079121",
"name": "Globals",
"type": "n8n-nodes-base.set",
"position": [
600,
240
],
"parameters": {
"values": {
"string": [
{
"name": "repo.url",
"value": "https://git.vdm.dev"
},
{
"name": "repo.name",
"value": "workflows"
},
{
"name": "repo.owner",
"value": "n8n"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"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
},
{
"id": "4278a176-6496-4817-82f8-591539619673",
"name": "PutGitea",
"type": "n8n-nodes-base.httpRequest",
"position": [
2700,
360
],
"parameters": {
"url": "={{ $('Globals').item.json.repo.url }}/api/v1/repos/{{ $('Globals').item.json.repo.owner }}/{{ $('Globals').item.json.repo.name }}/contents/{{ encodeURIComponent($('GetGitea').item.json.name) }}",
"method": "PUT",
"options": {},
"sendBody": true,
"authentication": "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
},
{
"id": "12307a61-e7cc-42f9-a7c7-8abbcab9e3ab",
"name": "GetGitea",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
1380,
260
],
"parameters": {
"url": "={{ $('Globals').item.json.repo.url }}/api/v1/repos/{{ encodeURIComponent($('Globals').item.json.repo.owner) }}/{{ encodeURIComponent($('Globals').item.json.repo.name) }}/contents/{{ encodeURIComponent($json.name) }}.json",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "gTvBAgkOmqhl5Nmr",
"name": "Gitea Token"
}
},
"typeVersion": 4.2
},
{
"id": "24fda439-bb23-4392-a297-d8070907f9e6",
"name": "PostGitea",
"type": "n8n-nodes-base.httpRequest",
"position": [
1920,
640
],
"parameters": {
"url": "={{ $('Globals').item.json.repo.url }}/api/v1/repos/{{ $('Globals').item.json.repo.owner }}/{{ $('Globals').item.json.repo.name }}/contents/{{ encodeURIComponent($('ForEach').item.json.name) }}.json",
"method": "POST",
"options": {},
"sendBody": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "content",
"value": "={{ $json.item }}"
}
]
},
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "gTvBAgkOmqhl5Nmr",
"name": "Gitea Token"
}
},
"typeVersion": 4.2
},
{
"id": "43a60315-d381-4ac4-be4c-f6a158651a00",
"name": "ForEach",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1060,
240
],
"parameters": {
"options": {}
},
"executeOnce": false,
"typeVersion": 3
},
{
"id": "88578dc4-2398-48d0-b0ba-2198b35bb994",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
440
],
"parameters": {
"width": 560,
"height": 1620,
"content": "### **đ Setup Guide for Backup Workflows to Git Repository on Gitea**\n\n#### **đ§ 1. Configure Global Variables**\nGo to the **Globals** node and update the following:\n- **`repo.url`** â `https://your-gitea-instance.com` *(Replace with your actual Gitea URL)*\n- **`repo.name`** â `workflows` *(Repository name where backups will be stored)*\n- **`repo.owner`** â `octoleo` *(Gitea account that owns the repository)*\n\nđ **These settings define where workflows will be backed up.**\n\n---\n\n#### **đ 2. Set Up Gitea Authentication**\n1ïžâŁ **In Gitea:**\n- Generate a **Personal Access Token** under **Settings â Applications â Generate Token**\n- Ensure the token has **repo read/write permissions**\n\n2ïžâŁ **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đ **Ensure there is a space after `Bearer` before the token!**\n\n---\n\n#### **đ 3. Connect Gitea Credentials to Git Nodes**\n- Open each of these **three Git nodes**:\n- **GetGitea** â Retrieves existing repository data\n- **PutGitea** â Updates workflows\n- **PostGitea** â Adds new workflows\n\n- Assign the **Gitea Token** credential to each node.\n\nđ **These nodes handle pushing your workflows to Gitea.**\n\n---\n\n#### **đ 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đ **This ensures the workflow can fetch all available workflows from your system.**\n\n---\n\n#### **đ ïž 5. Test & Activate the Workflow**\nâ
**Run the workflow manually** â Check that workflows are being backed up correctly.\nâ
**Review the Gitea repository** â Ensure the files are updated.\nâ
**Enable the scheduled trigger** â Automates backups at defined intervals.\n\nđ **The workflow automatically checks for changes before committing updates!**\n\n---\n\n### **đ Done! Your Workflows Are Now Backed Up Securely!**\nđŹ Have issues? **Reach out on the forum for help!**"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "84ba3f3f-fbc8-4792-8e28-198f515fef4e",
"staticData": {
"node:Schedule Trigger": {
"recurrenceRules": []
}
},
"connections": {
"n8n": {
"main": [
[
{
"node": "ForEach",
"type": "main",
"index": 0
}
]
]
},
"Exist": {
"main": [
[
{
"node": "SetDataUpdateNode",
"type": "main",
"index": 0
}
],
[
{
"node": "SetDataCreateNode",
"type": "main",
"index": 0
}
]
]
},
"Changed": {
"main": [
[
{
"node": "PutGitea",
"type": "main",
"index": 0
}
],
[
{
"node": "ForEach",
"type": "main",
"index": 0
}
]
]
},
"ForEach": {
"main": [
[],
[
{
"node": "GetGitea",
"type": "main",
"index": 0
}
]
]
},
"Globals": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"GetGitea": {
"main": [
[
{
"node": "Exist",
"type": "main",
"index": 0
}
]
]
},
"PutGitea": {
"main": [
[
{
"node": "ForEach",
"type": "main",
"index": 0
}
]
]
},
"PostGitea": {
"main": [
[
{
"node": "ForEach",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Globals",
"type": "main",
"index": 0
}
]
]
},
"SetDataCreateNode": {
"main": [
[
{
"node": "Base64EncodeCreate",
"type": "main",
"index": 0
}
]
]
},
"SetDataUpdateNode": {
"main": [
[
{
"node": "Base64EncodeUpdate",
"type": "main",
"index": 0
}
]
]
},
"Base64EncodeCreate": {
"main": [
[
{
"node": "PostGitea",
"type": "main",
"index": 0
}
]
]
},
"Base64EncodeUpdate": {
"main": [
[
{
"node": "Changed",
"type": "main",
"index": 0
}
]
]
}
},
"triggerCount": 1
}Workflow n8n Gitea, sauvegarde, workflows : pour qui est ce workflow ?
Ce workflow s'adresse aux équipes de développement et aux responsables de projets utilisant n8n et Gitea. Il est conçu pour des entreprises de taille moyenne à grande, ayant une certaine expérience technique et cherchant à automatiser la gestion de leurs workflows.
Workflow n8n Gitea, sauvegarde, workflows : problÚme résolu
Ce workflow résout le problÚme de la perte de données et de la gestion inefficace des workflows. En automatisant la sauvegarde des configurations, il élimine les risques de perte d'informations critiques et réduit le temps consacré à la gestion manuelle des sauvegardes. Les utilisateurs bénéficient d'une traçabilité améliorée et d'une sécurité accrue pour leurs projets.
Workflow n8n Gitea, sauvegarde, workflows : étapes du workflow
Ătape 1 : le dĂ©clencheur planifiĂ© active le workflow Ă des intervalles dĂ©finis.
- Ătape 1 : les nĆuds 'Sticky Note' collectent et prĂ©parent les informations nĂ©cessaires.
- Ătape 2 : les nĆuds 'Set' organisent les donnĂ©es pour l'envoi.
- Ătape 3 : des conditions vĂ©rifient si des modifications ont eu lieu.
- Ătape 4 : les donnĂ©es sont encodĂ©es en Base64.
- Ătape 5 : les donnĂ©es sont envoyĂ©es Ă Gitea via des requĂȘtes HTTP, assurant ainsi la sauvegarde des workflows.
Workflow n8n Gitea, sauvegarde, workflows : guide de personnalisation
Pour personnaliser ce workflow, vous pouvez modifier l'URL de Gitea dans les nĆuds HTTP pour pointer vers votre dĂ©pĂŽt. Adaptez Ă©galement les paramĂštres des nĆuds 'Set' pour inclure les informations spĂ©cifiques de vos workflows. Pensez Ă ajuster la frĂ©quence du dĂ©clencheur planifiĂ© selon vos besoins. Vous pouvez Ă©galement ajouter des nĆuds supplĂ©mentaires pour intĂ©grer d'autres outils ou services si nĂ©cessaire. Enfin, assurez-vous de sĂ©curiser vos requĂȘtes HTTP en configurant les mĂ©thodes d'authentification appropriĂ©es.