8
n8n 中文网amn8n.com

PUQ Docker NextCloud 部署

高级

这是一个Engineering领域的自动化工作流,包含 44 个节点。主要使用 If, Set, Ssh, Code, Switch 等节点。 部署 Docker NextCloud,WHMCS/WISECP 的 API 后端

前置要求
  • HTTP Webhook 端点(n8n 会自动生成)
  • 可能需要目标 API 的认证凭证

分类

工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "d3xtaER6gl4aqLZR",
  "meta": {
    "instanceId": "ffb0782f8b2cf4278577cb919e0cd26141bc9ff8774294348146d454633aa4e3",
    "templateCredsSetupCompleted": true
  },
  "name": "PUQ Docker NextCloud 部署",
  "tags": [],
  "nodes": [
    {
      "id": "dc9d4284-0ff7-4068-af3d-2b7f38451118",
      "name": "条件判断",
      "type": "n8n-nodes-base.if",
      "position": [
        540,
        920
      ],
      "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": "={{ $('API').item.json.body.server_domain }}",
              "rightValue": "=d01-test.uuq.pl"
            },
            {
              "id": "8a6662a4-4539-4ab1-bd5b-46b0a0d6e023",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('API').item.json.body.server_domain }}",
              "rightValue": "d02-test.uuq.pl"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b015bca6-fe71-4eb4-8e99-2904911c03b3",
      "name": "参数",
      "type": "n8n-nodes-base.set",
      "position": [
        320,
        920
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "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
    },
    {
      "id": "b0c5ccb8-0692-4bb0-99e1-769fde372e0f",
      "name": "API",
      "type": "n8n-nodes-base.webhook",
      "position": [
        0,
        920
      ],
      "webhookId": "4e8168b3-2cad-462a-9750-152986331ce2",
      "parameters": {
        "path": "docker-nextcloud",
        "options": {},
        "httpMethod": [
          "POST"
        ],
        "responseMode": "responseNode",
        "authentication": "basicAuth",
        "multipleMethods": true
      },
      "credentials": {
        "httpBasicAuth": {
          "id": "0gzq1np6ZmIrtK5o",
          "name": "nextcloud"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "bcaf7ce1-464a-492e-b7f5-50ba8e465171",
      "name": "422-服务器域名无效",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        500,
        1240
      ],
      "parameters": {
        "options": {
          "responseCode": 422
        },
        "respondWith": "json",
        "responseBody": "[{\n  \"status\": \"error\",\n  \"error\": \"Invalid server domain\"\n}]"
      },
      "typeVersion": 1.1,
      "alwaysOutputData": false
    },
    {
      "id": "3c642087-bd6b-4996-890b-4d50fbca8c55",
      "name": "容器操作",
      "type": "n8n-nodes-base.switch",
      "position": [
        940,
        1740
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "start",
              "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": "stop",
              "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": "mount_disk",
              "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": "unmount_disk",
              "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": "container_get_acl",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "72a60c6b-5dc5-48db-8d3a-e083ffad6ae2",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.command }}",
                    "rightValue": "container_get_acl"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "container_set_acl",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "74eb2334-6176-46ef-b444-d99b439fea17",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.command }}",
                    "rightValue": "container_set_acl"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "container_get_net",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "817ef082-a2d8-4b13-a8df-6e946878653b",
                    "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
    },
    {
      "id": "396e6074-98ec-47df-956c-ce5c3b75e57e",
      "name": "容器状态",
      "type": "n8n-nodes-base.switch",
      "position": [
        940,
        1080
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "inspect",
              "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": "stats",
              "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": "log",
              "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
            },
            {
              "outputKey": "dependent_containers_information_stats",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "d3070310-d3c2-4200-9765-495cf69fa835",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.command }}",
                    "rightValue": "dependent_containers_information_stats"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "container_update_dns_record",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "dc17d6ad-4fa1-4006-8718-8188efa5f458",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.command }}",
                    "rightValue": "container_update_dns_record"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "d0084a58-b157-4635-955a-8638f348bf72",
      "name": "检查",
      "type": "n8n-nodes-base.set",
      "position": [
        1260,
        760
      ],
      "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
    },
    {
      "id": "cec87c49-d7ea-4407-bc4c-21ea75b25baa",
      "name": "状态",
      "type": "n8n-nodes-base.set",
      "position": [
        1260,
        920
      ],
      "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 }}_nextcloud\"\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
    },
    {
      "id": "80dcd9b2-f1f5-44c3-98e8-38dae5ad4edb",
      "name": "开始",
      "type": "n8n-nodes-base.set",
      "position": [
        1400,
        1500
      ],
      "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 }}\"\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
    },
    {
      "id": "9cde27ca-4749-4660-9d46-d3161946b627",
      "name": "停止",
      "type": "n8n-nodes-base.set",
      "position": [
        1400,
        1660
      ],
      "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 }}\"\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
    },
    {
      "id": "f957ffb7-ccb5-41b2-b89e-ef1a92942251",
      "name": "挂载磁盘",
      "type": "n8n-nodes-base.set",
      "position": [
        1400,
        1820
      ],
      "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 }}\"\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
    },
    {
      "id": "00cb7b5b-429e-494f-b2a9-1c0c45ac8d66",
      "name": "卸载磁盘",
      "type": "n8n-nodes-base.set",
      "position": [
        1400,
        1980
      ],
      "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 }}\"\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
    },
    {
      "id": "49487b07-8b7f-48c4-b7d0-819336ce6691",
      "name": "日志",
      "type": "n8n-nodes-base.set",
      "position": [
        1420,
        1040
      ],
      "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 }}_nextcloud\"\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
    },
    {
      "id": "f8dfb4a8-5887-4796-9d1e-f882947fe9e8",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 6,
        "width": 639,
        "height": 909,
        "content": "## 👋 欢迎使用 PUQ Docker NextCloud 部署!"
      },
      "typeVersion": 1
    },
    {
      "id": "29bd957b-a5be-4a6e-81e3-ba7d88462d93",
      "name": "部署-docker-compose",
      "type": "n8n-nodes-base.set",
      "position": [
        1340,
        20
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "21f4453e-c136-4388-be90-1411ae78e8a5",
              "name": "docker-compose",
              "type": "string",
              "value": "=version: \"3.8\"\n\nservices:\n  {{ $('API').item.json.body.domain }}_nextcloud:\n    image: nextcloud:latest\n    container_name: {{ $('API').item.json.body.domain }}_nextcloud\n    environment:\n      NEXTCLOUD_ADMIN_USER: {{ $('API').item.json.body.nc_admin_user }}\n      NEXTCLOUD_ADMIN_PASSWORD: {{ $('API').item.json.body.nc_admin_password }}\n      NEXTCLOUD_TRUSTED_DOMAINS: {{ $('API').item.json.body.domain }}\n      MYSQL_PASSWORD: {{ $('API').item.json.body.mysql_password }}\n      MYSQL_DATABASE: {{ $('API').item.json.body.mysql_database }}\n      MYSQL_USER: {{ $('API').item.json.body.mysql_user }}\n      MYSQL_HOST: {{ $('API').item.json.body.domain }}_db\n      REDIS_HOST: {{ $('API').item.json.body.domain }}_redis\n      VIRTUAL_HOST: {{ $('API').item.json.body.domain }}\n      LETSENCRYPT_HOST: {{ $('API').item.json.body.domain }}\n    volumes:\n      - \"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}/config:/var/www/html/config\"\n      - \"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}/data:/var/www/html/data\"\n      - \"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}/html:/var/www/html\"\n    networks:\n      - nginx-proxy_web\n    depends_on:\n      - {{ $('API').item.json.body.domain }}_db\n      - {{ $('API').item.json.body.domain }}_redis\n      - {{ $('API').item.json.body.domain }}_collabora\n    mem_limit: \"{{ $('API').item.json.body.ram }}G\"\n    cpus: \"{{ $('API').item.json.body.cpu }}\"\n\n  {{ $('API').item.json.body.domain }}_db:\n    image: mariadb:11.4\n    container_name: {{ $('API').item.json.body.domain }}_db\n    environment:\n      MYSQL_ROOT_PASSWORD: {{ $('API').item.json.body.mysql_root_password }}\n      MYSQL_PASSWORD: {{ $('API').item.json.body.mysql_password }}\n      MYSQL_DATABASE: {{ $('API').item.json.body.mysql_database }}\n      MYSQL_USER: {{ $('API').item.json.body.mysql_user }}\n    volumes:\n      - \"{{ $('Parametrs').item.json.mount_dir }}/{{ $('API').item.json.body.domain }}/db:/var/lib/mysql\"\n    networks:\n      - nginx-proxy_web\n    mem_limit: \"{{ Number($('API').item.json.body.ram) / 2 }}G\"\n    cpus: \"{{ Number($('API').item.json.body.cpu) / 2 }}\"\n\n  {{ $('API').item.json.body.domain }}_redis:\n    image: redis:alpine\n    container_name: {{ $('API').item.json.body.domain }}_redis\n    networks:\n      - nginx-proxy_web\n    mem_limit: \"{{ Number($('API').item.json.body.ram) / 4 }}G\"\n    cpus: \"{{ Number($('API').item.json.body.cpu) / 4 }}\"\n\n  {{ $('API').item.json.body.domain }}_collabora:\n    image: collabora/code\n    container_name: {{ $('API').item.json.body.domain }}_collabora\n    environment:\n      - domain={{ $('API').item.json.body.office_domain_escaped }}:443\n      - server_name=office.{{ $('API').item.json.body.domain }}\n      - username={{ $('API').item.json.body.mysql_user }}\n      - password={{ $('API').item.json.body.mysql_password }}\n      - \"dictionaries=ru_RU uk_UA pl_PL en\"\n      - \"extra_params=--o:ssl.enable=true --o:ssl.termination=true --o:net.proto=https --o:ssl.le=true --o:storage.wopi.host=https://{{ $('API').item.json.body.domain }}\"\n      - VIRTUAL_HOST=office.{{ $('API').item.json.body.domain }}\n      - LETSENCRYPT_HOST=office.{{ $('API').item.json.body.domain }}\n      - VIRTUAL_PROTO=https\n      - VIRTUAL_PORT=9980\n    cap_add:\n      - MKNOD\n      - SYS_ADMIN\n    extra_hosts:\n      - \"{{ $('API').item.json.body.domain }}:77.87.125.201\"\n    dns:\n      - 8.8.8.8\n      - 8.8.4.4\n    networks:\n      - nginx-proxy_web\n    mem_limit: \"{{ Number($('API').item.json.body.ram) }}G\"\n    cpus: \"{{ Number($('API').item.json.body.cpu) / 2 }}\"\n\nnetworks:\n  nginx-proxy_web:\n    external: true\n"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "4243c90b-de8a-4931-972b-5f700edb09d4",
      "name": "版本",
      "type": "n8n-nodes-base.set",
      "position": [
        1380,
        2640
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "21f4453e-c136-4388-be90-1411ae78e8a5",
              "name": "sh",
              "type": "string",
              "value": "=#!/bin/bash\n\n# Define the container name dynamically using an API call\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}_nextcloud\"\nVERSION_JSON=\"{}\"\n\n# Function to handle errors and return a JSON-formatted message\nhandle_error() {\n    echo \"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    exit 1\n}\n\n# Check if the container exists by searching for its name in the list of all Docker containers\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# Retrieve the Nextcloud status as a JSON response from the container\n# The '-u 33' option ensures that the command is executed as the Nextcloud user (www-data)\nNEXTCLOUD_STATUS=$(sudo docker exec -u 33 \"$CONTAINER_NAME\" php occ status --output=json 2>/dev/null)\n\n# Validate if the command was executed successfully and if the output is not empty\nif [ $? -ne 0 ] || [ -z \"$NEXTCLOUD_STATUS\" ]; then\n    handle_error \"Failed to retrieve Nextcloud status for $CONTAINER_NAME\"\nfi\n\n# Extract the Nextcloud version string from the JSON response\nVERSION=$(echo \"$NEXTCLOUD_STATUS\" | jq -r '.versionstring')\n\n# Ensure that a valid version string was extracted\nif [ -z \"$VERSION\" ]; then\n    handle_error \"Failed to parse Nextcloud version from response\"\nfi\n\n# Construct a JSON-formatted output containing the Nextcloud version\nVERSION_JSON=\"{\\\"version\\\": \\\"$VERSION\\\"}\"\n\n# Print the JSON result\necho \"$VERSION_JSON\"\nexit 0\n"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "4f13c4f2-82dd-478f-915b-247a071db107",
      "name": "用户",
      "type": "n8n-nodes-base.set",
      "position": [
        1380,
        2780
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "21f4453e-c136-4388-be90-1411ae78e8a5",
              "name": "sh",
              "type": "string",
              "value": "=#!/bin/bash\n\n# Define the container name dynamically using an API call\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}_nextcloud\"\nUSERS_JSON=\"{}\"\n\n# Function to handle errors and return a JSON-formatted message\nhandle_error() {\n    echo \"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    exit 1\n}\n\n# Check if the container exists by searching for its name in the list of all Docker containers\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# Retrieve the list of Nextcloud users and reformat it into a proper JSON array\nUSERS=$(sudo docker exec -u 33 \"$CONTAINER_NAME\" php occ user:list --output=json 2>/dev/null | jq -c 'to_entries | map({username: .key, displayname: .value})')\n\n# Validate if the command executed successfully and output is not empty\nif [ $? -ne 0 ] || [ -z \"$USERS\" ]; then\n    handle_error \"Failed to retrieve users from Nextcloud\"\nfi\n\n# Construct a JSON-formatted output containing all retrieved users\nUSERS_JSON=\"{\\\"users\\\": $USERS}\"\n\n# Print the JSON result\necho \"$USERS_JSON\"\nexit 0\n"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "6d385bc7-01f1-4d42-b16e-a2e45927ef7f",
      "name": "更改密码",
      "type": "n8n-nodes-base.set",
      "position": [
        1380,
        2960
      ],
      "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 }}_nextcloud\"\nNC_USER=\"{{ $('API').item.json.body.user_email }}\"\nNEW_PASSWORD=\"{{ $('API').item.json.body.password }}\"\n\n# Function to output error in JSON format and exit with code 1\nhandle_error() {\n    echo \"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    exit 1\n}\n\n# Check if container name is provided\nif [ -z \"$CONTAINER_NAME\" ]; then\n    handle_error \"No container name provided\"\nfi\n\n# Check if Nextcloud username is provided\nif [ -z \"$NC_USER\" ]; then\n    handle_error \"No Nextcloud user provided\"\nfi\n\n# Check if password is provided\nif [ -z \"$NEW_PASSWORD\" ]; then\n    handle_error \"No password provided\"\nfi\n\n# Run command in container\n#   -u 33                  => as UID 33 (often www-data in Nextcloud)\n#   -e OC_PASS=\"$NEW_PASSWORD\" => pass password through environment to container\n#   php occ user:resetpassword --password-from-env \"$NC_USER\"\n# returns 0 if successful\n\nOUTPUT=$( sudo docker exec -u 33 \\\n                    -e OC_PASS=\"$NEW_PASSWORD\" \\\n                    \"$CONTAINER_NAME\" \\\n                    php occ user:resetpassword --password-from-env \"$NC_USER\" 2>&1 )\n\n# Check return code\nif [ $? -ne 0 ]; then\n    handle_error \"Failed to reset password. Output: $OUTPUT\"\nfi\n\necho \"{\\\"status\\\": \\\"success\\\"}\"\nexit 0\n"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "dd283191-a5cd-4d29-8c2d-0ef42b63f69c",
      "name": "NextCloud",
      "type": "n8n-nodes-base.switch",
      "position": [
        920,
        2620
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "version",
              "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": "users",
              "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
            },
            {
              "outputKey": "change_password",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "7c862a6f-5df1-499c-b9c6-9b266e2bebec",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.command }}",
                    "rightValue": "change_password"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "9f5e3d3e-4f6d-4967-aefe-b953c5c3418b",
      "name": "nginx",
      "type": "n8n-nodes-base.set",
      "position": [
        1080,
        140
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "21f4453e-c136-4388-be90-1411ae78e8a5",
              "name": "main",
              "type": "string",
              "value": "=# Increase max body size for large file uploads\nclient_max_body_size 50000M;\n\n# Proxy headers\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\n# WebSocket support\nproxy_http_version 1.1;\nproxy_set_header   Upgrade    $http_upgrade;\nproxy_set_header   Connection \"upgrade\";\n\n# Timeouts\nproxy_read_timeout 600s;\nproxy_send_timeout 600s;\nsend_timeout       600s;\n\n# Additional optimizations\nproxy_buffering off;\nproxy_buffer_size 128k;\nproxy_buffers 4 256k;\nproxy_busy_buffers_size 256k;\nproxy_temp_file_write_size 256k;\nproxy_connect_timeout 600s;\n"
            },
            {
              "id": "6507763a-21d4-4ff0-84d2-5dc9d21b7430",
              "name": "main_location",
              "type": "string",
              "value": "="
            },
            {
              "id": "d00aa07a-0641-43ef-8fd2-5fb9ef62e313",
              "name": "office",
              "type": "string",
              "value": "=server_name  office.{{ $('API').item.json.body.domain }};\n\n# static files\n location ^~ /browser {\n   proxy_pass https://office.{{ $('API').item.json.body.domain }};\n   proxy_set_header Host $host;\n }\n\n\n # WOPI discovery URL\n location ^~ /hosting/discovery {\n   proxy_pass https://office.{{ $('API').item.json.body.domain }};\n   proxy_set_header Host $host;\n }\n\n\n # Capabilities\n location ^~ /hosting/capabilities {\n   proxy_pass https://office.{{ $('API').item.json.body.domain }};\n   proxy_set_header Host $host;\n }\n\n\n # main websocket\n location ~ ^/cool/(.*)/ws$ {\n   proxy_pass https://office.{{ $('API').item.json.body.domain }};\n   proxy_set_header Upgrade $http_upgrade;\n   proxy_set_header Connection \"Upgrade\";\n   proxy_set_header Host $host;\n   proxy_read_timeout 36000s;\n }\n\n\n # download, presentation and image upload\n location ~ ^/(c|l)ool {\n   proxy_pass https://office.{{ $('API').item.json.body.domain }};\n   proxy_set_header Host $host;\n }\n\n # Admin Console websocket\n location ^~ /cool/adminws {\n   proxy_pass https://office.{{ $('API').item.json.body.domain }};\n   proxy_set_header Upgrade $http_upgrade;\n   proxy_set_header Connection \"Upgrade\";\n   proxy_set_header Host $host;\n   proxy_read_timeout 36000s;\n }\n"
            },
            {
              "id": "c00fb803-8b9f-4aca-a1b1-2e3da42fc8d1",
              "name": "office_location",
              "type": "string",
              "value": "="
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "fa40012b-0e58-4d6c-af19-b9dd6c72386d",
      "name": "测试连接",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1920,
        -40
      ],
      "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
    },
    {
      "id": "12240691-bcbe-407c-b53c-89cf84bc190f",
      "name": "更换套餐",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1920,
        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 }}\"\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}_nextcloud\"\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='{{ JSON.stringify($('Deploy-docker-compose').item.json['docker-compose']).base64Encode() }}'\n\nNGINX_MAIN_TEXT='{{ JSON.stringify($('nginx').item.json['main']).base64Encode() }}'\nNGINX_MAIN_FILE=\"$NGINX_DIR/$DOMAIN\"\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\n\nNGINX_MAIN_LOCATION_TEXT='{{ JSON.stringify($('nginx').item.json['main_location']).base64Encode() }}'\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$DOMAIN\"_location\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\n\nNGINX_OFFICE_TEXT='{{ JSON.stringify($('nginx').item.json['office']).base64Encode() }}'\nNGINX_OFFICE_FILE=\"$NGINX_DIR/office.$DOMAIN\"\nVHOST_OFFICE_FILE=\"$VHOST_DIR/office.$DOMAIN\"\n\nNGINX_OFFICE_LOCATION_TEXT='{{ JSON.stringify($('nginx').item.json['office_location']).base64Encode() }}'\nNGINX_OFFICE_LOCATION_FILE=\"$NGINX_DIR/office.$DOMAIN\"_location\nVHOST_OFFICE_LOCATION_FILE=\"$VHOST_DIR/office.$DOMAIN\"_location\n\nDISK_SIZE=\"{{ $('API').item.json.body.disk }}\"\n\n# Function to log an error, write to status file, and print to office\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# Get nginx-proxy IP address before installing Nextcloud Office\nget_proxy_ip() {\n    local ip=\"\"\n    local retries=10  # Try a few times\n    local count=0\n    while [[ -z \"$ip\" && $count -lt $retries ]]; do\n        ip=$(sudo docker inspect -f '{{ $('Parametrs').item.json.screen_left }}range .NetworkSettings.Networks{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}.IPAddress{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}end{{ $('Parametrs').item.json.screen_right }}' nginx-proxy)\n        if [[ -z \"$ip\" ]]; then\n            echo \"[DEBUG] nginx-proxy IP not found, retrying ($count/$retries)...\" >> \"$STATUS_FILE\"\n            sleep 2  # Wait a bit before retrying\n        fi\n        ((count++))\n    done\n\n    if [[ -z \"$ip\" ]]; then\n        echo \"[ERROR] Failed to retrieve nginx-proxy IP after $retries attempts!\" >> \"$STATUS_FILE\"\n        handle_error \"Failed to retrieve nginx-proxy IP\"\n    fi\n\n    echo \"[DEBUG] Detected nginx-proxy IP: $ip\" >> \"$STATUS_FILE\"\n    echo \"$ip\"\n}\n\n# Get the IP address of Nextcloud Office\nget_office_ip() {\n    local ip=\"\"\n    local retries=10  # Try a few times\n    local count=0\n    while [[ -z \"$ip\" && $count -lt $retries ]]; do\n        ip=$(sudo docker inspect -f '{{ $('Parametrs').item.json.screen_left }}range .NetworkSettings.Networks{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}.IPAddress{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}end{{ $('Parametrs').item.json.screen_right }}' \"$DOMAIN\"_collabora)\n        if [[ -z \"$ip\" ]]; then\n            echo \"[DEBUG] office IP not found, retrying ($count/$retries)...\" >> \"$STATUS_FILE\"\n            sleep 2  # Wait a bit before retrying\n        fi\n        ((count++))\n    done\n\n    if [[ -z \"$ip\" ]]; then\n        echo \"[ERROR] Failed to retrieve office IP after $retries attempts!\" >> \"$STATUS_FILE\"\n        handle_error \"Failed to retrieve office IP\"\n    fi\n\n    # Convert IP to subnet by replacing the last octet with 0 and adding /24\n    local subnet=$(echo \"$ip\" | sed 's/\\.[0-9]*$/.0\\/24/')\n    echo \"[DEBUG] Detected office subnet: $subnet\" >> \"$STATUS_FILE\"\n    echo \"$subnet\"\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 -e \"$DOCKER_COMPOSE_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$COMPOSE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $COMPOSE_FILE\"\n\n# Create NGINX configuration files\necho -e \"$NGINX_MAIN_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_MAIN_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_FILE\"\necho -e \"$NGINX_MAIN_LOCATION_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_MAIN_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_LOCATION_FILE\"\n\necho -e \"$NGINX_OFFICE_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_OFFICE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_OFFICE_FILE\"\necho -e \"$NGINX_OFFICE_LOCATION_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_OFFICE_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_OFFICE_LOCATION_FILE\"\n\n# Resize or extend the disk image to match DISK_SIZE\nif [ -f \"$IMG_FILE\" ]; then\n    DESIRED_SIZE_BYTES=$((DISK_SIZE * 1024 * 1024 * 1024))\n    CURRENT_SIZE_BYTES=$(stat -c %s \"$IMG_FILE\")\n\n    # Expand or shrink as needed\n    if [ \"$CURRENT_SIZE_BYTES\" -lt \"$DESIRED_SIZE_BYTES\" ]; then\n        # echo \"[INFO] Expanding image to $DISK_SIZE GB...\"\n        sudo truncate -s \"$DESIRED_SIZE_BYTES\" \"$IMG_FILE\" || handle_error \"Failed to expand $IMG_FILE\" 2>/dev/null\n\n        LOOP_DEV=$(sudo losetup --find --show \"$IMG_FILE\" 2>/dev/null) || handle_error \"Failed to setup loop device\" \n        sudo e2fsck -fy \"$LOOP_DEV\" || { sudo losetup -d \"$LOOP_DEV\"; handle_error \"Filesystem check failed\" ; } 2>/dev/null\n        sudo resize2fs \"$LOOP_DEV\" || { sudo losetup -d \"$LOOP_DEV\"; handle_error \"resize2fs after expand failed\" ; } 2>/dev/null\n        sudo losetup -d \"$LOOP_DEV\" 2>/dev/null\n\n    elif [ \"$CURRENT_SIZE_BYTES\" -gt \"$DESIRED_SIZE_BYTES\" ]; then\n        # echo \"[INFO] Shrinking image to $DISK_SIZE GB...\"\n        LOOP_DEV=$(sudo losetup --find --show \"$IMG_FILE\" 2>/dev/null) || handle_error \"Failed to setup loop device\" \n        sudo e2fsck -fy \"$LOOP_DEV\" || { sudo losetup -d \"$LOOP_DEV\"; handle_error \"Filesystem check failed\" ; } 2>/dev/null\n        sudo resize2fs -M \"$LOOP_DEV\" || { sudo losetup -d \"$LOOP_DEV\"; handle_error \"resize2fs -M failed\" ; } 2>/dev/null\n\n        BLOCKS=$(sudo tune2fs -l \"$LOOP_DEV\" | grep '^Block count:' | awk '{print $3}')\n        BLOCK_SIZE=$(sudo tune2fs -l \"$LOOP_DEV\" | grep '^Block size:' | awk '{print $3}')\n        MIN_BYTES=$((BLOCKS * BLOCK_SIZE))\n        sudo losetup -d \"$LOOP_DEV\" 2>/dev/null\n\n        if [ \"$DESIRED_SIZE_BYTES\" -lt \"$MIN_BYTES\" ]; then\n            handle_error \"DISK_SIZE too small. Minimum size is $((MIN_BYTES / 1024 / 1024 / 1024)) GB\"\n        fi\n\n        sudo truncate -s \"$DESIRED_SIZE_BYTES\" \"$IMG_FILE\" || handle_error \"Failed to truncate to desired size\"\n\n        LOOP_DEV=$(sudo losetup --find --show \"$IMG_FILE\" 2>/dev/null) || handle_error \"Failed to setup loop device (after shrink)\"\n        sudo resize2fs \"$LOOP_DEV\" || { sudo losetup -d \"$LOOP_DEV\"; handle_error \"resize2fs after shrink failed\" ; } 2>/dev/null\n        sudo losetup -d \"$LOOP_DEV\" 2>/dev/null\n    fi\n\n    # Remove the old line from /etc/fstab (if it exists) and add it again\n    sudo sed -i \"\\|$IMG_FILE|d\" /etc/fstab\n    echo \"$IMG_FILE $MOUNT_DIR ext4 loop 0 0\" | sudo tee -a /etc/fstab > /dev/null || handle_error \"Failed to update /etc/fstab\"\n\n    # Create the folder if it doesn't exist\n    sudo mkdir -p \"$MOUNT_DIR\"\n    sudo chmod 777 \"$MOUNT_DIR\"\n\n    # Try to mount manually\n    if ! sudo mount \"$MOUNT_DIR\"; then\n        echo \"[WARN] mount -a failed, trying manual mount with loop\"\n        LOOP_DEV=$(sudo losetup --find --show \"$IMG_FILE\") || handle_error \"Failed to setup loop device (manual)\"\n        sudo mount -t ext4 \"$LOOP_DEV\" \"$MOUNT_DIR\" || {\n            sudo losetup -d \"$LOOP_DEV\"\n            handle_error \"Manual mount failed\"\n        }\n    fi\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_OFFICE_FILE\" \"$VHOST_OFFICE_FILE\" || handle_error \"Failed to copy $NGINX_OFFICE_FILE to $VHOST_OFFICE_FILE\"\nsudo chmod 777 \"$VHOST_OFFICE_FILE\" || handle_error \"Failed to set permissions on $VHOST_OFFICE_FILE\"\n\nsudo cp -f \"$NGINX_OFFICE_LOCATION_FILE\" \"$VHOST_OFFICE_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_OFFICE_LOCATION_FILE to $VHOST_OFFICE_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_OFFICE_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_OFFICE_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# --- Function that installs Nextcloud Office (Collabora) in the background ---\ninstall_nextcloud_office() {\n    MAX_RETRIES=60\n    COUNTER=0\n\n\n    # 1) Wait until \"installed: true\" in occ status\n    while true; do\n        STATUS_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ status 2>&1)\"\n        if echo \"$STATUS_OUTPUT\" | grep -q \"installed: true\"; then\n            echo \"[OfficeSetup] Nextcloud reports installed: true. Proceeding...\" >> \"$STATUS_FILE\"\n            break\n        else\n            echo \"[OfficeSetup] [$COUNTER/$MAX_RETRIES] Nextcloud not fully installed yet, waiting...\" >> \"$STATUS_FILE\"\n            sleep 2\n            ((COUNTER++))\n            if [ $COUNTER -ge $MAX_RETRIES ]; then\n                echo \"[OfficeSetup] Nextcloud did not report 'installed: true' within time limit. Skipping Office install.\" >> \"$STATUS_FILE\"\n                return\n            fi\n        fi\n    done\n\n    # Get the nginx-proxy IP\n    PROXY_IP=$(get_proxy_ip)\n\n    echo \"[OfficeSetup] Detected nginx-proxy IP: $PROXY_IP\" >> \"$STATUS_FILE\"\n    \n\n    # Write the needed parameters to the Nextcloud config\n    echo \"[OfficeSetup] Setting overwrite protocol/host/cli.url in Nextcloud config...\" >> \"$STATUS_FILE\"\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwriteprotocol --value=https 2>&1\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwritehost --value=\"$DOMAIN\" 2>&1\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwrite.cli.url --value=\"https://$DOMAIN\" 2>&1\n\n    # Add the nginx-proxy IP to the trusted_proxies list\n    echo \"[OfficeSetup] Adding nginx-proxy IP to trusted_proxies...\" >> \"$STATUS_FILE\"\n    # *** NEW BLOCK *** - Get the IP address of the reverse proxy\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set trusted_proxies 0 --value=\"$PROXY_IP\" 2>&1\n\n    echo \"[OfficeSetup] Installing Nextcloud Office (richdocuments)...\" >> \"$STATUS_FILE\"\n\n    # 2) Install the richdocuments app\n    INSTALL_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:install richdocuments 2>&1 || echo \"[OfficeSetup] App already installed\")\"\n    echo \"[OfficeSetup] app:install richdocuments => $INSTALL_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 3) Set the Collabora Online URL in Nextcloud\n    WOPI_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:app:set richdocuments wopi_url --value=\"https://office.$DOMAIN/\" 2>&1)\"\n    echo \"[OfficeSetup] wopi_url => $WOPI_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 4) Enable the app\n    ENABLE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:enable richdocuments 2>&1)\"\n    echo \"[OfficeSetup] app:enable richdocuments => $ENABLE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 5) Allow local remote servers (Fix for Collabora access issues)\n    ALLOW_LOCAL_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set allow_local_remote_servers --value=true --type=bool 2>&1)\"\n    echo \"[OfficeSetup] allow_local_remote_servers => $ALLOW_LOCAL_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 6) Apply changes by running maintenance repair\n    REPAIR_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ maintenance:repair 2>&1)\"\n    echo \"[OfficeSetup] maintenance:repair => $REPAIR_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 7) Activate Collabora Online configuration\n    ACTIVATE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ richdocuments:activate-config 2>&1)\"\n    echo \"[OfficeSetup] richdocuments:activate-config => $ACTIVATE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 8) Refresh cache by scanning all files\n    SCAN_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ files:scan --all 2>&1)\"\n    echo \"[OfficeSetup] files:scan --all => $SCAN_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 9) Double-check if the app is enabled\n    APP_LIST=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:list 2>&1)\"\n    echo \"[OfficeSetup] occ app:list => $APP_LIST\" >> \"$STATUS_FILE\"\n\n    # 10) Perform the migrations\n    MIGRATION_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ maintenance:repair --include-expensive 2>&1)\"\n    echo \"[OfficeSetup] maintenance:repair --include-expensive => $MIGRATION_OUTPUT\" >> \"$STATUS_FILE\"\n\n    if echo \"$APP_LIST\" | grep -q \"richdocuments: enabled\"; then\n        echo \"[OfficeSetup] Nextcloud Office successfully installed and configured!\" >> \"$STATUS_FILE\"\n    else\n        echo \"[OfficeSetup] Nextcloud Office installation failed or not enabled.\" >> \"$STATUS_FILE\"\n    fi\n\n    OFFICE_IP_SUBNET=$(get_office_ip)\n    echo \"[OfficeSetup] Detected office IP: $OFFICE_IP_SUBNET\" >> \"$STATUS_FILE\"\n\n    # Write the needed parameters to the Collabora config\n    # 1) Collabora \n    ACTIVATE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:app:set richdocuments wopi_allowlist --value=\"$OFFICE_IP_SUBNET\" 2>&1)\"\n    echo \"[OfficeSetup] richdocuments:wopi_allowlist => $ACTIVATE_OUTPUT\" >> \"$STATUS_FILE\"\n\n}\n\n# Export DOMAIN so it's visible to the function in background\nexport DOMAIN\nexport CONTAINER_NAME\n\n# Export the get_proxy_ip function for visibility in nohup\nexport -f get_proxy_ip\n# Export the get_office_ip function for visibility in nohup\nexport -f get_office_ip\n\n\n# Run the installation in the background, no blocking\n nohup bash -c \"$(\n  declare -f install_nextcloud_office\n  echo 'install_nextcloud_office'\n )\" > /tmp/office_install.log 2>&1 &\n\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
    },
    {
      "id": "5c01e300-9eb2-42db-b609-1ddb1d0140e7",
      "name": "已终止",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1920,
        660
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "21f4453e-c136-4388-be90-1411ae78e8a5",
              "name": "sh",
              "type": "string",
              "value": "=#!/bin/bash\n\nDOMAIN=\"{{ $('API').item.json.body.domain }}\"\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}_nextcloud\"\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\"\nCRON_SCRIPT=\"{{ $('Parametrs').item.json.clients_dir }}/$DOMAIN/cron.sh\"\n\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\nVHOST_OFFICE_FILE=\"$VHOST_DIR/office.$DOMAIN\"\nVHOST_OFFICE_LOCATION_FILE=\"$VHOST_DIR/office.$DOMAIN\"_location\nMOUNT_DIR=\"{{ $('Parametrs').item.json.mount_dir }}/$DOMAIN\"\n\n# Function to log an error, write to status file, and print to office\nhandle_error() {\n    STATUS_JSON=\"{\\\"status\\\": \\\"error\\\", \\\"message\\\": \\\"$1\\\"}\"\n    echo \"error: $1\"\n    exit 1\n}\n\n# Function to remove Nextcloud cron job\nremove_nextcloud_cron() {\n    echo \"[CRON] Removing Nextcloud cron job...\" >> /dev/null\n    \n    # Remove from crontab\n    crontab -l 2>/dev/null | grep -v \"$CONTAINER_NAME\" | crontab -\n    \n    echo \"[CRON] Nextcloud cron job removed successfully!\" >> /dev/null\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_OFFICE_FILE\" \"$VHOST_OFFICE_LOCATION_FILE\"; do\n    if [ -e \"$item\" ]; then\n        sudo rm -rf \"$item\" || handle_error \"Failed to remove $item\"\n    fi\ndone\n\nexport CONTAINER_NAME\n\n# Remove the cron after execution\nremove_nextcloud_cron\n\necho \"success\"\nexit 0\n"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "264dac81-0eda-4a49-b209-c7dda4dd649d",
      "name": "恢复",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1920,
        480
      ],
      "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='{{ JSON.stringify($('Deploy-docker-compose').item.json[\"docker-compose\"]).base64Encode() }}'\n\nNGINX_MAIN_TEXT='{{ JSON.stringify($('nginx').item.json['main']).base64Encode() }}'\nNGINX_MAIN_FILE=\"$NGINX_DIR/$DOMAIN\"\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\n\nNGINX_MAIN_LOCATION_TEXT='{{ JSON.stringify($('nginx').item.json['main_location']).base64Encode() }}'\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$DOMAIN\"_location\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\n\nNGINX_OFFICE_TEXT='{{ JSON.stringify($('nginx').item.json['office']).base64Encode() }}'\nNGINX_OFFICE_FILE=\"$NGINX_DIR/office.$DOMAIN\"\nVHOST_OFFICE_FILE=\"$VHOST_DIR/office.$DOMAIN\"\n\nNGINX_OFFICE_LOCATION_TEXT='{{ JSON.stringify($('nginx').item.json['office_location']).base64Encode() }}'\nNGINX_OFFICE_LOCATION_FILE=\"$NGINX_DIR/office.$DOMAIN\"_location\nVHOST_OFFICE_LOCATION_FILE=\"$VHOST_DIR/office.$DOMAIN\"_location\n\nDISK_SIZE=\"{{ $('API').item.json.body.disk }}\"\n\n# Function to log an error, write to status file, and print to office\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\n# Get nginx-proxy IP address before installing Nextcloud Office\nget_proxy_ip() {\n    local ip=\"\"\n    local retries=10  # Try a few times\n    local count=0\n    while [[ -z \"$ip\" && $count -lt $retries ]]; do\n        ip=$(sudo docker inspect -f '{{ $('Parametrs').item.json.screen_left }}range .NetworkSettings.Networks{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}.IPAddress{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}end{{ $('Parametrs').item.json.screen_right }}' nginx-proxy)\n        if [[ -z \"$ip\" ]]; then\n            echo \"[DEBUG] nginx-proxy IP not found, retrying ($count/$retries)...\" >> \"$STATUS_FILE\"\n            sleep 2  # Wait a bit before retrying\n        fi\n        ((count++))\n    done\n\n    if [[ -z \"$ip\" ]]; then\n        echo \"[ERROR] Failed to retrieve nginx-proxy IP after $retries attempts!\" >> \"$STATUS_FILE\"\n        handle_error \"Failed to retrieve nginx-proxy IP\"\n    fi\n\n    echo \"[DEBUG] Detected nginx-proxy IP: $ip\" >> \"$STATUS_FILE\"\n    echo \"$ip\"\n}\n\n# Get the IP address of Nextcloud Office\nget_office_ip() {\n    local ip=\"\"\n    local retries=10  # Try a few times\n    local count=0\n    while [[ -z \"$ip\" && $count -lt $retries ]]; do\n        ip=$(sudo docker inspect -f '{{ $('Parametrs').item.json.screen_left }}range .NetworkSettings.Networks{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}.IPAddress{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}end{{ $('Parametrs').item.json.screen_right }}' \"$DOMAIN\"_collabora)\n        if [[ -z \"$ip\" ]]; then\n            echo \"[DEBUG] office IP not found, retrying ($count/$retries)...\" >> \"$STATUS_FILE\"\n            sleep 2  # Wait a bit before retrying\n        fi\n        ((count++))\n    done\n\n    if [[ -z \"$ip\" ]]; then\n        echo \"[ERROR] Failed to retrieve office IP after $retries attempts!\" >> \"$STATUS_FILE\"\n        handle_error \"Failed to retrieve office IP\"\n    fi\n\n    # Convert IP to subnet by replacing the last octet with 0 and adding /24\n    local subnet=$(echo \"$ip\" | sed 's/\\.[0-9]*$/.0\\/24/')\n    echo \"[DEBUG] Detected office subnet: $subnet\" >> \"$STATUS_FILE\"\n    echo \"$subnet\"\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 -e \"$DOCKER_COMPOSE_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$COMPOSE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $COMPOSE_FILE\"\n\n# Create NGINX configuration files\necho -e \"$NGINX_MAIN_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_MAIN_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_FILE\"\necho -e \"$NGINX_MAIN_LOCATION_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_MAIN_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_LOCATION_FILE\"\n\necho -e \"$NGINX_OFFICE_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_OFFICE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_OFFICE_FILE\"\necho -e \"$NGINX_OFFICE_LOCATION_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_OFFICE_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_OFFICE_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_OFFICE_FILE\" \"$VHOST_OFFICE_FILE\" || handle_error \"Failed to copy $NGINX_OFFICE_FILE to $VHOST_OFFICE_FILE\"\nsudo chmod 777 \"$VHOST_OFFICE_FILE\" || handle_error \"Failed to set permissions on $VHOST_OFFICE_FILE\"\n\nsudo cp -f \"$NGINX_OFFICE_LOCATION_FILE\" \"$VHOST_OFFICE_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_OFFICE_LOCATION_FILE to $VHOST_OFFICE_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_OFFICE_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_OFFICE_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\n\n# Function to add Nextcloud cron job\nadd_nextcloud_cron() {\n    echo \"[CRON] Adding Nextcloud cron job...\" >> /dev/null\n    \n    # Create cron command\n    CRON_CMD=\"*/5 * * * * sudo docker exec -u www-data $CONTAINER_NAME php cron.php --force\"\n    \n    # Add to crontab (remove old if exists)\n    (crontab -l 2>/dev/null | grep -v \"$CONTAINER_NAME\"; echo \"$CRON_CMD\") | crontab -\n    \n    echo \"[CRON] Nextcloud cron job added successfully!\" >> /dev/null\n}\n\n# Function to remove Nextcloud cron job\nremove_nextcloud_cron() {\n    echo \"[CRON] Removing Nextcloud cron job...\" >> /dev/null\n    \n    # Remove from crontab\n    crontab -l 2>/dev/null | grep -v \"$CONTAINER_NAME\" | crontab -\n    \n    echo \"[CRON] Nextcloud cron job removed successfully!\" >> /dev/null\n}\n\n\n\n# --- Function that installs Nextcloud Office (Collabora) in the background ---\ninstall_nextcloud_office() {\n    MAX_RETRIES=60\n    COUNTER=0\n\n\n    # 1) Wait until \"installed: true\" in occ status\n    while true; do\n        STATUS_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ status 2>&1)\"\n        if echo \"$STATUS_OUTPUT\" | grep -q \"installed: true\"; then\n            echo \"[OfficeSetup] Nextcloud reports installed: true. Proceeding...\" >> \"$STATUS_FILE\"\n            break\n        else\n            echo \"[OfficeSetup] [$COUNTER/$MAX_RETRIES] Nextcloud not fully installed yet, waiting...\" >> \"$STATUS_FILE\"\n            sleep 2\n            ((COUNTER++))\n            if [ $COUNTER -ge $MAX_RETRIES ]; then\n                echo \"[OfficeSetup] Nextcloud did not report 'installed: true' within time limit. Skipping Office install.\" >> \"$STATUS_FILE\"\n                return\n            fi\n        fi\n    done\n\n    # Get the nginx-proxy IP\n    PROXY_IP=$(get_proxy_ip)\n\n    echo \"[OfficeSetup] Detected nginx-proxy IP: $PROXY_IP\" >> \"$STATUS_FILE\"\n    \n\n    # Write the needed parameters to the Nextcloud config\n    echo \"[OfficeSetup] Setting overwrite protocol/host/cli.url in Nextcloud config...\" >> \"$STATUS_FILE\"\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwriteprotocol --value=https 2>&1\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwritehost --value=\"$DOMAIN\" 2>&1\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwrite.cli.url --value=\"https://$DOMAIN\" 2>&1\n\n    # Add the nginx-proxy IP to the trusted_proxies list\n    echo \"[OfficeSetup] Adding nginx-proxy IP to trusted_proxies...\" >> \"$STATUS_FILE\"\n    # *** NEW BLOCK *** - Get the IP address of the reverse proxy\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set trusted_proxies 0 --value=\"$PROXY_IP\" 2>&1\n\n    echo \"[OfficeSetup] Installing Nextcloud Office (richdocuments)...\" >> \"$STATUS_FILE\"\n\n    # 2) Install the richdocuments app\n    INSTALL_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:install richdocuments 2>&1 || echo \"[OfficeSetup] App already installed\")\"\n    echo \"[OfficeSetup] app:install richdocuments => $INSTALL_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 3) Set the Collabora Online URL in Nextcloud\n    WOPI_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:app:set richdocuments wopi_url --value=\"https://office.$DOMAIN/\" 2>&1)\"\n    echo \"[OfficeSetup] wopi_url => $WOPI_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 4) Enable the app\n    ENABLE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:enable richdocuments 2>&1)\"\n    echo \"[OfficeSetup] app:enable richdocuments => $ENABLE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 5) Allow local remote servers (Fix for Collabora access issues)\n    ALLOW_LOCAL_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set allow_local_remote_servers --value=true --type=bool 2>&1)\"\n    echo \"[OfficeSetup] allow_local_remote_servers => $ALLOW_LOCAL_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 6) Apply changes by running maintenance repair\n    REPAIR_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ maintenance:repair 2>&1)\"\n    echo \"[OfficeSetup] maintenance:repair => $REPAIR_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 7) Activate Collabora Online configuration\n    ACTIVATE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ richdocuments:activate-config 2>&1)\"\n    echo \"[OfficeSetup] richdocuments:activate-config => $ACTIVATE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 8) Refresh cache by scanning all files\n    SCAN_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ files:scan --all 2>&1)\"\n    echo \"[OfficeSetup] files:scan --all => $SCAN_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 9) Double-check if the app is enabled\n    APP_LIST=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:list 2>&1)\"\n    echo \"[OfficeSetup] occ app:list => $APP_LIST\" >> \"$STATUS_FILE\"\n\n    # 10) Perform the migrations\n    MIGRATION_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ maintenance:repair --include-expensive 2>&1)\"\n    echo \"[OfficeSetup] maintenance:repair --include-expensive => $MIGRATION_OUTPUT\" >> \"$STATUS_FILE\"\n\n    if echo \"$APP_LIST\" | grep -q \"richdocuments: enabled\"; then\n        echo \"[OfficeSetup] Nextcloud Office successfully installed and configured!\" >> \"$STATUS_FILE\"\n    else\n        echo \"[OfficeSetup] Nextcloud Office installation failed or not enabled.\" >> \"$STATUS_FILE\"\n    fi\n\n    OFFICE_IP_SUBNET=$(get_office_ip)\n    echo \"[OfficeSetup] Detected office IP: $OFFICE_IP_SUBNET\" >> \"$STATUS_FILE\"\n\n    # Write the needed parameters to the Collabora config\n    # 1) Collabora \n    ACTIVATE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:app:set richdocuments wopi_allowlist --value=\"$OFFICE_IP_SUBNET\" 2>&1)\"\n    echo \"[OfficeSetup] richdocuments:wopi_allowlist => $ACTIVATE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 2) Add Nextcloud cron job\n    add_nextcloud_cron\n}\n\n# Export DOMAIN so it's visible to the function in background\nexport DOMAIN\nexport CONTAINER_NAME\n\n# Export the get_proxy_ip function for visibility in nohup\nexport -f get_proxy_ip\n# Export the get_office_ip function for visibility in nohup\nexport -f get_office_ip\n# Export the add_nextcloud_cron function for visibility in nohup\nexport -f add_nextcloud_cron\n\n\n# Run the installation in the background\nnohup bash -c \"$(\n  declare -f install_nextcloud_office\n  echo 'install_nextcloud_office'\n )\" > /tmp/office_install.log 2>&1 &\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
    },
    {
      "id": "616b6f8b-707f-473f-8065-f3e1623ece2c",
      "name": "暂停",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1920,
        320
      ],
      "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_OFFICE_FILE=\"$VHOST_DIR/office.$DOMAIN\"\nVHOST_OFFICE_LOCATION_FILE=\"$VHOST_DIR/office.$DOMAIN\"_location\n\n# Function to log an error, write to status file, and print to office\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_OFFICE_FILE\" ] && sudo rm -f \"$VHOST_OFFICE_FILE\" || handle_error \"Warning: $VHOST_OFFICE_FILE not found.\"\n[ -f \"$VHOST_OFFICE_LOCATION_FILE\" ] && sudo rm -f \"$VHOST_OFFICE_LOCATION_FILE\" || handle_error \"Warning: $VHOST_OFFICE_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
    },
    {
      "id": "e09ef109-d4bd-4d2f-acad-a442854bc299",
      "name": "部署",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1920,
        160
      ],
      "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 }}\"\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}_nextcloud\"\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='{{ JSON.stringify($('Deploy-docker-compose').item.json['docker-compose']).base64Encode() }}'\n\nNGINX_MAIN_TEXT='{{ JSON.stringify($('nginx').item.json['main']).base64Encode() }}'\nNGINX_MAIN_FILE=\"$NGINX_DIR/$DOMAIN\"\nVHOST_MAIN_FILE=\"$VHOST_DIR/$DOMAIN\"\n\nNGINX_MAIN_LOCATION_TEXT='{{ JSON.stringify($('nginx').item.json['main_location']).base64Encode() }}'\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$DOMAIN\"_location\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\n\nNGINX_OFFICE_TEXT='{{ JSON.stringify($('nginx').item.json['office']).base64Encode() }}'\nNGINX_OFFICE_FILE=\"$NGINX_DIR/office.$DOMAIN\"\nVHOST_OFFICE_FILE=\"$VHOST_DIR/office.$DOMAIN\"\n\nNGINX_OFFICE_LOCATION_TEXT='{{ JSON.stringify($('nginx').item.json['office_location']).base64Encode() }}'\nNGINX_OFFICE_LOCATION_FILE=\"$NGINX_DIR/office.$DOMAIN\"_location\nVHOST_OFFICE_LOCATION_FILE=\"$VHOST_DIR/office.$DOMAIN\"_location\n\nDISK_SIZE=\"{{ $('API').item.json.body.disk }}\"\n\n# Get nginx-proxy IP address before installing Nextcloud Office\nget_proxy_ip() {\n    local ip=\"\"\n    local retries=10  # Try a few times\n    local count=0\n    while [[ -z \"$ip\" && $count -lt $retries ]]; do\n        ip=$(sudo docker inspect -f '{{ $('Parametrs').item.json.screen_left }}range .NetworkSettings.Networks{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}.IPAddress{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}end{{ $('Parametrs').item.json.screen_right }}' nginx-proxy)\n        if [[ -z \"$ip\" ]]; then\n            echo \"[DEBUG] nginx-proxy IP not found, retrying ($count/$retries)...\" >> \"$STATUS_FILE\"\n            sleep 2  # Wait a bit before retrying\n        fi\n        ((count++))\n    done\n\n    if [[ -z \"$ip\" ]]; then\n        echo \"[ERROR] Failed to retrieve nginx-proxy IP after $retries attempts!\" >> \"$STATUS_FILE\"\n        handle_error \"Failed to retrieve nginx-proxy IP\"\n    fi\n\n    echo \"[DEBUG] Detected nginx-proxy IP: $ip\" >> \"$STATUS_FILE\"\n    echo \"$ip\"\n}\n\n# Get the IP address of Nextcloud Office\nget_office_ip() {\n    local ip=\"\"\n    local retries=10  # Try a few times\n    local count=0\n    while [[ -z \"$ip\" && $count -lt $retries ]]; do\n        ip=$(sudo docker inspect -f '{{ $('Parametrs').item.json.screen_left }}range .NetworkSettings.Networks{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}.IPAddress{{ $('Parametrs').item.json.screen_right }}{{ $('Parametrs').item.json.screen_left }}end{{ $('Parametrs').item.json.screen_right }}' \"$DOMAIN\"_collabora)\n        if [[ -z \"$ip\" ]]; then\n            echo \"[DEBUG] office IP not found, retrying ($count/$retries)...\" >> \"$STATUS_FILE\"\n            sleep 2  # Wait a bit before retrying\n        fi\n        ((count++))\n    done\n\n    if [[ -z \"$ip\" ]]; then\n        echo \"[ERROR] Failed to retrieve office IP after $retries attempts!\" >> \"$STATUS_FILE\"\n        handle_error \"Failed to retrieve office IP\"\n    fi\n\n    # Convert IP to subnet by replacing the last octet with 0 and adding /24\n    local subnet=$(echo \"$ip\" | sed 's/\\.[0-9]*$/.0\\/24/')\n    echo \"[DEBUG] Detected office subnet: $subnet\" >> \"$STATUS_FILE\"\n    echo \"$subnet\"\n}\n\n# Function to handle errors: write to the status file and print the message to office\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 office\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 -e \"$DOCKER_COMPOSE_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$COMPOSE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $COMPOSE_FILE\"\n\n# Create NGINX configuration files\necho -e \"$NGINX_MAIN_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_MAIN_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_FILE\"\necho -e \"$NGINX_MAIN_LOCATION_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_MAIN_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_MAIN_LOCATION_FILE\"\n\necho -e \"$NGINX_OFFICE_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_OFFICE_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_OFFICE_FILE\"\necho -e \"$NGINX_OFFICE_LOCATION_TEXT\" | base64 --decode | sed 's/\\\\n/\\n/g' | sed 's/\\\\\"/\"/g' | sed '1s/^\"//' | sed '$s/\"$//' | sudo tee \"$NGINX_OFFICE_LOCATION_FILE\" > /dev/null 2>&1 || handle_error \"Failed to create $NGINX_OFFICE_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_OFFICE_FILE\" \"$VHOST_OFFICE_FILE\" || handle_error \"Failed to copy $NGINX_OFFICE_FILE to $VHOST_OFFICE_FILE\"\nsudo chmod 777 \"$VHOST_OFFICE_FILE\" || handle_error \"Failed to set permissions on $VHOST_OFFICE_FILE\"\n\nsudo cp -f \"$NGINX_OFFICE_LOCATION_FILE\" \"$VHOST_OFFICE_LOCATION_FILE\" || handle_error \"Failed to copy $NGINX_OFFICE_LOCATION_FILE to $VHOST_OFFICE_LOCATION_FILE\"\nsudo chmod 777 \"$VHOST_OFFICE_LOCATION_FILE\" || handle_error \"Failed to set permissions on $VHOST_OFFICE_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# Function to add Nextcloud cron job\nadd_nextcloud_cron() {\n    echo \"[CRON] Adding Nextcloud cron job...\" >> /dev/null\n    \n    # Create cron command\n    CRON_CMD=\"*/5 * * * * sudo docker exec -u www-data $CONTAINER_NAME php cron.php --force\"\n    \n    # Add to crontab (remove old if exists)\n    (crontab -l 2>/dev/null | grep -v \"$CONTAINER_NAME\"; echo \"$CRON_CMD\") | crontab -\n    \n    echo \"[CRON] Nextcloud cron job added successfully!\" >> /dev/null\n}\n\n# Function to remove Nextcloud cron job\nremove_nextcloud_cron() {\n    echo \"[CRON] Removing Nextcloud cron job...\" >> /dev/null\n    \n    # Remove from crontab\n    crontab -l 2>/dev/null | grep -v \"$CONTAINER_NAME\" | crontab -\n    \n    echo \"[CRON] Nextcloud cron job removed successfully!\" >> /dev/null\n}\n\n\n\n# --- Function that installs Nextcloud Office (Collabora) in the background ---\ninstall_nextcloud_office() {\n    MAX_RETRIES=60\n    COUNTER=0\n\n\n    # 1) Wait until \"installed: true\" in occ status\n    while true; do\n        STATUS_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ status 2>&1)\"\n        if echo \"$STATUS_OUTPUT\" | grep -q \"installed: true\"; then\n            echo \"[OfficeSetup] Nextcloud reports installed: true. Proceeding...\" >> \"$STATUS_FILE\"\n            break\n        else\n            echo \"[OfficeSetup] [$COUNTER/$MAX_RETRIES] Nextcloud not fully installed yet, waiting...\" >> \"$STATUS_FILE\"\n            sleep 2\n            ((COUNTER++))\n            if [ $COUNTER -ge $MAX_RETRIES ]; then\n                echo \"[OfficeSetup] Nextcloud did not report 'installed: true' within time limit. Skipping Office install.\" >> \"$STATUS_FILE\"\n                return\n            fi\n        fi\n    done\n\n    # Get the nginx-proxy IP\n    PROXY_IP=$(get_proxy_ip)\n\n    echo \"[OfficeSetup] Detected nginx-proxy IP: $PROXY_IP\" >> \"$STATUS_FILE\"\n    \n\n    # Write the needed parameters to the Nextcloud config\n    echo \"[OfficeSetup] Setting overwrite protocol/host/cli.url in Nextcloud config...\" >> \"$STATUS_FILE\"\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwriteprotocol --value=https 2>&1\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwritehost --value=\"$DOMAIN\" 2>&1\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set overwrite.cli.url --value=\"https://$DOMAIN\" 2>&1\n\n    # Add the nginx-proxy IP to the trusted_proxies list\n    echo \"[OfficeSetup] Adding nginx-proxy IP to trusted_proxies...\" >> \"$STATUS_FILE\"\n    # *** NEW BLOCK *** - Get the IP address of the reverse proxy\n    sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set trusted_proxies 0 --value=\"$PROXY_IP\" 2>&1\n\n    echo \"[OfficeSetup] Installing Nextcloud Office (richdocuments)...\" >> \"$STATUS_FILE\"\n\n    # 2) Install the richdocuments app\n    INSTALL_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:install richdocuments 2>&1 || echo \"[OfficeSetup] App already installed\")\"\n    echo \"[OfficeSetup] app:install richdocuments => $INSTALL_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 3) Set the Collabora Online URL in Nextcloud\n    WOPI_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:app:set richdocuments wopi_url --value=\"https://office.$DOMAIN/\" 2>&1)\"\n    echo \"[OfficeSetup] wopi_url => $WOPI_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 4) Enable the app\n    ENABLE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:enable richdocuments 2>&1)\"\n    echo \"[OfficeSetup] app:enable richdocuments => $ENABLE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 5) Allow local remote servers (Fix for Collabora access issues)\n    ALLOW_LOCAL_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:system:set allow_local_remote_servers --value=true --type=bool 2>&1)\"\n    echo \"[OfficeSetup] allow_local_remote_servers => $ALLOW_LOCAL_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 6) Apply changes by running maintenance repair\n    REPAIR_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ maintenance:repair 2>&1)\"\n    echo \"[OfficeSetup] maintenance:repair => $REPAIR_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 7) Activate Collabora Online configuration\n    ACTIVATE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ richdocuments:activate-config 2>&1)\"\n    echo \"[OfficeSetup] richdocuments:activate-config => $ACTIVATE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 8) Refresh cache by scanning all files\n    SCAN_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ files:scan --all 2>&1)\"\n    echo \"[OfficeSetup] files:scan --all => $SCAN_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 9) Double-check if the app is enabled\n    APP_LIST=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ app:list 2>&1)\"\n    echo \"[OfficeSetup] occ app:list => $APP_LIST\" >> \"$STATUS_FILE\"\n\n    # 10) Perform the migrations\n    MIGRATION_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ maintenance:repair --include-expensive 2>&1)\"\n    echo \"[OfficeSetup] maintenance:repair --include-expensive => $MIGRATION_OUTPUT\" >> \"$STATUS_FILE\"\n\n    if echo \"$APP_LIST\" | grep -q \"richdocuments: enabled\"; then\n        echo \"[OfficeSetup] Nextcloud Office successfully installed and configured!\" >> \"$STATUS_FILE\"\n    else\n        echo \"[OfficeSetup] Nextcloud Office installation failed or not enabled.\" >> \"$STATUS_FILE\"\n    fi\n\n    OFFICE_IP_SUBNET=$(get_office_ip)\n    echo \"[OfficeSetup] Detected office IP: $OFFICE_IP_SUBNET\" >> \"$STATUS_FILE\"\n\n    # Write the needed parameters to the Collabora config\n    # 1) Collabora \n    ACTIVATE_OUTPUT=\"$(sudo docker exec -u www-data \"$CONTAINER_NAME\" php occ config:app:set richdocuments wopi_allowlist --value=\"$OFFICE_IP_SUBNET\" 2>&1)\"\n    echo \"[OfficeSetup] richdocuments:wopi_allowlist => $ACTIVATE_OUTPUT\" >> \"$STATUS_FILE\"\n\n    # 2) Add Nextcloud cron job\n    add_nextcloud_cron\n}\n\n# Export DOMAIN so it's visible to the function in background\nexport DOMAIN\nexport CONTAINER_NAME\n\n# Export the get_proxy_ip function for visibility in nohup\nexport -f get_proxy_ip\n# Export the get_office_ip function for visibility in nohup\nexport -f get_office_ip\n# Export the add_nextcloud_cron function for visibility in nohup\nexport -f add_nextcloud_cron\n\n\n# Run the installation in the background\nnohup bash -c \"$(\n  declare -f install_nextcloud_office\n  echo 'install_nextcloud_office'\n )\" > /tmp/office_install.log 2>&1 &\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
    },
    {
      "id": "8b9f1482-cc21-4f7b-aa82-fdb47643d807",
      "name": "服务操作",
      "type": "n8n-nodes-base.switch",
      "position": [
        1640,
        140
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "test_connection",
              "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": "create",
              "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": "suspend",
              "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": "unsuspend",
              "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": "terminate",
              "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": "change_package",
              "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
    },
    {
      "id": "b27da6f4-859b-4b8a-9542-f0cad5f2cbfc",
      "name": "条件判断1",
      "type": "n8n-nodes-base.if",
      "position": [
        920,
        320
      ],
      "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
    },
    {
      "id": "0af4d346-d369-412b-b8f1-9847c5deb645",
      "name": "依赖容器状态",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1300,
        1180
      ],
      "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\nCONTAINER_NAME_ML=\"{{ $('API').item.json.body.domain }}_collabora\"\nCONTAINER_NAME_DB=\"{{ $('API').item.json.body.domain }}_db\"\nCONTAINER_NAME_REDIS=\"{{ $('API').item.json.body.domain }}_redis\"\n\n# Initialize empty container data\nINSPECT_JSON_ML=\"{}\"\nSTATS_JSON_ML=\"{}\"\n\nINSPECT_JSON_DB=\"{}\"\nSTATS_JSON_DB=\"{}\"\n\nINSPECT_JSON_REDIS=\"{}\"\nSTATS_JSON_REDIS=\"{}\"\n\n# Check if container is running\nif sudo docker ps -a --filter \"name=$CONTAINER_NAME_ML\" | grep -q \"$CONTAINER_NAME_ML\"; then\n  # Get Docker inspect info in JSON (as raw string)\n  INSPECT_JSON_ML=$(sudo docker inspect \"$CONTAINER_NAME_ML\")\n  # Get Docker stats info in JSON (as raw string)\n  STATS_JSON_ML=$(sudo docker stats --no-stream --format \"{{ $('Parametrs').item.json.screen_left }}json .{{ $('Parametrs').item.json.screen_right }}\" \"$CONTAINER_NAME_ML\")\n  STATS_JSON_ML=${STATS_JSON_ML:-'{}'}\nfi\n\n# Check if container is running\nif sudo docker ps -a --filter \"name=$CONTAINER_NAME_DB\" | grep -q \"$CONTAINER_NAME_DB\"; then\n  # Get Docker inspect info in JSON (as raw string)\n  INSPECT_JSON_DB=$(sudo docker inspect \"$CONTAINER_NAME_DB\")\n  # Get Docker stats info in JSON (as raw string)\n  STATS_JSON_DB=$(sudo docker stats --no-stream --format \"{{ $('Parametrs').item.json.screen_left }}json .{{ $('Parametrs').item.json.screen_right }}\" \"$CONTAINER_NAME_DB\")\n  STATS_JSON_DB=${STATS_JSON_DB:-'{}'}\nfi\n\n# Check if container is running\nif sudo docker ps -a --filter \"name=$CONTAINER_NAME_REDIS\" | grep -q \"$CONTAINER_NAME_REDIS\"; then\n  # Get Docker inspect info in JSON (as raw string)\n  INSPECT_JSON_REDIS=$(sudo docker inspect \"$CONTAINER_NAME_REDIS\")\n  # Get Docker stats info in JSON (as raw string)\n  STATS_JSON_REDIS=$(sudo docker stats --no-stream --format \"{{ $('Parametrs').item.json.screen_left }}json .{{ $('Parametrs').item.json.screen_right }}\" \"$CONTAINER_NAME_REDIS\")\n  STATS_JSON_REDIS=${STATS_JSON_REDIS:-'{}'}\nfi\n\n# Manually create a combined JSON object\nFINAL_JSON=\"{\\\"inspect_ml\\\": $INSPECT_JSON_ML, \\\"stats_ml\\\": $STATS_JSON_ML,\\\"inspect_db\\\": $INSPECT_JSON_DB, \\\"stats_db\\\": $STATS_JSON_DB,\\\"inspect_redis\\\": $INSPECT_JSON_REDIS, \\\"stats_redis\\\": $STATS_JSON_REDIS}\"\n\n# Output the result\necho \"$FINAL_JSON\"\n\nexit 0"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "2e53a360-deb3-41e7-8ddd-c06a3733e4bd",
      "name": "获取 ACL",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1400,
        2140
      ],
      "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\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_MAIN_ACL_FILE\" ]]; then\n    MAIN_IPS=$(cat \"$NGINX_MAIN_ACL_FILE\" | jq -R -s 'split(\"\\n\") | map(select(length > 0))')\nelse\n    MAIN_IPS=\"[]\"\nfi\n\n# Output JSON\necho \"{ \\\"main_ips\\\": $MAIN_IPS}\"\n\nexit 0\n"
            }
          ]
        }
      },
      "typeVersion": 3.4,
      "alwaysOutputData": true
    },
    {
      "id": "ec319d39-328f-4af6-a6d1-23ba6efb11d2",
      "name": "设置 ACL",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1400,
        2320
      ],
      "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.main_ips }}\"\nVHOST_MAIN_LOCATION_FILE=\"$VHOST_DIR/$DOMAIN\"_location\nNGINX_MAIN_LOCATION_FILE=\"$NGINX_DIR/$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\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\nupdate_nginx_acl \"$NGINX_MAIN_ACL_FILE\" \"$VHOST_MAIN_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
    },
    {
      "id": "a80322f9-1d95-4d09-b659-e98cfd31ed4b",
      "name": "获取网络",
      "type": "n8n-nodes-base.set",
      "onError": "continueRegularOutput",
      "position": [
        1400,
        2460
      ],
      "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 }}\"\nCONTAINER_NAME=\"{{ $('API').item.json.body.domain }}_nextcloud\"\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 \"$CONTAINER_NAME\" 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
    },
    {
      "id": "3158fb78-50c5-4ee2-b9ff-34947867457d",
      "name": "如果2",
      "type": "n8n-nodes-base.if",
      "position": [
        3240,
        -320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ac3730e4-8776-486b-b393-60ef103d35ea",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $('Split domain').item.json.mainDomain }}",
              "rightValue": "d01-test.uuq.pl"
            },
            {
              "id": "5baca1f0-fa26-4b78-ae94-44b876ac4fee",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $('Split domain').item.json.mainDomain }}",
              "rightValue": "d02-test.uuq.pl"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8076c40a-793b-4831-af1d-6afe0bb46f35",
      "name": "分割域名",
      "type": "n8n-nodes-base.code",
      "position": [
        2740,
        -320
      ],
      "parameters": {
        "jsCode": "const domain = $('API').item.json.body.domain;\n\nconst parts = domain.split('.');\n\nlet subDomain = '';\nlet mainDomain = domain;\n\nif (parts.length > 2) {\n    subDomain = parts[0]; \n    mainDomain = parts.slice(1).join('.');\n}\n\nreturn {\n  json: {\n    subDomain: subDomain,\n    mainDomain: mainDomain\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "042d1043-a64e-4aa9-84b5-fa4f47591542",
      "name": "DNS 服务操作",
      "type": "n8n-nodes-base.switch",
      "position": [
        3640,
        -400
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "container_update_dns_record",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "8ac3b338-9407-4c8b-8e88-935cb017fbbe",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.command }}",
                    "rightValue": "container_update_dns_record"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "create",
              "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": "suspend",
              "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": "unsuspend",
              "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": "terminate",
              "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": "change_package",
              "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
    },
    {
      "id": "38117189-c233-40c3-8dd0-67d94f4e868a",
      "name": "DNS 参数",
      "type": "n8n-nodes-base.set",
      "position": [
        3000,
        -320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a6328600-7ee0-4031-9bdb-fcee99b79658",
              "name": "api_url",
              "type": "string",
              "value": "https://your.pdns.url"
            },
            {
              "id": "370ddc4e-0fc0-48f6-9b30-ebdfba72c62f",
              "name": "api_key",
              "type": "string",
              "value": "your_api_key"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f9cf3c3e-83d0-46ea-983b-7f536ae8356d",
      "name": "添加记录",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        4000,
        -440
      ],
      "parameters": {
        "url": "={{ $('DNS Parametrs').item.json.api_url }}/api/v1/servers/localhost/zones/{{ $('Split domain').item.json.mainDomain }}",
        "body": "={\n  \"rrsets\": [\n    {\n      \"name\": \"{{ $('API').item.json.body.domain }}.\",\n      \"type\": \"CNAME\",\n      \"changetype\": \"REPLACE\",\n      \"ttl\": 300,\n      \"records\": [\n        {\n          \"content\": \"{{ $('API').item.json.body.server_domain }}.\",\n          \"disabled\": false\n        }\n      ]\n    }\n  ]\n}\n",
        "method": "PATCH",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "rawContentType": "application/json",
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-Key",
              "value": "={{ $('DNS Parametrs').item.json.api_key }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "f50c9fd0-efaa-42b3-aa03-ff66ca400299",
      "name": "删除记录",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        4000,
        -280
      ],
      "parameters": {
        "url": "={{ $('DNS Parametrs').item.json.api_url }}/api/v1/servers/localhost/zones/{{ $('Split domain').item.json.mainDomain }}",
        "body": "={\n  \"rrsets\": [\n    {\n      \"name\": \"{{ $('API').item.json.body.domain }}.\",\n      \"type\": \"CNAME\",\n      \"changetype\": \"REPLACE\",\n      \"ttl\": 300,\n      \"records\": []\n    }\n  ]\n}\n",
        "method": "PATCH",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "rawContentType": "application/json",
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-Key",
              "value": "={{ $('DNS Parametrs').item.json.api_key }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "d1823b67-662c-4bab-815b-0dda8da8284e",
      "name": "API 响应1",
      "type": "n8n-nodes-base.respondToWebhook",
      "onError": "continueRegularOutput",
      "position": [
        4000,
        -580
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "{\n  \"status\": \"success\",\n  \"message\": \"\",\n  \"data\": \"\"\n}\n"
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "446fdfae-adf1-45c1-b237-705a235e735a",
      "name": "d01-test.uuq.pl",
      "type": "n8n-nodes-base.ssh",
      "onError": "continueErrorOutput",
      "position": [
        2900,
        1560
      ],
      "parameters": {
        "cwd": "=/",
        "command": "={{ $json.sh }}"
      },
      "credentials": {
        "sshPassword": {
          "id": "AxPODSmAvTNzqrJb",
          "name": "SSH puq on d01-test.uuq.pl"
        }
      },
      "executeOnce": true,
      "typeVersion": 1
    },
    {
      "id": "0a52c410-c82a-4cb6-872d-3dd328224db0",
      "name": "d02-test.uuq.pl",
      "type": "n8n-nodes-base.ssh",
      "onError": "continueErrorOutput",
      "position": [
        2900,
        1840
      ],
      "parameters": {
        "cwd": "=/",
        "command": "={{ $json.sh }}"
      },
      "credentials": {
        "sshPassword": {
          "id": "JseVEj5f5icL4csj",
          "name": "d02-test.uuq.pl"
        }
      },
      "executeOnce": true,
      "typeVersion": 1
    },
    {
      "id": "9721b93a-4b20-4ea6-965e-44277613edee",
      "name": "服务器切换",
      "type": "n8n-nodes-base.switch",
      "position": [
        2560,
        1700
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "d01-test.uuq.pl",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.server_domain }}",
                    "rightValue": "d01-test.uuq.pl"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "d02-test.uuq.pl",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "a032f373-4856-4b2d-b722-9a3ad36d12e7",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('API').item.json.body.server_domain }}",
                    "rightValue": "d02-test.uuq.pl"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "84d45b2c-e8bc-4a68-9c6a-051e12451c48",
      "name": "代码",
      "type": "n8n-nodes-base.code",
      "position": [
        3400,
        1740
      ],
      "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
    },
    {
      "id": "d7e8d93c-0292-44ef-ac21-8c934d60a750",
      "name": "API 响应2",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3800,
        1740
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    }
  ],
  "active": true,
  "pinData": {},
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveExecutionProgress": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "versionId": "db430021-ac5c-4b7d-8512-8a6f04dc4952",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Container Stats",
            "type": "main",
            "index": 0
          },
          {
            "node": "Container Actions",
            "type": "main",
            "index": 0
          },
          {
            "node": "NextCloud",
            "type": "main",
            "index": 0
          },
          {
            "node": "If1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "422-Invalid server domain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API": {
      "main": [
        [
          {
            "node": "Parametrs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If1": {
      "main": [
        [
          {
            "node": "nginx",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Service Actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If2": {
      "main": [
        [
          {
            "node": "DNS Service Actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "API answer2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stat": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stop": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Users": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "nginx": {
      "main": [
        [
          {
            "node": "Deploy-docker-compose",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deploy": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET ACL": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GET NET": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inspect": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SET ACL": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Suspend": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Version": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NextCloud": {
      "main": [
        [
          {
            "node": "Version",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Users",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Change Password",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parametrs": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unsuspend": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mount Disk": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Terminated": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split domain": {
      "main": [
        [
          {
            "node": "DNS Parametrs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unmount Disk": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ChangePackage": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DNS Parametrs": {
      "main": [
        [
          {
            "node": "If2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Servers Switch": {
      "main": [
        [
          {
            "node": "d01-test.uuq.pl",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "d02-test.uuq.pl",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Change Password": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Container Stats": {
      "main": [
        [
          {
            "node": "Inspect",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stat",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Dependent containers Stat",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Split domain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Service Actions": {
      "main": [
        [
          {
            "node": "Test Connection",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Deploy",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split domain",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Suspend",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Unsuspend",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Terminated",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split domain",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "ChangePackage",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split domain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test Connection": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "d01-test.uuq.pl": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "d02-test.uuq.pl": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Container Actions": {
      "main": [
        [
          {
            "node": "Start",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stop",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mount Disk",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Unmount Disk",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "GET ACL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "SET ACL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "GET NET",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DNS Service Actions": {
      "main": [
        [
          {
            "node": "Add record",
            "type": "main",
            "index": 0
          },
          {
            "node": "API answer1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Add record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Delete record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Add record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Delete record",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Add record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deploy-docker-compose": {
      "main": [
        [
          {
            "node": "Service Actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dependent containers Stat": {
      "main": [
        [
          {
            "node": "Servers Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 工程

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量44
分类1
节点类型9
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
PUQcloud

PUQcloud

@puqcloud

PUQ sp. z o.o. is an international IT company building solutions to automate cloud and IT service sales. We created PUQcloud — a free, open-source platform for billing, automation, and IT business management. We also develop modules for WHMCS and WISECP, helping people start their own business without big tech. Our offices are in Poland and Canada, with a global team.

外部链接
在 n8n.io 查看

分享此工作流