Workflow: puq-docker-minio-deploy
Integrations: code, if, respondToWebhook, set, ssh, stickyNote, switch, webhook

{"id": "IJYpB2CIAdLk8Umg", "meta": {"instanceId": "workflow-aff0b176", "versionId": "1.0.0", "createdAt": "2025-09-29T07:07:43.405320", "updatedAt": "2025-09-29T07:07:43.405328", "owner": "n8n-user", "license": "MIT", "category": "automation", "status": "active", "priority": "high", "environment": "production"}, "name": "puq-docker-minio-deploy", "tags": ["automation", "n8n", "production-ready", "excellent", "optimized"], "nodes": [{"id": "d79fe295-a0b0-4871-8382-67d9af5d0d2c", "name": "If Node", "type": "n8n-nodes-base.if", "position": [-2060, -320], "parameters": {"options": {}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "or", "conditions": [{"id": "b702e607-888a-42c9-b9a7-f9d2a64dfccd", "operator": {"type": "string", "operation": "equals"}, "leftValue": "={{ $json.server_domain }}", "rightValue": "={{ $('API').item.json.body.server_domain }}"}]}}, "typeVersion": 2.2, "notes": "This if node performs automated tasks as part of the workflow."}, {"id": "52c088af-95ae-411f-b1fa-f50b8ea99b58", "name": "Parametrs", "type": "n8n-nodes-base.set", "position": [-2280, -320], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "a6328600-7ee0-4031-9bdb-fcee99b79658", "name": "server_domain", "type": "string", "value": "d01-test.uuq.pl"}, {"id": "370ddc4e-0fc0-48f6-9b30-ebdfba72c62f", "name": "clients_dir", "type": "string", "value": "/opt/docker/clients"}, {"id": "92202bb8-6113-4bc5-9a29-79d238456df2", "name": "mount_dir", "type": "string", "value": "/mnt"}, {"id": "baa52df2-9c10-42b2-939f-f05ea85ea2be", "name": "screen_left", "type": "string", "value": "{{"}, {"id": "2b19ed99-2630-412a-98b6-4be44d35d2e7", "name": "screen_right", "type": "string", "value": "}}"}]}}, "typeVersion": 3.4, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "9814333d-a9c1-4787-aed1-116db9395b88", "name": "API", "type": "n8n-nodes-base.webhook", "position": [-2600, -320], "webhookId": "73068cf8-be17-4b10-b9a3-744f7e4843b0", "parameters": {"path": "docker-minio", "options": {}, "httpMethod": ["POST"], "responseMode": "responseNode", "authentication": "{{ $credentials.basicAuth }}", "multipleMethods": true}, "credentials": {"httpBasicAuth": {"id": "J4uXcnEb1SIQ2VN7", "name": "MinIO"}}, "typeVersion": 2, "notes": "This webhook node performs automated tasks as part of the workflow."}, {"id": "a3e0156c-8033-4829-ab57-06e3708a7a09", "name": "422-Invalid server domain", "type": "n8n-nodes-base.respondToWebhook", "position": [-2100, 0], "parameters": {"options": {"responseCode": 422}, "respondWith": "json", "responseBody": "[{\n  \"status\": \"error\",\n  \"error\": \"Invalid server domain\"\n}]"}, "typeVersion": 1.1, "alwaysOutputData": false, "notes": "This respondToWebhook node performs automated tasks as part of the workflow."}, {"id": "a5f410f8-ca52-4e85-b76f-651756c80de5", "name": "Code1", "type": "n8n-nodes-base.code", "position": [800, -240], "parameters": {"mode": "runOnceForEachItem", "jsCode": "try {\n  if ($json.stdout === 'success') {\n    return {\n      json: {\n        status: 'success',\n        message: '',\n        data: '',\n      }\n    };\n  }\n\n  const parsedData = JSON.parse($json.stdout);\n\n  return {\n    json: {\n      status: parsedData.status === 'error' ? 'error' : 'success',\n      message: parsedData.message || (parsedData.status === 'error' ? 'An error occurred' : ''),\n      data: parsedData || '',\n    }\n  };\n\n} catch (error) {\n  return {\n    json: {\n      status: 'error',\n      message: $json.stdout??$json.error,\n      data: '',\n    }\n  };\n}"}, "executeOnce": false, "retryOnFail": false, "typeVersion": 2, "alwaysOutputData": false, "notes": "This code node performs automated tasks as part of the workflow."}, {"id": "e162574f-c3ce-4fd0-8b31-d251ea360389", "name": "SSH", "type": "n8n-nodes-base.ssh", "onError": "continueErrorOutput", "position": [500, -240], "parameters": {"cwd": "=/", "command": "={{ $json.sh }}"}, "credentials": {"sshPassword": {"id": "Cyjy61UWHwD2Xcd8", "name": "d01-test.uuq.pl-puq"}}, "executeOnce": true, "typeVersion": 1, "notes": "This ssh node performs automated tasks as part of the workflow."}, {"id": "70f53357-5cdc-428c-876c-77036d6736cc", "name": "Container Actions", "type": "n8n-nodes-base.switch", "position": [-1680, 160], "parameters": {"rules": {"values": [{"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "66ad264d-5393-410c-bfa3-011ab8eb234a", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_start"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "b48957a0-22c0-4ac0-82ef-abd9e7ab0207", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_stop"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "727971bf-4218-41c1-9b07-22df4b947852", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_mount_disk"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "0c80b1d9-e7ca-4cf3-b3ac-b40fdf4dd8f8", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_unmount_disk"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "755e1a9f-667a-4022-9cb5-3f8153f62e95", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_get_acl"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "8d75626f-789e-42fc-be5e-3a4e93a9bbc6", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_set_acl"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "c49d811a-735c-42f4-8b77-d0cd47b3d2b8", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_get_net"}]}, "renameOutput": true}]}, "options": {}}, "typeVersion": 3.2, "notes": "This switch node performs automated tasks as part of the workflow."}, {"id": "901a657d-873c-4b92-9949-d03e73a5313c", "name": "Service Actions", "type": "n8n-nodes-base.switch", "position": [-900, -1300], "parameters": {"rules": {"values": [{"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "3afdd2f1-fe93-47c2-95cd-bac9b1d94eeb", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "test_connection"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "102f10e9-ec6c-4e63-ba95-0fe6c7dc0bd1", "operator": {"type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "create"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "f62dfa34-6751-4b34-adcc-3d6ba1b21a8c", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "suspend"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "384d2026-b753-4c27-94c2-8f4fc189eb5f", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "unsuspend"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "0e190a97-827a-4e87-8222-093ff7048b21", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "terminate"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "6f7832f3-b61d-4517-ab6b-6007998136dd", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "change_package"}]}, "renameOutput": true}]}, "options": {}}, "typeVersion": 3.2, "notes": "This switch node performs automated tasks as part of the workflow."}, {"id": "1c59a844-f4ef-422f-abbf-288a55e11934", "name": "API answer", "type": "n8n-nodes-base.respondToWebhook", "position": [820, 0], "parameters": {"options": {"responseCode": 200}, "respondWith": "allIncomingItems"}, "typeVersion": 1.1, "alwaysOutputData": true, "notes": "This respondToWebhook node performs automated tasks as part of the workflow."}, {"id": "c2019d97-1012-4089-84c3-305308f8603f", "name": "Inspect", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1160, -380], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/{{ $('API').item.json.body.domain }}\"\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}\"\n\nINSPECT_JSON=\"{}\"\nif sudo docker ps -a --filter \"name=$CONTAINER_NAME\" | grep -q \"$CONTAINER_NAME\"; then\n  INSPECT_JSON=$(sudo docker inspect \"$CONTAINER_NAME\")\nfi\n\necho \"{\\\"inspect\\\": $INSPECT_JSON}\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "a274a2d1-2382-48a0-a94d-6ef89cd22a57", "name": "Stat", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1060, -240], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/{{ $('API').item.json.body.domain }}\"\nSTATUS_FILE=\"$COMPOSE_DIR/status.json\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}\"\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}\"\n\n# Initialize empty container data\nINSPECT_JSON=\"{}\"\nSTATS_JSON=\"{}\"\n\n# Check if container is running\nif sudo docker ps -a --filter \"name=$CONTAINER_NAME\" | grep -q \"$CONTAINER_NAME\"; then\n  # Get Docker inspect info in JSON (as raw string)\n  INSPECT_JSON=$(sudo docker inspect \"$CONTAINER_NAME\")\n\n  # Get Docker stats info in JSON (as raw string)\n  STATS_JSON=$(sudo docker stats --no-stream --format \"{{ $('Parametrs').item.json.screen_left }}json .{{ $('Parametrs').item.json.screen_right }}\" \"$CONTAINER_NAME\")\n  STATS_JSON=${STATS_JSON:-'{}'}\nfi\n\n# Initialize disk info variables\nMOUNT_USED=\"N/A\"\nMOUNT_FREE=\"N/A\"\nMOUNT_TOTAL=\"N/A\"\nMOUNT_PERCENT=\"N/A\"\nIMG_SIZE=\"N/A\"\nIMG_PERCENT=\"N/A\"\nDISK_STATS_IMG=\"N/A\"\n\n# Check if mount directory exists and is accessible\nif [ -d \"$MOUNT_DIR\" ]; then\n  if mount | grep -q \"$MOUNT_DIR\"; then\n    # Get disk usage for mounted directory\n    DISK_STATS_MOUNT=$(df -h \"$MOUNT_DIR\" | tail -n 1)\n    MOUNT_USED=$(echo \"$DISK_STATS_MOUNT\" | awk '{print $3}')\n    MOUNT_FREE=$(echo \"$DISK_STATS_MOUNT\" | awk '{print $4}')\n    MOUNT_TOTAL=$(echo \"$DISK_STATS_MOUNT\" | awk '{print $2}')\n    MOUNT_PERCENT=$(echo \"$DISK_STATS_MOUNT\" | awk '{print $5}')\n  fi\nfi\n\n# Check if image file exists\nif [ -f \"$IMG_FILE\" ]; then\n  # Get disk usage for image file\n  IMG_SIZE=$(du -sh \"$IMG_FILE\" | awk '{print $1}')\nfi\n\n# Manually create a combined JSON object\nFINAL_JSON=\"{\\\"inspect\\\": $INSPECT_JSON, \\\"stats\\\": $STATS_JSON, \\\"disk\\\": {\\\"mounted\\\": {\\\"used\\\": \\\"$MOUNT_USED\\\", \\\"free\\\": \\\"$MOUNT_FREE\\\", \\\"total\\\": \\\"$MOUNT_TOTAL\\\", \\\"percent\\\": \\\"$MOUNT_PERCENT\\\"}, \\\"img_file\\\": {\\\"size\\\": \\\"$IMG_SIZE\\\"}}}\"\n\n# Output the result\necho \"$FINAL_JSON\"\n\nexit 0"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "3e80ebbe-bb8e-4fec-ab20-ba69271a48f8", "name": "Start", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1180, 140], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/{{ $('API').item.json.body.domain }}\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}\"\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\nif ! df -h | grep -q \"$MOUNT_DIR\"; then\n    handle_error \"The file $IMG_FILE is not mounted to $MOUNT_DIR\"\nfi\n\nif sudo docker ps --filter \"name={{ $('API').item.json.body.domain }}\" --filter \"status=running\" -q | grep -q .; then\n    handle_error \"{{ $('API').item.json.body.domain }} container is running\"\nfi\n\n# Change to the compose directory\ncd \"$COMPOSE_DIR\" > /dev/null 2>&1 || handle_error \"Failed to change directory to $COMPOSE_DIR\"\n\n# Start the Docker containers\nif ! sudo docker-compose up -d > /dev/null 2>error.log; then\n    ERROR_MSG=$(tail -n 10 error.log)\n    handle_error \"Docker-compose failed: $ERROR_MSG\"\nfi\n\n# Success\necho \"success\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "4e13ceea-a01f-438c-ba6f-27f55b88798b", "name": "Stop", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1060, 240], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/{{ $('API').item.json.body.domain }}\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}\"\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\n# Check if Docker container is running\nif ! sudo docker ps --filter \"name={{ $('API').item.json.body.domain }}\" --filter \"status=running\" -q | grep -q .; then\n    handle_error \"{{ $('API').item.json.body.domain }} container is not running\"\nfi\n\n# Stop and remove the Docker containers (also remove associated volumes)\nif ! sudo docker-compose -f \"$COMPOSE_DIR/docker-compose.yml\" down > /dev/null 2>&1; then\n    handle_error \"Failed to stop and remove docker-compose containers\"\nfi\n\necho \"success\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "afa7a4e2-85a6-420b-9e33-30802e9cbb7b", "name": "Test Connection1", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-220, -1320], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\n# Function to log an error, print to console\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\n# Check if Docker is installed\nif ! command -v docker &> /dev/null; then\n    handle_error \"Docker is not installed\"\nfi\n\n# Check if Docker service is running\nif ! systemctl is-active --quiet docker; then\n    handle_error \"Docker service is not running\"\nfi\n\n# Check if nginx-proxy container is running\nif ! sudo docker ps --filter \"name=nginx-proxy\" --filter \"status=running\" -q > /dev/null; then\n    handle_error \"nginx-proxy container is not running\"\nfi\n\n# Check if letsencrypt-nginx-proxy-companion container is running\nif ! sudo docker ps --filter \"name=letsencrypt-nginx-proxy-companion\" --filter \"status=running\" -q > /dev/null; then\n    handle_error \"letsencrypt-nginx-proxy-companion container is not running\"\nfi\n\n# If everything is successful\necho \"success\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "6c8261b4-f024-4b8e-a11c-1f2305e03e1d", "name": "Deploy", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-220, -1120], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\n# Get values for variables from templates\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nCOMPOSE_FILE=\"$COMPOSE_DIR/docker-compose.yml\"\nSTATUS_FILE=\"$COMPOSE_DIR/status\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\nVHOST_DIR=\"/opt/docker/nginx-proxy/nginx/vhost.d\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/$DOMAIN\"\nDOCKER_COMPOSE_TEXT='{{ $('Deploy-docker-compose').item.json[\"docker-compose\"] }}'\n\nNGINX_MAIN_ACL_FILE=\"$NGINX_DIR/$DOMAIN\"_acl\n\nNGINX_MAIN_TEXT='{{ $('nginx').item.json['main'] }}'\nNGINX_MAIN_FILE=\"$NGINX_DIR/$DOMAIN\"\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\n\nNGINX_MAIN_LOCATION_TEXT='{{ $('nginx').item.json['main_location'] }}'\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$DOMAIN\"_location\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\n\n\nNGINX_CONSOLE_ACL_FILE=\"$NGINX_DIR/console.$DOMAIN\"_acl\n\nNGINX_CONSOLE_TEXT='{{ $('nginx').item.json['console'] }}'\nNGINX_CONSOLE_FILE=\"$NGINX_DIR/console.$DOMAIN\"\nVHOST_CONSOLE_FILE=\"$VHOST_DIR/console.$DOMAIN\"\n\nNGINX_CONSOLE_LOCATION_TEXT='{{ $('nginx').item.json['console_location'] }}'\nNGINX_CONSOLE_LOCATION_FILE=\"$NGINX_DIR/console.$DOMAIN\"_location\nVHOST_CONSOLE_LOCATION_FILE=\"$VHOST_DIR/console.$DOMAIN\"_location\n\n\nDISK_SIZE=\"{{ $('API').item.json.body.disk }}\"\n\n# Function to handle errors: write to the status file and print the message to console\nhandle_error() {\n    STATUS_JSON=\"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    echo \"$STATUS_JSON\" | sudo tee \"$STATUS_FILE\" > /dev/null  # Write error to the status file\n    echo \"error: $1\"  # Print the error message to the console\n    exit 1  # Exit the script with an error code\n}\n\n# Check if the directory already exists. If yes, exit with an error.\nif [ -d \"$COMPOSE_DIR\" ]; then\n    echo \"error: Directory $COMPOSE_DIR already exists\"\n    exit 1\nfi\n\n# Create necessary directories with permissions\nsudo mkdir -p \"$COMPOSE_DIR\" > /dev/null 2>&1 || handle_error \"Failed to create $COMPOSE_DIR\"\nsudo mkdir -p \"$NGINX_DIR\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_DIR\"\nsudo mkdir -p \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to create $MOUNT_DIR\"\n\n# Set permissions on the created directories\nsudo chmod -R 777 \"$COMPOSE_DIR\" > /dev/null 2>&1 || handle_error \"Failed to set permissions on $COMPOSE_DIR\"\nsudo chmod -R 777 \"$NGINX_DIR\" > /dev/null 2>&1 || handle_error \"Failed to set permissions on $NGINX_DIR\"\nsudo chmod -R 777 \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to set permissions on $MOUNT_DIR\"\n\n# Create docker-compose.yml file\necho \"$DOCKER_COMPOSE_TEXT\" | sudo tee \"$COMPOSE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $COMPOSE_FILE\"\n\n# Create NGINX configuration files\necho \"\" | sudo tee \"$NGINX_MAIN_ACL_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_ACL_FILE\"\necho \"\" | sudo tee \"$NGINX_CONSOLE_ACL_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_CONSOLE_ACL_FILE\"\n\necho \"$NGINX_MAIN_TEXT\" | sudo tee \"$NGINX_MAIN_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_FILE\"\necho \"$NGINX_MAIN_LOCATION_TEXT\" | sudo tee \"$NGINX_MAIN_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_LOCATION_FILE\"\n\necho \"$NGINX_CONSOLE_TEXT\" | sudo tee \"$NGINX_CONSOLE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_CONSOLE_FILE\"\necho \"$NGINX_CONSOLE_LOCATION_TEXT\" | sudo tee \"$NGINX_CONSOLE_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_CONSOLE_LOCATION_FILE\"\n\n# Change to the compose directory\ncd \"$COMPOSE_DIR\" > /dev/null 2>&1 || handle_error \"Failed to change directory to $COMPOSE_DIR\"\n\n# Create data.img file if it doesn't exist\nif [ ! -f \"$IMG_FILE\" ]; then\n    sudo fallocate -l \"$DISK_SIZE\"G \"$IMG_FILE\" > /dev/null 2>&1 || sudo truncate -s \"$DISK_SIZE\"G \"$IMG_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $IMG_FILE\"\n    sudo mkfs.ext4 \"$IMG_FILE\" > /dev/null 2>&1 || handle_error \"Failed to format $IMG_FILE\"  # Format the image as ext4\n    sync  # Synchronize the data to disk\nfi\n\n# Add an entry to /etc/fstab for mounting if not already present\nif ! grep -q \"$IMG_FILE\" /etc/fstab; then\n    echo \"$IMG_FILE $MOUNT_DIR ext4 loop 0 0\" | sudo tee -a /etc/fstab > /dev/null || handle_error \"Failed to add entry to /etc/fstab\"\nfi\n\n# Mount all entries in /etc/fstab\nsudo mount -a || handle_error \"Failed to mount entries from /etc/fstab\"\n\n# Set permissions on the mount directory\nsudo chmod -R 777 \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to set permissions on $MOUNT_DIR\"\n\n# Copy NGINX configuration files instead of creating symbolic links\nsudo cp -f \"$NGINX_MAIN_FILE\" \"$VHOST_MAIN_FILE\" || handle_error \"Failed to copy $NGINX_MAIN_FILE to $VHOST_MAIN_FILE\"\nsudo chmod 777 \"$VHOST_MAIN_FILE\" || handle_error \"Failed to set permissions on $VHOST_MAIN_FILE\"\n\nsudo cp -f \"$NGINX_MAIN_LOCATION_FILE\" \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_MAIN_LOCATION_FILE to $VHOST_MAIN_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_MAIN_LOCATION_FILE\"\n\nsudo cp -f \"$NGINX_CONSOLE_FILE\" \"$VHOST_CONSOLE_FILE\" || handle_error \"Failed to copy $NGINX_CONSOLE_FILE to $VHOST_CONSOLE_FILE\"\nsudo chmod 777 \"$VHOST_CONSOLE_FILE\" || handle_error \"Failed to set permissions on $VHOST_CONSOLE_FILE\"\n\nsudo cp -f \"$NGINX_CONSOLE_LOCATION_FILE\" \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_CONSOLE_LOCATION_FILE to $VHOST_CONSOLE_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_CONSOLE_LOCATION_FILE\"\n\n# Start Docker containers using docker-compose\nif ! sudo docker-compose up -d > /dev/null 2>error.log; then\n    ERROR_MSG=$(tail -n 10 error.log)  # Read the last 10 lines from error.log\n    handle_error \"Docker-compose failed: $ERROR_MSG\"\nfi\n\n# If everything is successful, update the status file and print success message\necho \"active\" | sudo tee \"$STATUS_FILE\" > /dev/null\necho \"success\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "d2f48f02-1a75-445e-832b-f9bf1a4d4b71", "name": "Suspend", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-220, -960], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nCOMPOSE_FILE=\"$COMPOSE_DIR/docker-compose.yml\"\nSTATUS_FILE=\"$COMPOSE_DIR/status\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\nVHOST_DIR=\"/opt/docker/nginx-proxy/nginx/vhost.d\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/$DOMAIN\"\n\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\nVHOST_CONSOLE_FILE=\"$VHOST_DIR/console.$DOMAIN\"\nVHOST_CONSOLE_LOCATION_FILE=\"$VHOST_DIR/console.$DOMAIN\"_location\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    STATUS_JSON=\"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    echo \"$STATUS_JSON\" | sudo tee \"$STATUS_FILE\" > /dev/null\n    echo \"error: $1\"\n    exit 1\n}\n\n# Stop and remove Docker containers (also remove associated volumes)\nif [ -f \"$COMPOSE_FILE\" ]; then\n    if ! sudo docker-compose -f \"$COMPOSE_FILE\" down > /dev/null 2>&1; then\n        handle_error \"Failed to stop and remove docker-compose containers\"\n    fi\nelse\n    echo \"Warning: docker-compose.yml not found, skipping container stop.\"\nfi\n\n# Remove mount entry from /etc/fstab if it exists\nif grep -q \"$IMG_FILE\" /etc/fstab; then\n    sudo sed -i \"\\|$(printf '%s\\n' \"$IMG_FILE\" | sed 's/[.[\\*^$]/\\\\&/g')|d\" /etc/fstab\nfi\n\n# Unmount the image if it is mounted\nif mount | grep -q \"$MOUNT_DIR\"; then\n    sudo umount \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to unmount $MOUNT_DIR\"\nfi\n\n# Remove the mount directory\nif [ -d \"$MOUNT_DIR\" ]; then\n    sudo rm -rf \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to remove $MOUNT_DIR\"\nfi\n\n# Remove NGINX configuration files\n[ -f \"$VHOST_MAIN_FILE\" ] && sudo rm -f \"$VHOST_MAIN_FILE\" || handle_error \"Warning: $VHOST_MAIN_FILE not found.\"\n[ -f \"$VHOST_MAIN_LOCATION_FILE\" ] && sudo rm -f \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Warning: $VHOST_MAIN_LOCATION_FILE not found.\"\n[ -f \"$VHOST_CONSOLE_FILE\" ] && sudo rm -f \"$VHOST_CONSOLE_FILE\" || handle_error \"Warning: $VHOST_CONSOLE_FILE not found.\"\n[ -f \"$VHOST_CONSOLE_LOCATION_FILE\" ] && sudo rm -f \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Warning: $VHOST_CONSOLE_LOCATION_FILE not found.\"\n\n# Update status\necho \"suspended\" | sudo tee \"$STATUS_FILE\" > /dev/null\n\n# Success\necho \"success\"\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "87b7f7c2-7f7e-49e5-846c-3f92d436b5b6", "name": "Terminated", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-220, -620], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nCOMPOSE_FILE=\"$COMPOSE_DIR/docker-compose.yml\"\nSTATUS_FILE=\"$COMPOSE_DIR/status\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\nVHOST_DIR=\"/opt/docker/nginx-proxy/nginx/vhost.d\"\n\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\nVHOST_CONSOLE_FILE=\"$VHOST_DIR/console.$DOMAIN\"\nVHOST_CONSOLE_LOCATION_FILE=\"$VHOST_DIR/console.$DOMAIN\"_location\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/$DOMAIN\"\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    STATUS_JSON=\"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    echo \"error: $1\"\n    exit 1\n}\n\n# Stop and remove the Docker containers\nif [ -f \"$COMPOSE_FILE\" ]; then\n    sudo docker-compose -f \"$COMPOSE_FILE\" down > /dev/null 2>&1\nfi\n\n# Remove the mount entry from /etc/fstab if it exists\nif grep -q \"$IMG_FILE\" /etc/fstab; then\n    sudo sed -i \"\\|$(printf '%s\\n' \"$IMG_FILE\" | sed 's/[.[\\*^$]/\\\\&/g')|d\" /etc/fstab\nfi\n\n# Unmount the image if it is still mounted\nif mount | grep -q \"$MOUNT_DIR\"; then\n    sudo umount \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to unmount $MOUNT_DIR\"\nfi\n\n# Remove all related directories and files\nfor item in \"$COMPOSE_DIR\" \"$VHOST_MAIN_FILE\" \"$VHOST_MAIN_LOCATION_FILE\" \"$VHOST_CONSOLE_FILE\" \"$VHOST_CONSOLE_LOCATION_FILE\"; do\n    if [ -e \"$item\" ]; then\n        sudo rm -rf \"$item\" || handle_error \"Failed to remove $item\"\n    fi\ndone\n\necho \"success\"\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "610dc730-9a2f-4fbf-bbbe-ce31d1494422", "name": "Unsuspend", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-220, -800], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nCOMPOSE_FILE=\"$COMPOSE_DIR/docker-compose.yml\"\nSTATUS_FILE=\"$COMPOSE_DIR/status\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\nVHOST_DIR=\"/opt/docker/nginx-proxy/nginx/vhost.d\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/$DOMAIN\"\nDOCKER_COMPOSE_TEXT='{{ $('Deploy-docker-compose').item.json[\"docker-compose\"] }}'\n\nNGINX_MAIN_ACL_FILE=\"$NGINX_DIR/$DOMAIN\"_acl\n\nNGINX_MAIN_TEXT='{{ $('nginx').item.json['main'] }}'\nNGINX_MAIN_FILE=\"$NGINX_DIR/$DOMAIN\"\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\n\nNGINX_MAIN_LOCATION_TEXT='{{ $('nginx').item.json['main_location'] }}'\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$DOMAIN\"_location\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\n\nNGINX_CONSOLE_ACL_FILE=\"$NGINX_DIR/console.$DOMAIN\"_acl\n\nNGINX_CONSOLE_TEXT='{{ $('nginx').item.json['console'] }}'\nNGINX_CONSOLE_FILE=\"$NGINX_DIR/console.$DOMAIN\"\nVHOST_CONSOLE_FILE=\"$VHOST_DIR/console.$DOMAIN\"\n\nNGINX_CONSOLE_LOCATION_TEXT='{{ $('nginx').item.json['console_location'] }}'\nNGINX_CONSOLE_LOCATION_FILE=\"$NGINX_DIR/console.$DOMAIN\"_location\nVHOST_CONSOLE_LOCATION_FILE=\"$VHOST_DIR/console.$DOMAIN\"_location\n\nDISK_SIZE=\"{{ $('API').item.json.body.disk }}\"\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    STATUS_JSON=\"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    echo \"$STATUS_JSON\" | sudo tee \"$STATUS_FILE\" > /dev/null\n    echo \"error: $1\"\n    exit 1\n}\n\nupdate_nginx_acl() {\n    ACL_FILE=$1\n    LOCATION_FILE=$2\n    \n    if [ -s \"$ACL_FILE\" ]; then  # \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0444\u0430\u0439\u043b \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0438 \u043d\u0435 \u043f\u0443\u0441\u0442\u043e\u0439\n        VALID_LINES=$(grep -vE '^\\s*$' \"$ACL_FILE\")  # \u0423\u0431\u0438\u0440\u0430\u0435\u043c \u043f\u0443\u0441\u0442\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438\n        if [ -n \"$VALID_LINES\" ]; then  # \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043d\u0435\u043f\u0443\u0441\u0442\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438\n            while IFS= read -r line; do\n                echo \"allow $line;\" | sudo tee -a \"$LOCATION_FILE\" > /dev/null || handle_error \"Failed to update $LOCATION_FILE\"\n            done <<< \"$VALID_LINES\"\n            echo \"deny all;\" | sudo tee -a \"$LOCATION_FILE\" > /dev/null || handle_error \"Failed to update $LOCATION_FILE\"\n        fi\n    fi\n}\n\n# Create necessary directories with permissions\nfor dir in \"$COMPOSE_DIR\" \"$NGINX_DIR\" \"$MOUNT_DIR\"; do\n    sudo mkdir -p \"$dir\" || handle_error \"Failed to create $dir\"\n    sudo chmod -R 777 \"$dir\" || handle_error \"Failed to set permissions on $dir\"\ndone\n\n# Check if the image is already mounted using fstab\nif ! grep -q \"$IMG_FILE\" /etc/fstab; then\n    echo \"$IMG_FILE $MOUNT_DIR ext4 loop 0 0\" | sudo tee -a /etc/fstab > /dev/null || handle_error \"Failed to add fstab entry for $IMG_FILE\"\nfi\n\n# Apply the fstab changes and mount the image\nif ! mount | grep -q \"$MOUNT_DIR\"; then\n    sudo mount -a || handle_error \"Failed to mount image using fstab\"\nfi\n\n# Create docker-compose.yml file\necho \"$DOCKER_COMPOSE_TEXT\" | sudo tee \"$COMPOSE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $COMPOSE_FILE\"\n\n# Create NGINX configuration files\necho \"$NGINX_MAIN_TEXT\" | sudo tee \"$NGINX_MAIN_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_FILE\"\necho \"$NGINX_MAIN_LOCATION_TEXT\" | sudo tee \"$NGINX_MAIN_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_FILE\"\n\necho \"$NGINX_CONSOLE_TEXT\" | sudo tee \"$NGINX_CONSOLE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_CONSOLE_FILE\"\necho \"$NGINX_CONSOLE_LOCATION_TEXT\" | sudo tee \"$NGINX_CONSOLE_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_CONSOLE_LOCATION_FILE\"\n\n# Copy NGINX configuration files instead of creating symbolic links\nsudo cp -f \"$NGINX_MAIN_FILE\" \"$VHOST_MAIN_FILE\" || handle_error \"Failed to copy $NGINX_MAIN_FILE to $VHOST_MAIN_FILE\"\nsudo chmod 777 \"$VHOST_MAIN_FILE\" || handle_error \"Failed to set permissions on $VHOST_MAIN_FILE\"\n\nsudo cp -f \"$NGINX_MAIN_LOCATION_FILE\" \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_MAIN_LOCATION_FILE to $VHOST_MAIN_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_MAIN_LOCATION_FILE\"\n\nsudo cp -f \"$NGINX_CONSOLE_FILE\" \"$VHOST_CONSOLE_FILE\" || handle_error \"Failed to copy $NGINX_CONSOLE_FILE to $VHOST_CONSOLE_FILE\"\nsudo chmod 777 \"$VHOST_CONSOLE_FILE\" || handle_error \"Failed to set permissions on $VHOST_CONSOLE_FILE\"\n\nsudo cp -f \"$NGINX_CONSOLE_LOCATION_FILE\" \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_CONSOLE_LOCATION_FILE to $VHOST_CONSOLE_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_CONSOLE_LOCATION_FILE\"\n\nupdate_nginx_acl \"$NGINX_MAIN_ACL_FILE\" \"$VHOST_MAIN_LOCATION_FILE\"\nupdate_nginx_acl \"$NGINX_CONSOLE_ACL_FILE\" \"$VHOST_CONSOLE_LOCATION_FILE\"\n\n# Change to the compose directory\ncd \"$COMPOSE_DIR\" || handle_error \"Failed to change directory to $COMPOSE_DIR\"\n\n# Start Docker containers using docker-compose\n> error.log\nif ! sudo docker-compose up -d > error.log 2>&1; then\n    ERROR_MSG=$(tail -n 10 error.log)  # Read the last 10 lines from error.log\n    handle_error \"Docker-compose failed: $ERROR_MSG\"\nfi\n\n# If everything is successful, update the status file and print success message\necho \"active\" | sudo tee \"$STATUS_FILE\" > /dev/null\necho \"success\"\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "8d6893c3-9597-43fe-bbec-ba3c55d2c220", "name": "Mount Disk", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1180, 360], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/{{ $('API').item.json.body.domain }}\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}\"\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\n# Create necessary directories with permissions\nsudo mkdir -p \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to create $MOUNT_DIR\"\nsudo chmod 777 \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to set permissions on $MOUNT_DIR\"\n\nif df -h | grep -q \"$MOUNT_DIR\"; then\n    handle_error \"The file $IMG_FILE is mounted to $MOUNT_DIR\"\nfi\n\nif ! grep -q \"$IMG_FILE\" /etc/fstab; then\n    echo \"$IMG_FILE $MOUNT_DIR ext4 loop 0 0\" | sudo tee -a /etc/fstab > /dev/null || handle_error \"Failed to add entry to /etc/fstab\"\nfi\n\nsudo mount -a || handle_error \"Failed to mount entries from /etc/fstab\"\n\necho \"success\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "1b2182c6-7080-4b09-9699-2ba7c3292913", "name": "Unmount Disk", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1060, 460], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/{{ $('API').item.json.body.domain }}\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}\"\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\nif ! df -h | grep -q \"$MOUNT_DIR\"; then\n    handle_error \"The file $IMG_FILE is not mounted to $MOUNT_DIR\"\nfi\n\n# Remove the mount entry from /etc/fstab if it exists\nif grep -q \"$IMG_FILE\" /etc/fstab; then\n    sudo sed -i \"\\|$(printf '%s\\n' \"$IMG_FILE\" | sed 's/[.[\\*^$]/\\\\&/g')|d\" /etc/fstab\nfi\n\n# Unmount the image if it is mounted (using fstab)\nif mount | grep -q \"$MOUNT_DIR\"; then\n    sudo umount \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to unmount $MOUNT_DIR\"\nfi\n\n# Remove the mount directory (if needed)\nif ! sudo rm -rf \"$MOUNT_DIR\" > /dev/null 2>&1; then\n    handle_error \"Failed to remove $MOUNT_DIR\"\nfi\n\necho \"success\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "dd0cd3d9-876e-485c-94ed-f69e6f26c62b", "name": "Log", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1180, -100], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}\"\nLOGS_JSON=\"{}\"\n\n# Function to return error in JSON format\nhandle_error() {\n    echo \"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    exit 1\n}\n\n# Check if the container exists\nif ! sudo docker ps -a | grep -q \"$CONTAINER_NAME\" > /dev/null 2>&1; then\n    handle_error \"Container $CONTAINER_NAME not found\"\nfi\n\n# Get logs of the container\nLOGS=$(sudo docker logs --tail 1000 \"$CONTAINER_NAME\" 2>&1)\nif [ $? -ne 0 ]; then\n    handle_error \"Failed to retrieve logs for $CONTAINER_NAME\"\nfi\n\n# Escape double quotes in logs for valid JSON\nLOGS_ESCAPED=$(echo \"$LOGS\" | sed 's/\"/\\\\\"/g' | sed ':a;N;$!ba;s/\\n/\\\\n/g')\n\n# Format logs as JSON\nLOGS_JSON=\"{\\\"logs\\\": \\\"$LOGS_ESCAPED\\\"}\"\n\necho \"$LOGS_JSON\"\nexit 0"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "64e41e91-62b3-4346-874b-e952201fecb5", "name": "ChangePackage", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-220, -440], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\n# Get values for variables from templates\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nCOMPOSE_FILE=\"$COMPOSE_DIR/docker-compose.yml\"\nSTATUS_FILE=\"$COMPOSE_DIR/status\"\nIMG_FILE=\"$COMPOSE_DIR/data.img\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\nVHOST_DIR=\"/opt/docker/nginx-proxy/nginx/vhost.d\"\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/$DOMAIN\"\nDOCKER_COMPOSE_TEXT='{{ $('Deploy-docker-compose').item.json[\"docker-compose\"] }}'\n\nNGINX_MAIN_TEXT='{{ $('nginx').item.json['main'] }}'\nNGINX_MAIN_FILE=\"$NGINX_DIR/$DOMAIN\"\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\n\nNGINX_MAIN_LOCATION_TEXT='{{ $('nginx').item.json['main_location'] }}'\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$DOMAIN\"_location\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\n\nNGINX_CONSOLE_TEXT='{{ $('nginx').item.json['console'] }}'\nNGINX_CONSOLE_FILE=\"$NGINX_DIR/console.$DOMAIN\"\nVHOST_CONSOLE_FILE=\"$VHOST_DIR/console.$DOMAIN\"\n\nNGINX_CONSOLE_LOCATION_TEXT='{{ $('nginx').item.json['console_location'] }}'\nNGINX_CONSOLE_LOCATION_FILE=\"$NGINX_DIR/console.$DOMAIN\"_location\nVHOST_CONSOLE_LOCATION_FILE=\"$VHOST_DIR/console.$DOMAIN\"_location\n\nDISK_SIZE=\"{{ $('API').item.json.body.disk }}\"\n\n# Function to log an error, write to status file, and print to console\nhandle_error() {\n    STATUS_JSON=\"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    echo \"$STATUS_JSON\" | sudo tee \"$STATUS_FILE\" > /dev/null\n    echo \"error: $1\"\n    exit 1\n}\n\n# Check if the compose file exists before stopping the container\nif [ -f \"$COMPOSE_FILE\" ]; then\n    sudo docker-compose -f \"$COMPOSE_FILE\" down > /dev/null 2>&1 || handle_error \"Failed to stop containers\"\nelse\n    handle_error \"docker-compose.yml not found\"\nfi\n\n# Unmount the image if it is currently mounted\nif mount | grep -q \"$MOUNT_DIR\"; then\n    sudo umount \"$MOUNT_DIR\" > /dev/null 2>&1 || handle_error \"Failed to unmount $MOUNT_DIR\"\nfi\n\n# Create docker-compose.yml file\necho \"$DOCKER_COMPOSE_TEXT\" | sudo tee \"$COMPOSE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $COMPOSE_FILE\"\n\n# Create NGINX configuration files\necho \"$NGINX_MAIN_TEXT\" | sudo tee \"$NGINX_MAIN_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_FILE\"\necho \"$NGINX_MAIN_LOCATION_TEXT\" | sudo tee \"$NGINX_MAIN_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_LOCATION_FILE\"\n\necho \"$NGINX_CONSOLE_TEXT\" | sudo tee \"$NGINX_CONSOLE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_CONSOLE_FILE\"\necho \"$NGINX_CONSOLE_LOCATION_TEXT\" | sudo tee \"$NGINX_CONSOLE_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_CONSOLE_LOCATION_FILE\"\n\n# Resize the disk image if it exists\nif [ -f \"$IMG_FILE\" ]; then\n    sudo truncate -s \"$DISK_SIZE\"G \"$IMG_FILE\" > /dev/null 2>&1 || handle_error \"Failed to resize $IMG_FILE (truncate)\"\n    sudo e2fsck -fy \"$IMG_FILE\" > /dev/null 2>&1 || handle_error \"Filesystem check failed on $IMG_FILE\"\n    sudo resize2fs \"$IMG_FILE\" > /dev/null 2>&1 || handle_error \"Failed to resize filesystem on $IMG_FILE\"\nelse\n    handle_error \"Disk image $IMG_FILE does not exist\"\nfi\n\n# Mount the disk only if it is not already mounted\nif ! mount | grep -q \"$MOUNT_DIR\"; then\n    sudo mount -a || handle_error \"Failed to mount entries from /etc/fstab\"\nfi\n\n# Change to the compose directory\ncd \"$COMPOSE_DIR\" > /dev/null 2>&1 || handle_error \"Failed to change directory to $COMPOSE_DIR\"\n\n# Copy NGINX configuration files instead of creating symbolic links\nsudo cp -f \"$NGINX_MAIN_FILE\" \"$VHOST_MAIN_FILE\" || handle_error \"Failed to copy $NGINX_MAIN_FILE to $VHOST_MAIN_FILE\"\nsudo chmod 777 \"$VHOST_MAIN_FILE\" || handle_error \"Failed to set permissions on $VHOST_MAIN_FILE\"\n\nsudo cp -f \"$NGINX_MAIN_LOCATION_FILE\" \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_MAIN_LOCATION_FILE to $VHOST_MAIN_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_MAIN_LOCATION_FILE\"\n\nsudo cp -f \"$NGINX_CONSOLE_FILE\" \"$VHOST_CONSOLE_FILE\" || handle_error \"Failed to copy $NGINX_CONSOLE_FILE to $VHOST_CONSOLE_FILE\"\nsudo chmod 777 \"$VHOST_CONSOLE_FILE\" || handle_error \"Failed to set permissions on $VHOST_CONSOLE_FILE\"\n\nsudo cp -f \"$NGINX_CONSOLE_LOCATION_FILE\" \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_CONSOLE_LOCATION_FILE to $VHOST_CONSOLE_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_CONSOLE_LOCATION_FILE\"\n\n# Start Docker containers using docker-compose\nif ! sudo docker-compose up -d > /dev/null 2>error.log; then\n    ERROR_MSG=$(tail -n 10 error.log)  # Read the last 10 lines from error.log\n    handle_error \"Docker-compose failed: $ERROR_MSG\"\nfi\n\n# Update status file\necho \"active\" | sudo tee \"$STATUS_FILE\" > /dev/null\n\necho \"success\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "d7688118-55bb-4934-aac7-507bd3a3e956", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [-2640, -1280], "parameters": {"color": 6, "width": 639, "height": 909, "content": "## \ud83d\udc4b Welcome to PUQ Docker MinIO deploy!\n## Template for MinIO: API Backend for WHMCS/WISECP by PUQcloud\n\nv.1\n\nThis is an n8n template that creates an API backend for the WHMCS/WISECP module developed by PUQcloud.\n\n## Setup Instructions\n\n### 1. Configure API Webhook and SSH Access\n- Create a Credential (Basic Auth) for the **Webhook API Block** in n8n.\n- Create a Credential for **SSH access** to a server with Docker installed (**SSH Block**).\n\n### 2. Modify Template Parameters\nIn the **Parameters** block of the template, update the following settings:\n\n- `server_domain` \u2013 must match the domain of the WHMCS/WISECP Docker server.\n- `clients_dir` \u2013 directory where user data related to Docker and disks will be stored.\n- `mount_dir` \u2013 default mount point for the container disk (recommended not to change).\n\n**Do not modify** the following technical parameters:\n\n- `screen_left`\n- `screen_right`\n\n## Additional Resources\n- Full documentation: [{{ $env.WEBHOOK_URL }}]({{ $env.WEBHOOK_URL }}\n- WHMCS module: [{{ $env.WEBHOOK_URL }}]({{ $env.WEBHOOK_URL }}\n\n"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "e8b68657-ae60-4558-8ea0-768dba92fcba", "name": "Deploy-docker-compose", "type": "n8n-nodes-base.set", "position": [-1200, -1360], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "docker-compose", "type": "string", "value": "=version: \"3\"\n\nservices:\n  {{ $('API').item.json.body.domain }}:\n    image: minio/minio\n    restart: unless-stopped\n    container_name: {{ $('API').item.json.body.domain }}\n    command: server /data --console-address \":9001\"\n    environment:\n      MINIO_ROOT_USER: {{ $('API').item.json.body.username }}\n      MINIO_ROOT_PASSWORD: {{ $('API').item.json.body.password }}\n      MINIO_BROWSER_REDIRECT_URL: {{ $env.WEBHOOK_URL }}{{ $('API').item.json.body.domain }}\n      LETSENCRYPT_HOST: {{ $('API').item.json.body.domain }},console.{{ $('API').item.json.body.domain }}\n      VIRTUAL_HOST_MULTIPORTS: |-\n          {{ $('API').item.json.body.domain }}:\n            \"/\":\n              port: 9000\n          console.{{ $('API').item.json.body.domain }}:\n            \"/\":\n              port: 9001\n    volumes:\n      - \"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}/data:/data\"\n    networks:\n      - nginx-proxy_web\n    mem_limit: \"{{ $('API').item.json.body.ram }}G\"\n    cpus: \"{{ $('API').item.json.body.cpu }}\"\n\nnetworks:\n  nginx-proxy_web:\n    external: true\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "938520b1-aae6-4fe7-ac8e-e888f0793c8a", "name": "Version", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1080, 1300], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}\"\nVERSION_JSON=\"{}\"\n\n# Function to return error in JSON format\nhandle_error() {\n    echo \"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    exit 1\n}\n\n# Check if the container exists\nif ! sudo docker ps -a | grep -q \"$CONTAINER_NAME\" > /dev/null 2>&1; then\n    handle_error \"Container $CONTAINER_NAME not found\"\nfi\n\n# Get the MinIO version from the container (first line only)\nVERSION=$(sudo docker exec \"$CONTAINER_NAME\" minio -v | head -n 1)\n\n# Extract just the version string\nVERSION_CLEAN=$(echo \"$VERSION\" | awk '{print $3}')\n\n# Format version as JSON\nVERSION_JSON=\"{\\\"version\\\": \\\"$VERSION_CLEAN\\\"}\"\n\necho \"$VERSION_JSON\"\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "d83a8249-9ad9-4772-bb1b-5484ebeb4b81", "name": "Users", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1140, 1460], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}\"\nMINIO_USERNAME=\"{{ $('API').item.json.body.username }}\"\nMINIO_PASSWORD=\"{{ $('API').item.json.body.password }}\"\n\n# Function to return error in JSON format\nhandle_error() {\n    echo \"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    exit 1\n}\n\n# Check if the container exists\nif ! sudo docker ps -a | grep -q \"$CONTAINER_NAME\" > /dev/null 2>&1; then\n    handle_error \"Container $CONTAINER_NAME not found\"\nfi\n\n# Set alias for MinIO client\nsudo docker exec \"$CONTAINER_NAME\" mc alias set local {{ $env.WEBHOOK_URL }} \"$MINIO_USERNAME\" \"$MINIO_PASSWORD\" > /dev/null 2>&1\n\n# Get user list and format it correctly as JSON array\nUSERS_JSON=$(sudo docker exec \"$CONTAINER_NAME\" mc admin user list local --json | jq -s '.')\n\n# Check if USERS_JSON is empty\nif [ -z \"$USERS_JSON\" ]; then\n    handle_error \"Failed to retrieve user list for $CONTAINER_NAME\"\nfi\n\n# Wrap in a JSON object\nJSON=\"{\\\"users\\\": $USERS_JSON}\"\n\necho \"$JSON\"\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "ba9b26be-31b6-47c9-85c1-719f346abc1a", "name": "If1", "type": "n8n-nodes-base.if", "position": [-1780, -1260], "parameters": {"options": {}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "or", "conditions": [{"id": "8602bd4c-9693-4d5f-9e7d-5ee62210baca", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "create"}, {"id": "1c630b59-0e5a-441d-8aa5-70b31338d897", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "change_package"}, {"id": "b3eb7052-a70f-438e-befd-8c5240df32c7", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "unsuspend"}]}}, "typeVersion": 2.2, "notes": "This if node performs automated tasks as part of the workflow."}, {"id": "c08cfbd4-ef9a-4430-8a03-41ae209a3c92", "name": "MinIO", "type": "n8n-nodes-base.switch", "position": [-1680, 1380], "parameters": {"rules": {"values": [{"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "66ad264d-5393-410c-bfa3-011ab8eb234a", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "app_version"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "b48957a0-22c0-4ac0-82ef-abd9e7ab0207", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "app_users"}]}, "renameOutput": true}]}, "options": {}}, "typeVersion": 3.2, "notes": "This switch node performs automated tasks as part of the workflow."}, {"id": "d75c83ca-c106-4b96-9db7-9f3ef1e20453", "name": "nginx", "type": "n8n-nodes-base.set", "position": [-1420, -1360], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "main", "type": "string", "value": "=ignore_invalid_headers off;\nclient_max_body_size 0;\nproxy_buffering off;\nproxy_request_buffering off;"}, {"id": "6507763a-21d4-4ff0-84d2-5dc9d21b7430", "name": "main_location", "type": "string", "value": "=# Custom header\nproxy_set_header Host $http_host;\nproxy_set_header X-Real-IP $remote_addr;\nproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\nproxy_set_header X-Forwarded-Proto $scheme;\n\nproxy_connect_timeout 300;\n# Default is HTTP/1, keepalive is only enabled in HTTP/1.1\nproxy_http_version 1.1;\nproxy_set_header Connection \"\";\nchunked_transfer_encoding off;\n"}, {"id": "d00aa07a-0641-43ef-8fd2-5fb9ef62e313", "name": "console", "type": "string", "value": "=ignore_invalid_headers off;\nclient_max_body_size 0;\nproxy_buffering off;\nproxy_request_buffering off;"}, {"id": "c00fb803-8b9f-4aca-a1b1-2e3da42fc8d1", "name": "console_location", "type": "string", "value": "=# Custom header\nproxy_set_header Host $http_host;\nproxy_set_header X-Real-IP $remote_addr;\nproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\nproxy_set_header X-Forwarded-Proto $scheme;\nproxy_set_header X-NginX-Proxy true;\n\nreal_ip_header X-Real-IP;\nproxy_connect_timeout 300;\nproxy_http_version 1.1;\nproxy_set_header Upgrade $http_upgrade;\nproxy_set_header Connection \"upgrade\";\n  \nchunked_transfer_encoding off;"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "70c2cb4d-af9d-4003-8aaf-e5800580552b", "name": "Container Stat", "type": "n8n-nodes-base.switch", "position": [-1680, -240], "parameters": {"rules": {"values": [{"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "66ad264d-5393-410c-bfa3-011ab8eb234a", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_information_inspect"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "b48957a0-22c0-4ac0-82ef-abd9e7ab0207", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_information_stats"}]}, "renameOutput": true}, {"outputKey": "YOUR_CREDENTIAL_HERE", "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "50ede522-af22-4b7a-b1fd-34b27fd3fadd", "operator": {"name": "filter.operator.equals", "type": "string", "operation": "equals"}, "leftValue": "={{ $('API').item.json.body.command }}", "rightValue": "container_log"}]}, "renameOutput": true}]}, "options": {}}, "typeVersion": 3.2, "notes": "This switch node performs automated tasks as part of the workflow."}, {"id": "0bb2aeeb-8279-4f13-827f-a6559ef805b1", "name": "GET ACL", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1180, 560], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\n# Get values for variables from templates\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\n\nNGINX_MAIN_ACL_FILE=\"$NGINX_DIR/$DOMAIN\"_acl\nNGINX_CONSOLE_ACL_FILE=\"$NGINX_DIR/console.$DOMAIN\"_acl\n\n# Function to log an error and exit\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\n# Read files if they exist, else assign empty array\nif [[ -f \"$NGINX_CONSOLE_ACL_FILE\" ]]; then\n    WEB_CONSOLE_IPS=$(cat \"$NGINX_CONSOLE_ACL_FILE\" | jq -R -s 'split(\"\\n\") | map(select(length > 0))')\nelse\n    WEB_CONSOLE_IPS=\"[]\"\nfi\n\nif [[ -f \"$NGINX_MAIN_ACL_FILE\" ]]; then\n    REST_API_IPS=$(cat \"$NGINX_MAIN_ACL_FILE\" | jq -R -s 'split(\"\\n\") | map(select(length > 0))')\nelse\n    REST_API_IPS=\"[]\"\nfi\n\n# Output JSON\necho \"{ \\\"web_console_ips\\\": $WEB_CONSOLE_IPS, \\\"rest_api_ips\\\": $REST_API_IPS }\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "9603bee0-de6f-46bf-97d4-f7a2a4d27514", "name": "SET ACL", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1060, 700], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\n# Get values for variables from templates\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\nVHOST_DIR=\"/opt/docker/nginx-proxy/nginx/vhost.d\"\n\nNGINX_MAIN_ACL_FILE=\"$NGINX_DIR/$DOMAIN\"_acl\nNGINX_MAIN_ACL_TEXT=\"{{ $('API').item.json.body.rest_api_ips }}\"\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$DOMAIN\"_location\n\nNGINX_CONSOLE_ACL_FILE=\"$NGINX_DIR/console.$DOMAIN\"_acl\nNGINX_CONSOLE_ACL_TEXT=\"{{ $('API').item.json.body.web_console_ips }}\"\nVHOST_CONSOLE_LOCATION_FILE=\"$VHOST_DIR/console.$DOMAIN\"_location\nNGINX_CONSOLE_LOCATION_FILE=\"$NGINX_DIR/console.$DOMAIN\"_location\n\n# Function to log an error and exit\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\nupdate_nginx_acl() {\n    ACL_FILE=$1\n    LOCATION_FILE=$2\n    \n    if [ -s \"$ACL_FILE\" ]; then\n        VALID_LINES=$(grep -vE '^\\s*$' \"$ACL_FILE\")\n        if [ -n \"$VALID_LINES\" ]; then\n            while IFS= read -r line; do\n                echo \"allow $line;\" | sudo tee -a \"$LOCATION_FILE\" > /dev/null || handle_error \"Failed to update $LOCATION_FILE\"\n            done <<< \"$VALID_LINES\"\n            echo \"deny all;\" | sudo tee -a \"$LOCATION_FILE\" > /dev/null || handle_error \"Failed to update $LOCATION_FILE\"\n        fi\n    fi\n}\n\n# Create or overwrite the file with the content from variables\necho \"$NGINX_MAIN_ACL_TEXT\" | sudo tee \"$NGINX_MAIN_ACL_FILE\" > /dev/null\necho \"$NGINX_CONSOLE_ACL_TEXT\" | sudo tee \"$NGINX_CONSOLE_ACL_FILE\" > /dev/null\n\nsudo cp -f \"$NGINX_MAIN_LOCATION_FILE\" \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_MAIN_LOCATION_FILE to $VHOST_MAIN_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_MAIN_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_MAIN_LOCATION_FILE\"\n\nsudo cp -f \"$NGINX_CONSOLE_LOCATION_FILE\" \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_CONSOLE_LOCATION_FILE to $VHOST_CONSOLE_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_CONSOLE_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_CONSOLE_LOCATION_FILE\"\n\nupdate_nginx_acl \"$NGINX_MAIN_ACL_FILE\" \"$VHOST_MAIN_LOCATION_FILE\"\nupdate_nginx_acl \"$NGINX_CONSOLE_ACL_FILE\" \"$VHOST_CONSOLE_LOCATION_FILE\"\n\n# Reload Nginx with sudo\nif sudo docker exec nginx-proxy nginx -s reload; then\n    echo \"success\"\nelse\n    handle_error \"Failed to reload Nginx.\"\nfi\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}, {"id": "325e6cfc-f28e-490e-84a0-d8153e1c9fc9", "name": "GET NET", "type": "n8n-nodes-base.set", "onError": "continueRegularOutput", "position": [-1180, 840], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "21f4453e-c136-4388-be90-1411ae78e8a5", "name": "sh", "type": "string", "value": "=#!/bin/bash\n\n# Get values for variables from templates\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCOMPOSE_DIR=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN\"\nNGINX_DIR=\"$COMPOSE_DIR/nginx\"\nNET_IN_FILE=\"$COMPOSE_DIR/net_in\"\nNET_OUT_FILE=\"$COMPOSE_DIR/net_out\"\n\n# Function to log an error and exit\nhandle_error() {\n    echo \"error: $1\"\n    exit 1\n}\n\n# Get current network statistics from container\nSTATS=$(sudo docker exec \"$DOMAIN\" cat /proc/net/dev | grep eth0) || handle_error \"Failed to get network stats\"\nNET_IN_NEW=$(echo \"$STATS\" | awk '{print $2}')  # RX bytes (received)\nNET_OUT_NEW=$(echo \"$STATS\" | awk '{print $10}') # TX bytes (transmitted)\n\n# Ensure directory exists\nmkdir -p \"$COMPOSE_DIR\"\n\n# Read old values, create files if they don't exist\nif [[ -f \"$NET_IN_FILE\" ]]; then\n    NET_IN_OLD=$(sudo cat \"$NET_IN_FILE\")\nelse\n    NET_IN_OLD=0\nfi\n\nif [[ -f \"$NET_OUT_FILE\" ]]; then\n    NET_OUT_OLD=$(sudo cat \"$NET_OUT_FILE\")\nelse\n    NET_OUT_OLD=0\nfi\n\n# Save new values\necho \"$NET_IN_NEW\" | sudo tee \"$NET_IN_FILE\" > /dev/null\necho \"$NET_OUT_NEW\" | sudo tee \"$NET_OUT_FILE\" > /dev/null\n\n# Output JSON\necho \"{ \\\"net_in_new\\\": $NET_IN_NEW, \\\"net_out_new\\\": $NET_OUT_NEW, \\\"net_in_old\\\": $NET_IN_OLD, \\\"net_out_old\\\": $NET_OUT_OLD }\"\n\nexit 0\n"}]}}, "typeVersion": 3.4, "alwaysOutputData": true, "notes": "This set node performs automated tasks as part of the workflow."}], "active": true, "pinData": {}, "settings": {"timezone": "UTC", "callerPolicy": "workflowsFromSameOwner", "executionOrder": "v1", "saveManualExecutions": true, "errorWorkflow": null, "executionTimeout": 3600, "maxExecutions": 1000, "retryOnFail": true, "retryCount": 3, "retryDelay": 1000}, "versionId": "930dd393-6eff-43d5-8446-30ba19fce16d", "connections": {"9814333d-a9c1-4787-aed1-116db9395b88": {"main": [[{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-1deb7fd6", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-f0dd7ae3", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-7d180da3", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-e8db0af8", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-9acdc9bb", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-9076ed22", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-cc28f466", "type": "main", "index": 0}], [{"node": "error-handler-9814333d-a9c1-4787-aed1-116db9395b88-100fb3a5", "type": "main", "index": 0}]]}, "a3e0156c-8033-4829-ab57-06e3708a7a09": {"main": [[{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-d5a07dc2", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-47155fcb", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-1fabee82", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-ecbe4219", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-c7c4cf5e", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-0a647a49", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-b3f3c3ea", "type": "main", "index": 0}], [{"node": "error-handler-a3e0156c-8033-4829-ab57-06e3708a7a09-46c01d2c", "type": "main", "index": 0}]]}, "1c59a844-f4ef-422f-abbf-288a55e11934": {"main": [[{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-457941af", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-af091f39", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-9ddbd904", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-f2580c6a", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-b6a3f8c2", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-467d8a4d", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-8b8efb15", "type": "main", "index": 0}], [{"node": "error-handler-1c59a844-f4ef-422f-abbf-288a55e11934-9e21c911", "type": "main", "index": 0}]]}}, "description": "Automated workflow: puq-docker-minio-deploy. This workflow integrates 9 different services: webhook, stickyNote, code, switch, set. It contains 39 nodes and follows best practices for error handling and security.", "notes": "Excellent quality workflow: puq-docker-minio-deploy. This workflow has been optimized for production use with comprehensive error handling, security, and documentation."}