{
  "generated": "2026-10-06T14:48:26Z",
  "categories": [
    {
      "id": "Containers",
      "name": "Containers",
      "icon": "server",
      "color": "file"
    },
    {
      "id": "Security",
      "name": "Security",
      "icon": "shield",
      "color": "svc"
    },
    {
      "id": "Networking",
      "name": "Networking",
      "icon": "globe",
      "color": "term"
    },
    {
      "id": "Storage",
      "name": "Storage",
      "icon": "disk",
      "color": "sw"
    },
    {
      "id": "Databases",
      "name": "Databases",
      "icon": "database",
      "color": "file"
    },
    {
      "id": "Monitoring",
      "name": "Monitoring",
      "icon": "zap",
      "color": "ov"
    },
    {
      "id": "Web servers",
      "name": "Web servers",
      "icon": "globe",
      "color": "log"
    },
    {
      "id": "Games",
      "name": "Games",
      "icon": "play",
      "color": "term"
    }
  ],
  "plugins": [
    {
      "id": "docker",
      "name": "Docker",
      "version": "2.2.0",
      "author": "Ervisio team",
      "description": "Manage containers, compose stacks, images, volumes and networks. Live stats, logs, shell, templates and clean-up.",
      "icon": "server",
      "logo": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzI0OTZFRCIgZD0iTTEzLjk4MyAxMS4wNzhoMi4xMTlhLjE4Ni4xODYgMCAwMC4xODYtLjE4NVY5LjAwNmEuMTg2LjE4NiAwIDAwLS4xODYtLjE4NmgtMi4xMTlhLjE4NS4xODUgMCAwMC0uMTg1LjE4NXYxLjg4OGMwIC4xMDIuMDgzLjE4NS4xODUuMTg1bS0yLjk1NC01LjQzaDIuMTE4YS4xODYuMTg2IDAgMDAuMTg2LS4xODZWMy41NzRhLjE4Ni4xODYgMCAwMC0uMTg2LS4xODVoLTIuMTE4YS4xODUuMTg1IDAgMDAtLjE4NS4xODV2MS44ODhjMCAuMTAyLjA4Mi4xODUuMTg1LjE4NW0wIDIuNzE2aDIuMTE4YS4xODcuMTg3IDAgMDAuMTg2LS4xODZWNi4yOWEuMTg2LjE4NiAwIDAwLS4xODYtLjE4NWgtMi4xMThhLjE4NS4xODUgMCAwMC0uMTg1LjE4NXYxLjg4N2MwIC4xMDIuMDgyLjE4NS4xODUuMTg2bS0yLjkzIDBoMi4xMmEuMTg2LjE4NiAwIDAwLjE4NC0uMTg2VjYuMjlhLjE4NS4xODUgMCAwMC0uMTg1LS4xODVIOC4xYS4xODUuMTg1IDAgMDAtLjE4NS4xODV2MS44ODdjMCAuMTAyLjA4My4xODUuMTg1LjE4Nm0tMi45NjQgMGgyLjExOWEuMTg2LjE4NiAwIDAwLjE4NS0uMTg2VjYuMjlhLjE4NS4xODUgMCAwMC0uMTg1LS4xODVINS4xMzZhLjE4Ni4xODYgMCAwMC0uMTg2LjE4NXYxLjg4N2MwIC4xMDIuMDg0LjE4NS4xODYuMTg2bTUuODkzIDIuNzE1aDIuMTE4YS4xODYuMTg2IDAgMDAuMTg2LS4xODVWOS4wMDZhLjE4Ni4xODYgMCAwMC0uMTg2LS4xODZoLTIuMTE4YS4xODUuMTg1IDAgMDAtLjE4NS4xODV2MS44ODhjMCAuMTAyLjA4Mi4xODUuMTg1LjE4NW0tMi45MyAwaDIuMTJhLjE4NS4xODUgMCAwMC4xODQtLjE4NVY5LjAwNmEuMTg1LjE4NSAwIDAwLS4xODQtLjE4NmgtMi4xMmEuMTg1LjE4NSAwIDAwLS4xODQuMTg1djEuODg4YzAgLjEwMi4wODMuMTg1LjE4NS4xODVtLTIuOTY0IDBoMi4xMTlhLjE4NS4xODUgMCAwMC4xODUtLjE4NVY5LjAwNmEuMTg1LjE4NSAwIDAwLS4xODQtLjE4NmgtMi4xMmEuMTg2LjE4NiAwIDAwLS4xODYuMTg2djEuODg3YzAgLjEwMi4wODQuMTg1LjE4Ni4xODVtLTIuOTIgMGgyLjEyYS4xODUuMTg1IDAgMDAuMTg0LS4xODVWOS4wMDZhLjE4NS4xODUgMCAwMC0uMTg0LS4xODZoLTIuMTJhLjE4NS4xODUgMCAwMC0uMTg0LjE4NXYxLjg4OGMwIC4xMDIuMDgyLjE4NS4xODUuMTg1TTIzLjc2MyA5Ljg5Yy0uMDY1LS4wNTEtLjY3Mi0uNTEtMS45NTQtLjUxLS4zMzguMDAxLS42NzYuMDMtMS4wMS4wODctLjI0OC0xLjctMS42NTMtMi41My0xLjcxNi0yLjU2NmwtLjM0NC0uMTk5LS4yMjYuMzI3Yy0uMjg0LjQzOC0uNDkuOTIyLS42MTIgMS40My0uMjMuOTctLjA5IDEuODgyLjQwMyAyLjY2MS0uNTk1LjMzMi0xLjU1LjQxMy0xLjc0NC40MkguNzUxYS43NTEuNzUxIDAgMDAtLjc1Ljc0OCAxMS4zNzYgMTEuMzc2IDAgMDAuNjkyIDQuMDYyYy41NDUgMS40MjggMS4zNTUgMi40OCAyLjQxIDMuMTI0IDEuMTguNzIzIDMuMSAxLjEzNyA1LjI3NSAxLjEzNy45ODMuMDAzIDEuOTYzLS4wODYgMi45My0uMjY2YTEyLjI0OCAxMi4yNDggMCAwMDMuODIzLTEuMzg5Yy45OC0uNTY3IDEuODYtMS4yODggMi42MS0yLjEzNiAxLjI1Mi0xLjQxOCAxLjk5OC0yLjk5NyAyLjU1My00LjRoLjIyMWMxLjM3MiAwIDIuMjE1LS41NDkgMi42OC0xLjAwOS4zMDktLjI5My41NS0uNjUuNzA3LTEuMDQ2bC4wOTgtLjI4OFoiLz48L3N2Zz4=",
      "color": "file",
      "category": "Containers",
      "verified": true,
      "installs": 0,
      "featured": true,
      "notes": "2026-10-02. Needs Ervisio 0.5 or later (`minCore` is 0.5.0). New Attach tab: connect to the main process of a container in the same terminal as Shell. Ctrl+P then Ctrl+Q detaches and leaves it running. New Files tab: browse any folder of a container (also a stopped one, and images without `ls`), read text files, create folders and delete files. Upload a file of any size with progress, download a file under its own name, and download a folder as a `.tar`. \"Save as image\" (`docker commit`) with...",
      "minCore": "0.5.0",
      "source": "https://github.com/Ervisio/plugins/releases/download/docker-2.2.0/docker-2.2.0.tar.gz",
      "sha256": "fb17e9884ae6d2a2a16c507bbd6629b1a6b8151fd141fb31f5ab30086d527912",
      "capabilities": {
        "commands": [
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 128,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]*"
              },
              {
                "maxLen": 16,
                "pattern": "/bin/(sh|bash|ash|zsh)"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "exec",
              "-it",
              "-e",
              "TERM=xterm-256color",
              "{0}",
              "{1}"
            ],
            "description": "Open a shell inside a container",
            "name": "shell",
            "pty": true,
            "remote": "docker"
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 128,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]*"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "attach",
              "--sig-proxy=false",
              "--detach-keys=ctrl-p,ctrl-q",
              "{0}"
            ],
            "description": "Attach to the main process of a container (detach with Ctrl+P Ctrl+Q)",
            "name": "attach",
            "pty": true,
            "remote": "docker"
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "up",
              "-d",
              "--remove-orphans"
            ],
            "description": "Create or update the containers of a stack",
            "name": "compose-up",
            "remote": "docker",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "pull"
            ],
            "description": "Pull the images of a stack",
            "name": "compose-pull",
            "remote": "docker",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "down",
              "--remove-orphans"
            ],
            "description": "Stop and remove the containers of a stack",
            "name": "compose-down",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "down",
              "--remove-orphans",
              "--volumes"
            ],
            "description": "Stop and remove the containers and volumes of a stack",
            "name": "compose-down-volumes",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "restart"
            ],
            "description": "Restart the containers of a stack",
            "name": "compose-restart",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "stop"
            ],
            "description": "Stop the containers of a stack",
            "name": "compose-stop",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "start"
            ],
            "description": "Start the containers of a stack",
            "name": "compose-start",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 32,
                "pattern": "(\\.envs/env-[0-9a-f]{8}/)?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "--project-directory",
              "/opt/stacks/{1}{0}",
              "-f",
              "/opt/stacks/{1}{0}/compose.yaml",
              "config"
            ],
            "description": "Check a stack's compose file and show it fully resolved",
            "name": "compose-config",
            "remote": "docker",
            "timeoutSec": 60
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "down"
            ],
            "description": "Stop and remove the containers of a compose project, by project name",
            "name": "compose-p-down",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "restart"
            ],
            "description": "Restart the containers of a compose project, by project name",
            "name": "compose-p-restart",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "stop"
            ],
            "description": "Stop the containers of a compose project, by project name",
            "name": "compose-p-stop",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "start"
            ],
            "description": "Start the containers of a compose project, by project name",
            "name": "compose-p-start",
            "remote": "docker",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 400,
                "pattern": "(/[a-zA-Z0-9_@+-][a-zA-Z0-9_@+.-]*)+\\.ya?ml"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "--project-name",
              "{0}",
              "-f",
              "{1}",
              "config"
            ],
            "description": "Show a compose project outside /opt/stacks, fully resolved (read only)",
            "name": "compose-config-project",
            "remote": "docker",
            "timeoutSec": 60
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "argv": [
              "docker",
              "-H",
              "{env}",
              "compose",
              "ls",
              "-a",
              "--format",
              "json"
            ],
            "description": "List compose projects, including the ones not managed here",
            "name": "compose-ls",
            "remote": "docker",
            "timeoutSec": 30
          },
          {
            "admin": true,
            "argv": [
              "install",
              "-d",
              "-m",
              "2775",
              "-g",
              "docker",
              "/opt/stacks"
            ],
            "description": "Create the shared stacks folder /opt/stacks, writable by the docker group",
            "name": "stacks-init",
            "timeoutSec": 30
          },
          {
            "args": [],
            "argv": [
              "git",
              "--version"
            ],
            "description": "Check that git is installed",
            "name": "git-version",
            "timeoutSec": 10
          },
          {
            "args": [],
            "argv": [
              "ssh",
              "-V"
            ],
            "description": "Check that ssh is installed (needed for repositories that use a deploy key)",
            "name": "ssh-version",
            "timeoutSec": 10
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 512,
                "pattern": "https?://[A-Za-z0-9][A-Za-z0-9.-]*(:[0-9]+)?(/[A-Za-z0-9._~%+-]+)+/?|ssh://([A-Za-z0-9._-]+@)?[A-Za-z0-9][A-Za-z0-9.-]*(:[0-9]+)?(/[A-Za-z0-9._~%+-]+)+/?|[A-Za-z0-9._-]+@[A-Za-z0-9][A-Za-z0-9.-]*:[A-Za-z0-9._~%+-][A-Za-z0-9._~%+/-]*"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "ls-remote",
              "--symref",
              "--",
              "{1}"
            ],
            "description": "List the branches and tags of a Git repository",
            "name": "git-ls-remote",
            "timeoutSec": 60
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 512,
                "pattern": "https?://[A-Za-z0-9][A-Za-z0-9.-]*(:[0-9]+)?(/[A-Za-z0-9._~%+-]+)+/?|ssh://([A-Za-z0-9._-]+@)?[A-Za-z0-9][A-Za-z0-9.-]*(:[0-9]+)?(/[A-Za-z0-9._~%+-]+)+/?|[A-Za-z0-9._-]+@[A-Za-z0-9][A-Za-z0-9.-]*:[A-Za-z0-9._~%+-][A-Za-z0-9._~%+/-]*"
              },
              {
                "maxLen": 128,
                "pattern": "[A-Za-z0-9][A-Za-z0-9._/-]{0,127}"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "clone",
              "--depth",
              "1",
              "--single-branch",
              "--branch",
              "{2}",
              "--",
              "{1}",
              "/opt/stacks/{0}"
            ],
            "description": "Clone a Git repository into a new stack folder",
            "name": "git-clone",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 128,
                "pattern": "[A-Za-z0-9][A-Za-z0-9._/-]{0,127}"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "-c",
              "safe.directory=/opt/stacks/{0}",
              "-C",
              "/opt/stacks/{0}",
              "fetch",
              "--depth",
              "1",
              "--force",
              "origin",
              "{1}"
            ],
            "description": "Fetch a branch or tag of the stack repository",
            "name": "git-fetch",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "-c",
              "safe.directory=/opt/stacks/{0}",
              "-C",
              "/opt/stacks/{0}",
              "rev-parse",
              "HEAD"
            ],
            "description": "Commit the stack repository is at",
            "name": "git-rev-head",
            "timeoutSec": 30
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "-c",
              "safe.directory=/opt/stacks/{0}",
              "-C",
              "/opt/stacks/{0}",
              "rev-list",
              "-n",
              "1",
              "FETCH_HEAD"
            ],
            "description": "Commit of the last fetch",
            "name": "git-rev-fetched",
            "timeoutSec": 30
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "-c",
              "safe.directory=/opt/stacks/{0}",
              "-C",
              "/opt/stacks/{0}",
              "reset",
              "--hard",
              "FETCH_HEAD"
            ],
            "description": "Move the stack repository to the commit of the last fetch (replaces local edits)",
            "name": "git-reset",
            "timeoutSec": 60
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "-c",
              "safe.directory=/opt/stacks/{0}",
              "-C",
              "/opt/stacks/{0}",
              "log",
              "-1",
              "--format=%H%x1f%an%x1f%aI%x1f%s"
            ],
            "description": "Describe the commit the stack repository is at",
            "name": "git-info",
            "timeoutSec": 30
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "argv": [
              "git",
              "-c",
              "protocol.allow=never",
              "-c",
              "protocol.https.allow=always",
              "-c",
              "protocol.http.allow=always",
              "-c",
              "protocol.ssh.allow=always",
              "-c",
              "credential.helper=",
              "-c",
              "credential.helper=store --file ~/.config/ervisio/plugins/docker/git/{0}.cred",
              "-c",
              "core.sshCommand=ssh -i ~/.config/ervisio/plugins/docker/git/{0}.key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new",
              "-c",
              "core.askPass=",
              "-c",
              "http.lowSpeedLimit=1000",
              "-c",
              "http.lowSpeedTime=60",
              "-c",
              "safe.directory=/opt/stacks/{0}",
              "-C",
              "/opt/stacks/{0}",
              "status",
              "--porcelain",
              "--untracked-files=no"
            ],
            "description": "List local edits of tracked files in the stack repository",
            "name": "git-status",
            "timeoutSec": 30
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 200,
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "compose",
              "--project-name",
              "{0}",
              "-f",
              "/opt/stacks/{0}/{1}",
              "up",
              "-d",
              "--remove-orphans",
              "--build"
            ],
            "description": "Create or update the containers of a Git stack",
            "name": "compose-up-git",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 200,
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "compose",
              "--project-name",
              "{0}",
              "-f",
              "/opt/stacks/{0}/{1}",
              "pull",
              "--ignore-buildable",
              "--ignore-pull-failures"
            ],
            "description": "Pull the images of a Git stack",
            "name": "compose-pull-git",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 200,
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "compose",
              "--project-name",
              "{0}",
              "-f",
              "/opt/stacks/{0}/{1}",
              "config"
            ],
            "description": "Check the compose file of a Git stack",
            "name": "compose-config-git",
            "timeoutSec": 60
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 200,
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "compose",
              "--project-name",
              "{0}",
              "-f",
              "/opt/stacks/{0}/{1}",
              "down",
              "--remove-orphans"
            ],
            "description": "Remove the containers of a Git stack",
            "name": "compose-down-git",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 200,
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "compose",
              "--project-name",
              "{0}",
              "-f",
              "/opt/stacks/{0}/{1}",
              "down",
              "--remove-orphans",
              "--volumes"
            ],
            "description": "Remove the containers and volumes of a Git stack",
            "name": "compose-down-volumes-git",
            "timeoutSec": 300
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 63,
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "maxLen": 200,
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "argv": [
              "cp",
              "-f",
              "--",
              "/opt/stacks/{0}/{1}",
              "/opt/stacks/{0}/.compose.deployed.yaml"
            ],
            "description": "Remember the compose file a stack was last deployed with",
            "name": "stack-mark",
            "timeoutSec": 30
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 128,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "maxLen": 256,
                "pattern": "[a-z0-9][a-z0-9._/-]{0,127}(:[A-Za-z0-9._-]{1,128})?"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "run",
              "--rm",
              "--name",
              "{0}-redeploy",
              "-v",
              "/var/run/docker.sock:/var/run/docker.sock",
              "-e",
              "WATCHTOWER_RUN_ONCE=true",
              "-e",
              "WATCHTOWER_CLEANUP=true",
              "-e",
              "WATCHTOWER_NO_STARTUP_MESSAGE=true",
              "-e",
              "WATCHTOWER_LOG_FORMAT=LogFmt",
              "{1}",
              "{0}"
            ],
            "description": "Pull the image of a container and recreate it with the same settings (Watchtower, one run)",
            "name": "container-redeploy",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [],
            "argv": [
              "sh",
              "-c",
              "i=\"$1\"; d=\"docker -H unix:///var/run/docker.sock\"; $d image inspect \"$i\" >/dev/null 2>&1 && exit 0; $d pull --quiet \"$i\" >/dev/null || { echo \"Could not download the helper image $i. Check that this server can reach Docker Hub, or load it with docker load, then run the backup again.\" >&2; exit 1; }",
              "sh",
              "busybox:1.37"
            ],
            "description": "Make sure the helper image of volume backups is on this machine, pulling it when it is not",
            "name": "volume-image",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 128,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "maxLen": 3,
                "pattern": "[1-9][0-9]{0,2}"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "run",
              "--rm",
              "--network",
              "none",
              "-v",
              "{0}:/v:ro",
              "-v",
              "/var/backups/ervisio-docker/{0}:/out",
              "busybox:1.37",
              "sh",
              "-c",
              "set -e; f=/out/$(date -u +%Y-%m-%d_%H%M%S).tar; tar -cf \"$f.part\" -C /v .; mv \"$f.part\" \"$f\"; ls -1 /out/*.tar | sort -r | tail -n +$(($1+1)) | xargs -r rm -f --",
              "sh",
              "{1}"
            ],
            "description": "Write a tar of a volume to the backup folder and delete the oldest ones",
            "name": "volume-backup",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 128,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              }
            ],
            "argv": [
              "docker",
              "-H",
              "unix:///var/run/docker.sock",
              "volume",
              "inspect",
              "--format",
              "{{.Name}}",
              "{0}"
            ],
            "description": "Check that a volume exists",
            "name": "volume-check",
            "timeoutSec": 30
          },
          {
            "args": [
              {
                "maxLen": 128,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              }
            ],
            "argv": [
              "ls",
              "-l",
              "--time-style=long-iso",
              "/var/backups/ervisio-docker/{0}"
            ],
            "description": "List the backups of a volume",
            "name": "volume-backup-ls",
            "timeoutSec": 15
          }
        ],
        "files": {
          "read": [
            {
              "admin": true,
              "adminUnlessGroup": "docker",
              "path": "/opt/stacks"
            }
          ],
          "write": [
            {
              "admin": true,
              "adminUnlessGroup": "docker",
              "path": "/opt/stacks"
            },
            {
              "create": true,
              "path": "~/.config/ervisio/plugins/docker"
            }
          ]
        },
        "http": [
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "headers": [
              "Content-Type",
              "X-Registry-Auth",
              "X-Registry-Config"
            ],
            "maxBody": 8388608,
            "maxUpload": 1099511627776,
            "name": "docker",
            "remote": "docker",
            "rules": [
              {
                "methods": [
                  "GET",
                  "HEAD"
                ],
                "path": "/_ping"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/version"
              },
              {
                "methods": [
                  "GET",
                  "HEAD"
                ],
                "path": "/v1\\.[0-9]+/_ping"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/(version|info|events|system/df)"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/(containers|images|volumes|networks|build)/prune"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/containers/json"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/containers/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}/(json|logs|stats|top|changes|archive)"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/containers/create"
              },
              {
                "methods": [
                  "PUT"
                ],
                "path": "/v1\\.[0-9]+/containers/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}/archive"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/commit"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/containers/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}/(start|stop|restart|kill|pause|unpause|rename|update|exec)"
              },
              {
                "methods": [
                  "DELETE"
                ],
                "path": "/v1\\.[0-9]+/containers/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/exec/[a-f0-9]{64}/(start|resize)"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/exec/[a-f0-9]{64}/json"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/images/json"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/images/get"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/images/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*(?:/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*){0,9}/(json|history)"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/images/create"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/images/load"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/images/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*(?:/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*){0,9}/tag"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/images/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*(?:/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*){0,9}/push"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/build"
              },
              {
                "methods": [
                  "DELETE"
                ],
                "path": "/v1\\.[0-9]+/images/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*(?:/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*){0,9}"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/distribution/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*(?:/[a-zA-Z0-9_:@-]+(?:\\.[a-zA-Z0-9_:@-]+)*){0,9}/json"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/auth"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/volumes"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/volumes/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/volumes/create"
              },
              {
                "methods": [
                  "DELETE"
                ],
                "path": "/v1\\.[0-9]+/volumes/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/networks"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/networks/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/networks/create"
              },
              {
                "methods": [
                  "DELETE"
                ],
                "path": "/v1\\.[0-9]+/networks/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/networks/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}/(connect|disconnect)"
              }
            ],
            "socket": "/var/run/docker.sock",
            "timeoutSec": 120
          }
        ],
        "jobs": [
          {
            "description": "Update a Git stack when its branch or tag moved (quiet).",
            "name": "git-poll-n",
            "params": [
              {
                "description": "Stack name",
                "maxLen": 63,
                "name": "name",
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "description": "Branch or tag",
                "maxLen": 128,
                "name": "ref",
                "pattern": "[A-Za-z0-9][A-Za-z0-9._/-]{0,127}"
              },
              {
                "default": "compose.yaml",
                "description": "Compose file inside the repository",
                "maxLen": 200,
                "name": "file",
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.name}",
                  "{param.ref}"
                ],
                "command": "git-fetch",
                "id": "fetch"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-head",
                "id": "head"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-fetched",
                "id": "new"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-reset",
                "id": "reset",
                "if": {
                  "other": "head",
                  "step": "new",
                  "when": "differs"
                }
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-pull-git",
                "id": "pull",
                "if": {
                  "step": "reset",
                  "when": "ok"
                },
                "timeoutSec": 1200
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-up-git",
                "id": "up",
                "if": {
                  "step": "pull",
                  "when": "ok"
                },
                "timeoutSec": 1800
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "stack-mark",
                "continueOnError": true,
                "id": "mark",
                "if": {
                  "step": "up",
                  "when": "ok"
                }
              }
            ],
            "timeoutSec": 7200
          },
          {
            "description": "Update a Git stack when its branch or tag moved (notify on success).",
            "name": "git-poll-s",
            "params": [
              {
                "description": "Stack name",
                "maxLen": 63,
                "name": "name",
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "description": "Branch or tag",
                "maxLen": 128,
                "name": "ref",
                "pattern": "[A-Za-z0-9][A-Za-z0-9._/-]{0,127}"
              },
              {
                "default": "compose.yaml",
                "description": "Compose file inside the repository",
                "maxLen": 200,
                "name": "file",
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.name}",
                  "{param.ref}"
                ],
                "command": "git-fetch",
                "id": "fetch"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-head",
                "id": "head"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-fetched",
                "id": "new"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-reset",
                "id": "reset",
                "if": {
                  "other": "head",
                  "step": "new",
                  "when": "differs"
                }
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-pull-git",
                "id": "pull",
                "if": {
                  "step": "reset",
                  "when": "ok"
                },
                "timeoutSec": 1200
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-up-git",
                "id": "up",
                "if": {
                  "step": "pull",
                  "when": "ok"
                },
                "timeoutSec": 1800
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "stack-mark",
                "continueOnError": true,
                "id": "mark",
                "if": {
                  "step": "up",
                  "when": "ok"
                }
              },
              {
                "id": "said",
                "if": {
                  "step": "up",
                  "when": "ok"
                },
                "notify": {
                  "body": "Now at {step.new.stdout}",
                  "level": "success",
                  "link": "/p/docker",
                  "title": "Updated {param.name}"
                }
              }
            ],
            "timeoutSec": 7200
          },
          {
            "description": "Update a Git stack when its branch or tag moved (notify on failure).",
            "name": "git-poll-f",
            "params": [
              {
                "description": "Stack name",
                "maxLen": 63,
                "name": "name",
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "description": "Branch or tag",
                "maxLen": 128,
                "name": "ref",
                "pattern": "[A-Za-z0-9][A-Za-z0-9._/-]{0,127}"
              },
              {
                "default": "compose.yaml",
                "description": "Compose file inside the repository",
                "maxLen": 200,
                "name": "file",
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.name}",
                  "{param.ref}"
                ],
                "command": "git-fetch",
                "continueOnError": true,
                "id": "fetch"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-head",
                "continueOnError": true,
                "id": "head",
                "if": {
                  "step": "fetch",
                  "when": "ok"
                }
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-fetched",
                "continueOnError": true,
                "id": "new",
                "if": {
                  "step": "fetch",
                  "when": "ok"
                }
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-reset",
                "continueOnError": true,
                "id": "reset",
                "if": {
                  "other": "head",
                  "step": "new",
                  "when": "differs"
                }
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-pull-git",
                "continueOnError": true,
                "id": "pull",
                "if": {
                  "step": "reset",
                  "when": "ok"
                },
                "timeoutSec": 1200
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-up-git",
                "continueOnError": true,
                "id": "up",
                "if": {
                  "step": "pull",
                  "when": "ok"
                },
                "timeoutSec": 1800
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "stack-mark",
                "continueOnError": true,
                "id": "mark",
                "if": {
                  "step": "up",
                  "when": "ok"
                }
              },
              {
                "id": "fail_fetch",
                "if": {
                  "step": "fetch",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.fetch.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not fetch {param.name}"
                }
              },
              {
                "id": "fail_reset",
                "if": {
                  "step": "reset",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.reset.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not update the files of {param.name}"
                }
              },
              {
                "id": "fail_pull",
                "if": {
                  "step": "pull",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.pull.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not pull the images of {param.name}"
                }
              },
              {
                "id": "fail_up",
                "if": {
                  "step": "up",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.up.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not start {param.name}"
                }
              }
            ],
            "timeoutSec": 7200
          },
          {
            "description": "Update a Git stack when its branch or tag moved (notify on success and failure).",
            "name": "git-poll-b",
            "params": [
              {
                "description": "Stack name",
                "maxLen": 63,
                "name": "name",
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "description": "Branch or tag",
                "maxLen": 128,
                "name": "ref",
                "pattern": "[A-Za-z0-9][A-Za-z0-9._/-]{0,127}"
              },
              {
                "default": "compose.yaml",
                "description": "Compose file inside the repository",
                "maxLen": 200,
                "name": "file",
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.name}",
                  "{param.ref}"
                ],
                "command": "git-fetch",
                "continueOnError": true,
                "id": "fetch"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-head",
                "continueOnError": true,
                "id": "head",
                "if": {
                  "step": "fetch",
                  "when": "ok"
                }
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-rev-fetched",
                "continueOnError": true,
                "id": "new",
                "if": {
                  "step": "fetch",
                  "when": "ok"
                }
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-reset",
                "continueOnError": true,
                "id": "reset",
                "if": {
                  "other": "head",
                  "step": "new",
                  "when": "differs"
                }
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-pull-git",
                "continueOnError": true,
                "id": "pull",
                "if": {
                  "step": "reset",
                  "when": "ok"
                },
                "timeoutSec": 1200
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-up-git",
                "continueOnError": true,
                "id": "up",
                "if": {
                  "step": "pull",
                  "when": "ok"
                },
                "timeoutSec": 1800
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "stack-mark",
                "continueOnError": true,
                "id": "mark",
                "if": {
                  "step": "up",
                  "when": "ok"
                }
              },
              {
                "id": "said",
                "if": {
                  "step": "up",
                  "when": "ok"
                },
                "notify": {
                  "body": "Now at {step.new.stdout}",
                  "level": "success",
                  "link": "/p/docker",
                  "title": "Updated {param.name}"
                }
              },
              {
                "id": "fail_fetch",
                "if": {
                  "step": "fetch",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.fetch.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not fetch {param.name}"
                }
              },
              {
                "id": "fail_reset",
                "if": {
                  "step": "reset",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.reset.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not update the files of {param.name}"
                }
              },
              {
                "id": "fail_pull",
                "if": {
                  "step": "pull",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.pull.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not pull the images of {param.name}"
                }
              },
              {
                "id": "fail_up",
                "if": {
                  "step": "up",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.up.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Could not start {param.name}"
                }
              }
            ],
            "timeoutSec": 7200
          },
          {
            "description": "Pull a Git stack and redeploy it (webhook).",
            "name": "git-redeploy",
            "params": [
              {
                "description": "Stack name",
                "maxLen": 63,
                "name": "name",
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              },
              {
                "description": "Branch or tag",
                "maxLen": 128,
                "name": "ref",
                "pattern": "[A-Za-z0-9][A-Za-z0-9._/-]{0,127}"
              },
              {
                "default": "compose.yaml",
                "description": "Compose file inside the repository",
                "maxLen": 200,
                "name": "file",
                "pattern": "([A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*/)*[A-Za-z0-9_@+-][A-Za-z0-9_@+.-]*\\.ya?ml"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.name}",
                  "{param.ref}"
                ],
                "command": "git-fetch",
                "id": "fetch"
              },
              {
                "args": [
                  "{param.name}"
                ],
                "command": "git-reset",
                "id": "reset"
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-pull-git",
                "id": "pull",
                "timeoutSec": 1200
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "compose-up-git",
                "id": "up",
                "timeoutSec": 1800
              },
              {
                "args": [
                  "{param.name}",
                  "{param.file}"
                ],
                "command": "stack-mark",
                "continueOnError": true,
                "id": "mark",
                "if": {
                  "step": "up",
                  "when": "ok"
                }
              }
            ],
            "timeoutSec": 7200
          },
          {
            "description": "Pull the images of a stack and redeploy it (webhook).",
            "name": "stack-redeploy",
            "params": [
              {
                "description": "Stack name",
                "maxLen": 63,
                "name": "name",
                "pattern": "[a-z0-9][a-z0-9_-]{0,62}"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.name}",
                  ""
                ],
                "command": "compose-pull",
                "id": "pull",
                "timeoutSec": 1200
              },
              {
                "args": [
                  "{param.name}",
                  ""
                ],
                "command": "compose-up",
                "id": "up",
                "timeoutSec": 1800
              }
            ],
            "timeoutSec": 7200
          },
          {
            "description": "Pull the image of a container and recreate it with the same settings (webhook).",
            "name": "container-redeploy",
            "params": [
              {
                "description": "Container name",
                "maxLen": 128,
                "name": "name",
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "default": "nickfedor/watchtower:latest",
                "description": "Watchtower image",
                "maxLen": 256,
                "name": "image",
                "pattern": "[a-z0-9][a-z0-9._/-]{0,127}(:[A-Za-z0-9._-]{1,128})?"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.name}",
                  "{param.image}"
                ],
                "command": "container-redeploy",
                "id": "run"
              }
            ],
            "timeoutSec": 1800
          },
          {
            "description": "Write a tar of a volume to the backup folder (quiet).",
            "name": "volume-backup-n",
            "params": [
              {
                "description": "Volume name",
                "maxLen": 128,
                "name": "volume",
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "default": "7",
                "description": "Backups to keep",
                "maxLen": 3,
                "name": "keep",
                "pattern": "[1-9][0-9]{0,2}"
              }
            ],
            "steps": [
              {
                "args": [],
                "command": "volume-image",
                "id": "image"
              },
              {
                "args": [
                  "{param.volume}"
                ],
                "command": "volume-check",
                "id": "check"
              },
              {
                "args": [
                  "{param.volume}",
                  "{param.keep}"
                ],
                "command": "volume-backup",
                "id": "run",
                "timeoutSec": 21600
              }
            ],
            "timeoutSec": 21600
          },
          {
            "description": "Write a tar of a volume to the backup folder (notify on success).",
            "name": "volume-backup-s",
            "params": [
              {
                "description": "Volume name",
                "maxLen": 128,
                "name": "volume",
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "default": "7",
                "description": "Backups to keep",
                "maxLen": 3,
                "name": "keep",
                "pattern": "[1-9][0-9]{0,2}"
              }
            ],
            "steps": [
              {
                "args": [],
                "command": "volume-image",
                "id": "image"
              },
              {
                "args": [
                  "{param.volume}"
                ],
                "command": "volume-check",
                "id": "check"
              },
              {
                "args": [
                  "{param.volume}",
                  "{param.keep}"
                ],
                "command": "volume-backup",
                "id": "run",
                "timeoutSec": 21600
              },
              {
                "id": "said",
                "if": {
                  "step": "run",
                  "when": "ok"
                },
                "notify": {
                  "level": "success",
                  "link": "/p/docker",
                  "title": "Backed up volume {param.volume}"
                }
              }
            ],
            "timeoutSec": 21600
          },
          {
            "description": "Write a tar of a volume to the backup folder (notify on failure).",
            "name": "volume-backup-f",
            "params": [
              {
                "description": "Volume name",
                "maxLen": 128,
                "name": "volume",
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "default": "7",
                "description": "Backups to keep",
                "maxLen": 3,
                "name": "keep",
                "pattern": "[1-9][0-9]{0,2}"
              }
            ],
            "steps": [
              {
                "args": [],
                "command": "volume-image",
                "continueOnError": true,
                "id": "image"
              },
              {
                "args": [
                  "{param.volume}"
                ],
                "command": "volume-check",
                "continueOnError": true,
                "id": "check",
                "if": {
                  "step": "image",
                  "when": "ok"
                }
              },
              {
                "args": [
                  "{param.volume}",
                  "{param.keep}"
                ],
                "command": "volume-backup",
                "continueOnError": true,
                "id": "run",
                "if": {
                  "step": "check",
                  "when": "ok"
                },
                "timeoutSec": 21600
              },
              {
                "id": "fail_image",
                "if": {
                  "step": "image",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.image.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Backup of volume {param.volume} failed"
                }
              },
              {
                "id": "fail_check",
                "if": {
                  "step": "check",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.check.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Backup of volume {param.volume} failed"
                }
              },
              {
                "id": "fail",
                "if": {
                  "step": "run",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.run.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Backup of volume {param.volume} failed"
                }
              }
            ],
            "timeoutSec": 21600
          },
          {
            "description": "Write a tar of a volume to the backup folder (notify on success and failure).",
            "name": "volume-backup-b",
            "params": [
              {
                "description": "Volume name",
                "maxLen": 128,
                "name": "volume",
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}"
              },
              {
                "default": "7",
                "description": "Backups to keep",
                "maxLen": 3,
                "name": "keep",
                "pattern": "[1-9][0-9]{0,2}"
              }
            ],
            "steps": [
              {
                "args": [],
                "command": "volume-image",
                "continueOnError": true,
                "id": "image"
              },
              {
                "args": [
                  "{param.volume}"
                ],
                "command": "volume-check",
                "continueOnError": true,
                "id": "check",
                "if": {
                  "step": "image",
                  "when": "ok"
                }
              },
              {
                "args": [
                  "{param.volume}",
                  "{param.keep}"
                ],
                "command": "volume-backup",
                "continueOnError": true,
                "id": "run",
                "if": {
                  "step": "check",
                  "when": "ok"
                },
                "timeoutSec": 21600
              },
              {
                "id": "said",
                "if": {
                  "step": "run",
                  "when": "ok"
                },
                "notify": {
                  "level": "success",
                  "link": "/p/docker",
                  "title": "Backed up volume {param.volume}"
                }
              },
              {
                "id": "fail_image",
                "if": {
                  "step": "image",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.image.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Backup of volume {param.volume} failed"
                }
              },
              {
                "id": "fail_check",
                "if": {
                  "step": "check",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.check.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Backup of volume {param.volume} failed"
                }
              },
              {
                "id": "fail",
                "if": {
                  "step": "run",
                  "when": "failed"
                },
                "notify": {
                  "body": "{step.run.stderr}",
                  "level": "error",
                  "link": "/p/docker",
                  "title": "Backup of volume {param.volume} failed"
                }
              }
            ],
            "timeoutSec": 21600
          }
        ],
        "network": [
          "raw.githubusercontent.com",
          "gist.githubusercontent.com"
        ],
        "notify": true,
        "sockets": [
          "/var/run/docker.sock"
        ]
      },
      "contributes": {
        "pages": [
          {
            "icon": "server",
            "id": "docker",
            "title": "Docker"
          }
        ],
        "snippets": [
          {
            "command": "docker ps",
            "name": "docker ps"
          }
        ],
        "widgets": [
          {
            "icon": "server",
            "id": "containers",
            "title": "Containers"
          }
        ]
      },
      "visibleTo": {
        "groups": [
          "docker",
          "wheel",
          "sudo"
        ]
      },
      "repo": "Ervisio/plugin-docker",
      "trust": "team"
    },
    {
      "id": "database",
      "name": "Databases",
      "version": "1.1.0",
      "author": "Ervisio Community",
      "description": "HeidiSQL-style database manager for MySQL, MariaDB, PostgreSQL and SQLite: schema tree, editable data grid with live search and filters, SQL editor with autocomplete, users and slow query log.",
      "icon": "database",
      "logo": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZjk3MzE2IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+CiAgPGVsbGlwc2UgY3g9IjEyIiBjeT0iNSIgcng9IjgiIHJ5PSIzIiBmaWxsPSIjZjk3MzE2IiBmaWxsLW9wYWNpdHk9IjAuMjUiPjwvZWxsaXBzZT4KICA8cGF0aCBkPSJNMjAgMTJjMCAxLjY2LTMuNTggMy04IDNzLTgtMS4zNC04LTMiPjwvcGF0aD4KICA8cGF0aCBkPSJNNCA1djE0YzAgMS42NiAzLjU4IDMgOCAzczgtMS4zNCA4LTNWNSI+PC9wYXRoPgo8L3N2Zz4K",
      "color": "",
      "category": "Databases",
      "verified": true,
      "installs": 0,
      "featured": true,
      "notes": "HeidiSQL-grade database workbench for MySQL, MariaDB, PostgreSQL, and SQLite. Zero-terminal deployment: 1-click Docker container deployment (MySQL 8.4/8.0/9.0, MariaDB 11.4/10.11, PostgreSQL 17/16). Zero-terminal native APT installation for `mariadb-server`, `mysql-server`, `postgresql`, and `sqlite3`. Standalone SQLite database creator and auto-discovery across filesystem. Interactive schema tree with tables, views, and engine metrics. Editable data grid with inline cell editing, pagination,...",
      "source": "https://github.com/Ervisio/plugins/releases/download/database-1.1.0/database-1.1.0.tar.gz",
      "sha256": "9007084c7435af7ff7497d3996e69d807ff718e2e41f0e1ea86953aeed4ef7ab",
      "capabilities": {
        "commands": [
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "args": [
              {
                "maxLen": 64,
                "pattern": "[a-zA-Z0-9_-]+"
              },
              {
                "allowDash": true,
                "maxLen": 4096,
                "pattern": ".*"
              }
            ],
            "argv": [
              "python3",
              "/var/lib/ervisio/plugins/database/db-bridge.py",
              "{0}",
              "{1}"
            ],
            "description": "Ervisio Database Engine Bridge",
            "name": "db-bridge",
            "timeoutSec": 120
          }
        ],
        "files": {
          "write": [
            {
              "create": true,
              "path": "~/.config/ervisio/plugins/database"
            }
          ]
        }
      },
      "contributes": {
        "pages": [
          {
            "icon": "database",
            "id": "database",
            "title": "Databases"
          }
        ],
        "snippets": [
          {
            "command": "docker ps --filter 'name=db-'",
            "name": "Check Database Containers"
          }
        ]
      },
      "visibleTo": {
        "groups": [
          "wheel",
          "sudo",
          "docker"
        ]
      },
      "homepage": "https://github.com/Ervisio",
      "repo": "Ervisio/plugin-database",
      "trust": "team"
    },
    {
      "id": "caddy",
      "name": "Caddy",
      "version": "1.0.1",
      "author": "Ervisio team",
      "description": "Manage the Caddy web server, installed on the system or in a Docker container: sites, reverse proxies, the Caddyfile with history and checks, certificates and access logs.",
      "icon": "globe",
      "logo": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzFGODhDMCIgZD0iTTExLjA5NC40N2MtLjg0MiAwLTEuNjk2LjA5Mi0yLjU1Mi4yODhhMTEuMzcgMTEuMzcgMCAwIDAtNC44NyAyLjQyMyAxMC42MzIgMTAuNjMyIDAgMCAwLTIuMzYgMi44MjZBMTAuMTMyIDEwLjEzMiAwIDAgMCAuMzA1IDguNTgyYy0uMzk4IDEuNjItLjQgMy4zMzYtLjA0MyA1LjA0OC4wODUuNDA1LjE4My44MDkuMzEgMS4yMTJhMTEuODUgMTEuODUgMCAwIDAgMS42NjIgMy43MjkgMy4yNzMgMy4yNzMgMCAwIDAtLjA4Ni40MjcgMy4zMjMgMy4zMjMgMCAwIDAgMi44NDggMy43MSAzLjI3OSAzLjI3OSAwIDAgMCAxLjk0Ny0uMzQ2YzEuMDQ1LjUxIDIuMTcuODY0IDMuMzM5IDEuMDRhMTEuNjYgMTEuNjYgMCAwIDAgNC4yODUtLjE1NSAxMS41NjYgMTEuNTY2IDAgMCAwIDQuOTM2LTIuNDg1IDEwLjY0MyAxMC42NDMgMCAwIDAgMi4zNTItMi44OTQgMTEuMTY0IDExLjE2NCAwIDAgMCAxLjM1Ni00LjQyNCAxMS4yMTQgMTEuMjE0IDAgMCAwLS40OTgtNC4zMzVjLjE3NS0uMDc3LjMzOC0uMTc1LjQ4Ni0uMjkzYS40NDQuNDQ0IDg5Ljk5MiAwIDAgLjAwMSAwYy40MDItLjMyMi42OTMtLjc5NC43NzctMS4zNDJhMi4xNDYgMi4xNDYgMCAwIDAtMS43OS0yLjQzNCAyLjExNSAyLjExNSAwIDAgMC0xLjIwNS4xNzFjLS4wMzgtLjA0My0uMDc4LS4wODYtLjExMy0uMTNhMTEuNjkzIDExLjY5MyAwIDAgMC0zLjQ3Ni0yLjkzIDEzLjM0OCAxMy4zNDggMCAwIDAtMS43Ni0uODEgMTMuNTUgMTMuNTUgMCAwIDAtMi4wNi0uNjEzQTEyLjEyMSAxMi4xMjEgMCAwIDAgMTEuMDkzLjQ3Wm0uNzE0LjMyOGMuMzQ1LS4wMDQuNjg4LjAxIDEuMDI4LjA0MmE5Ljg5MiA5Ljg5MiAwIDAgMSAyLjc0My42MzljLjk4NC4zOSAxLjg5Ljk1OCAyLjcwNyAxLjYzMi44MDMuNjYyIDEuNTAyIDEuNDUgMi4wOTEgMi4zMjguMDI2LjAzOS4wNDguMDguMDcuMTJhMi4xMiAyLjEyIDAgMCAwLS40MzUgMi42NDZjLS4xNTguMTE0LS45Ny42OTItMS42MzQgMS4xODMtLjQxNC4zMDgtLjczMy41NTctLjczMy41NTdsLjU4MS42OHMuMjk2LS4yNzYuNjY1LS42MzhjLjU3Mi0uNTYyIDEuMjI5LTEuMjMzIDEuMzk1LTEuNDAzYTIuMTIyIDIuMTIyIDAgMCAwIDEuOTA3LjY3NyAxMS4yMjkgMTEuMjI5IDAgMCAxLS4wMTMgNC4wNDYgMTEuNDEgMTEuNDEgMCAwIDEtMS40NzUgMy44OTcgMTIuMzQzIDEyLjM0MyAwIDAgMS0yLjA3OSAyLjU4N2MtMS4xOSAxLjEyNS0yLjYzMyAyLjAyMi00LjMwNiAyLjUzMWExMC44MjYgMTAuODI2IDAgMCAxLTMuOTczLjQ4NCAxMS4wNCAxMS4wNCAwIDAgMS0zLjA1Ny0uNjUyIDMuMzA0IDMuMzA0IDAgMCAwIDEuNDE3LTIuMjk0IDMuMjc1IDMuMjc1IDAgMCAwLS4yOTQtMS44NDJjLjE4LS4xNjIuNDAzLS4zNjMuNjU2LS42IDEuMDE1LS45NTUgMi4zNTMtMi4zMDMgMi4zNTMtMi4zMDNsLS40Ny0uNTk5cy0xLjYzLjk3Mi0yLjgwMSAxLjcyOGMtLjMwNy4xOTgtLjU3My4zNzgtLjc3Ny41MTdhMy4yNzMgMy4yNzMgMCAwIDAtMS41MTYtLjYxMWMtMS41MDctLjE5OC0yLjkyNy42NzItMy40ODcgMi4wMTdhMTAuMzIzIDEwLjMyMyAwIDAgMS0uNjk1LTEuMDc4QTEwLjkyIDEwLjkyIDAgMCAxIC43MjggMTQuOGExMC4zNSAxMC4zNSAwIDAgMS0uMi0xLjIxMmMtLjE2NC0xLjY1My4xMDMtMy4yNTguNjI5LTQuNzU0YTEyLjk1IDEyLjk1IDAgMCAxIDEuMDg3LTIuMjg4Yy41Ny0uOTY4IDEuMjQ4LTEuODcyIDIuMDY5LTIuNjU2QTExLjAxMyAxMS4wMTMgMCAwIDEgMTEuODA4Ljc5N1ptLS4xNDcgMy4yNTdhMy44MzggMy44MzggMCAwIDAtMy44MiAzLjgydjIuMzZoLS45NGMtLjc1MSAwLTEuMzc3LjYyNS0xLjM3NyAxLjM3N3YzLjhoMS40NnYtMy43MThoOS4zNTR2Ni4yNjRIMTAuMDJ2MS40Nmg2LjRjLjc1MSAwIDEuMzc3LS42MjUgMS4zNzctMS4zNzd2LTYuNDNjMC0uNzUxLS42MjYtMS4zNzctMS4zNzctMS4zNzdoLS45NHYtMi4zNmEzLjgzOCAzLjgzOCAwIDAgMC0zLjgyLTMuODE5em0wIDEuNDZhMi4zNzEgMi4zNzEgMCAwIDEgMi4zNiAyLjM2djIuMzZIOS4zdi0yLjM2YTIuMzcyIDIuMzcyIDAgMCAxIDIuMzYtMi4zNnptMTAuMTQxLjM5MmExLjI1MyAxLjI1MyAwIDAgMSAxLjI5NiAxLjQzNGMtLjA0OS4zMTktLjIxNy41OS0uNDUzLjc4LS4yNjYuMjEzLS42MS4zMTgtLjk2OC4yNjRhMS4yNTMgMS4yNTMgMCAwIDEtMS4wNDUtMS40MiAxLjI1NSAxLjI1NSAwIDAgMSAxLjE3LTEuMDU4ek01LjM4NCAxNy40MjVhMi4wMiAyLjAyIDAgMCAxIDEuOTE3IDEuMjk4Yy4xMTYuMy4xNTkuNjI4LjExNC45NjdhMi4wMTUgMi4wMTUgMCAwIDEtMi4yNDkgMS43MjggMi4wMTYgMi4wMTYgMCAwIDEtMS43MjctMi4yNSAyLjAxNyAyLjAxNyAwIDAgMSAxLjk0NS0xLjc0M3oiLz48L3N2Zz4=",
      "color": "term",
      "category": "Web servers",
      "verified": true,
      "installs": 0,
      "notes": "The plugin has a logo: the Caddy logo is shown instead of the generic icon in the rail, Plugins and Browse (consoles newer than 0.4.0; older ones keep the icon).",
      "source": "https://github.com/Ervisio/plugins/releases/download/caddy-1.0.1/caddy-1.0.1.tar.gz",
      "sha256": "198216515362f9abfc3daaec151dcbaccf3f20e135dc8868e43f90fb4a5d8d33",
      "capabilities": {
        "commands": [
          {
            "argv": [
              "systemctl",
              "show",
              "caddy.service",
              "--no-pager",
              "--property=LoadState,ActiveState,SubState,MainPID,ExecMainStartTimestamp,ExecStart,FragmentPath,UnitFileState"
            ],
            "description": "Read the state of caddy.service",
            "name": "service-show"
          },
          {
            "argv": [
              "caddy",
              "version"
            ],
            "description": "Read the version of Caddy",
            "name": "version"
          },
          {
            "argv": [
              "id",
              "-un"
            ],
            "description": "Read your user name (for the history of changes)",
            "name": "user"
          },
          {
            "argv": [
              "id",
              "-Gn"
            ],
            "description": "Read your groups (to know if you can use Docker)",
            "name": "groups"
          },
          {
            "argv": [
              "test",
              "-S",
              "/var/run/docker.sock"
            ],
            "description": "Check whether Docker is installed",
            "name": "has-docker"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 8,
                "pattern": "start|stop|restart|reload"
              }
            ],
            "argv": [
              "systemctl",
              "{0}",
              "caddy.service"
            ],
            "description": "Start, stop, restart or reload caddy.service",
            "name": "service",
            "timeoutSec": 120
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 220,
                "pattern": "/etc/caddy/[A-Za-z0-9._-]{1,100}(/[A-Za-z0-9._-]{1,100})?"
              }
            ],
            "argv": [
              "env",
              "HOME=/run/ervisio-caddy",
              "XDG_DATA_HOME=/run/ervisio-caddy/data",
              "XDG_CONFIG_HOME=/run/ervisio-caddy/config",
              "caddy",
              "validate",
              "--config",
              "{0}",
              "--adapter",
              "caddyfile"
            ],
            "description": "Check a Caddyfile with caddy validate (in a scratch data folder, so nothing in Caddy's storage changes)",
            "name": "validate",
            "timeoutSec": 60
          },
          {
            "args": [
              {
                "maxLen": 5,
                "pattern": "[0-9]{1,5}"
              }
            ],
            "argv": [
              "journalctl",
              "-u",
              "caddy.service",
              "-o",
              "cat",
              "--no-pager",
              "-n",
              "{0}"
            ],
            "description": "Read the last lines Caddy wrote to the system journal",
            "name": "journal"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 5,
                "pattern": "[0-9]{1,5}"
              }
            ],
            "argv": [
              "journalctl",
              "-u",
              "caddy.service",
              "-o",
              "cat",
              "--no-pager",
              "-n",
              "{0}"
            ],
            "description": "Read Caddy's journal with administrator rights (when your account cannot read the journal)",
            "name": "journal-admin"
          },
          {
            "argv": [
              "journalctl",
              "-u",
              "caddy.service",
              "-o",
              "cat",
              "--no-pager",
              "-f",
              "-n",
              "0"
            ],
            "description": "Follow Caddy's journal live",
            "name": "journal-follow",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "argv": [
              "journalctl",
              "-u",
              "caddy.service",
              "-o",
              "cat",
              "--no-pager",
              "-f",
              "-n",
              "0"
            ],
            "description": "Follow Caddy's journal live with administrator rights",
            "name": "journal-follow-admin",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 5,
                "pattern": "[0-9]{1,5}"
              },
              {
                "maxLen": 120,
                "pattern": "/var/log/caddy/[A-Za-z0-9._-]{1,100}"
              }
            ],
            "argv": [
              "tail",
              "-n",
              "{0}",
              "{1}"
            ],
            "description": "Read the end of an access log in /var/log/caddy",
            "name": "tail"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 120,
                "pattern": "/var/log/caddy/[A-Za-z0-9._-]{1,100}"
              }
            ],
            "argv": [
              "tail",
              "-F",
              "-n",
              "0",
              "{0}"
            ],
            "description": "Follow an access log in /var/log/caddy live",
            "name": "tail-follow",
            "timeoutSec": 600
          },
          {
            "args": [
              {
                "maxLen": 270,
                "pattern": "https?://[A-Za-z0-9._-]{1,253}(:[0-9]{1,5})?"
              }
            ],
            "argv": [
              "curl",
              "-s",
              "-k",
              "-o",
              "/dev/null",
              "-w",
              "%{http_code}",
              "--connect-timeout",
              "3",
              "--max-time",
              "4",
              "{0}"
            ],
            "description": "Check that a site's backend answers (one HTTP request, 4 seconds at most)",
            "name": "probe",
            "timeoutSec": 10
          },
          {
            "args": [
              {
                "maxLen": 253,
                "pattern": "[A-Za-z0-9][A-Za-z0-9.-]{0,252}"
              }
            ],
            "argv": [
              "curl",
              "-sv",
              "-k",
              "-o",
              "/dev/null",
              "--connect-timeout",
              "3",
              "--max-time",
              "5",
              "--resolve",
              "{0}:443:127.0.0.1",
              "https://{0}/"
            ],
            "description": "Read the certificate a site presents (connects to this machine on port 443)",
            "name": "tls-check",
            "timeoutSec": 10
          },
          {
            "argv": [
              "ip",
              "-j",
              "addr",
              "show"
            ],
            "description": "List this machine's IP addresses (to check where a domain points)",
            "name": "addresses"
          },
          {
            "argv": [
              "ss",
              "-ltnH"
            ],
            "description": "List the ports that programs listen on (suggestions for a reverse proxy)",
            "name": "ports"
          }
        ],
        "files": {
          "read": [
            "/etc/caddy",
            "/var/lib/ervisio-caddy",
            {
              "admin": true,
              "path": "/var/lib/caddy/.local/share/caddy"
            },
            {
              "admin": true,
              "path": "/var/log/caddy"
            }
          ],
          "write": [
            {
              "admin": true,
              "path": "/etc/caddy"
            },
            {
              "admin": true,
              "create": true,
              "path": "/var/lib/ervisio-caddy"
            },
            {
              "create": true,
              "path": "~/.config/ervisio/plugins/caddy"
            }
          ]
        },
        "http": [
          {
            "admin": true,
            "adminUnlessGroup": "docker",
            "headers": [
              "Content-Type"
            ],
            "maxBody": 8388608,
            "name": "docker",
            "rules": [
              {
                "methods": [
                  "GET",
                  "HEAD"
                ],
                "path": "/_ping"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/version"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/containers/json"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/containers/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}/(json|logs)"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/containers/[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}/(start|stop|restart|exec)"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1\\.[0-9]+/exec/[a-f0-9]{64}/start"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1\\.[0-9]+/exec/[a-f0-9]{64}/json"
              }
            ],
            "socket": "/var/run/docker.sock",
            "timeoutSec": 120
          }
        ],
        "network": [
          "cloudflare-dns.com"
        ],
        "sockets": [
          "/var/run/docker.sock"
        ]
      },
      "contributes": {
        "pages": [
          {
            "icon": "globe",
            "id": "caddy",
            "title": "Caddy"
          }
        ],
        "snippets": [
          {
            "command": "sudo systemctl reload caddy",
            "name": "Reload Caddy"
          },
          {
            "command": "caddy validate --config /etc/caddy/Caddyfile",
            "name": "Check the Caddyfile"
          }
        ],
        "widgets": [
          {
            "icon": "globe",
            "id": "sites",
            "title": "Caddy"
          }
        ]
      },
      "visibleTo": {
        "groups": [
          "wheel",
          "sudo",
          "docker"
        ]
      },
      "homepage": "https://github.com/Ervisio/plugin-caddy",
      "repo": "Ervisio/plugin-caddy",
      "trust": "team"
    },
    {
      "id": "minecraft",
      "name": "Minecraft",
      "version": "0.1.1",
      "author": "Ervisio team",
      "description": "Manage multiple Java Minecraft servers, console, files, software, add-ons, players, worlds and backups.",
      "icon": "server",
      "logo": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NDYgNjUxIj48cGF0aCBkPSJtMS41OCAxNTkgMjcyIDgyLjZ2NDA4bC0yNzItMTIweiIgZmlsbD0iIzViNDIyZCIvPjxwYXRoIGQ9Ik0xLjUgMTk4bDE3IDcuNVYxODBsMTcgNy41VjIxM2wzNCAxNS0uMDA3IDI1LjUgMTcgNy41LS4wMDctNzYuNSAxNyA3LjQ4LS4wMyA1MSAxNyA3LjUuMDAzLTI1LjUgMTcgNy41LjAxMiA1MSAxNyA3LjUuMDctMjUuNSAxNi45IDcuNDh2MjUuNWwxNyA3LjV2LTI1LjVsMTcgNy41Mi0uMDEtMjUuNSAxNyA3LjUtLjAwOCAyNS41IDM0IDE1LS4wMS0yNS41IDE3IDcuNXYtNTFsLTI3Mi0xMjB6IiBmaWxsPSIjNjRhNDNhIiBzdHJva2U9IiM2NGE0M2EiLz48cGF0aCBkPSJNMS43IDEyMWwyNzIgMTIwIDI3MS0xMjAtMjcyLTEyMHoiIGZpbGw9IiM1YTlhMzAiIHN0cm9rZT0iIzVhOWEzMCIvPjxwYXRoIGQ9Im01NDUgMTcyLTI3MiA2OS4ydjQwOGwyNzItMTIweiIgZmlsbD0iIzRmMzYyNCIgc3Ryb2tlPSIjNGYzNjI0Ii8+PHBhdGggZD0iTTU0NSAxMjJsLjE1NCA1MC45LTE3IDcuNS4wNzMgMjUuNS0zNCAxNS0uMDczLTI1LjUtMTYuOSA3LjQ3LjAwMyAyNS41LTE3IDcuNS0uMDAzIDI1LjUtMTcgNy41LS4wMDMtMjUuNS0xNyA3LjUtLjIgMjUuNi0xNi44IDcuNDIuMDAzLTUxLTE3IDcuNTQuMDA4IDI1LjUtMTYuMyA3LjItLjY1NS01MC43LTE3IDcuNS0uMDA3IDc2LjUtMTcgNy41di0yNS41bC0zNCAxNXYtMjUuNWwtMTcgNy41djI1LjVsLTE3IDcuNS0uMDA0LTc2LjV6IiBmaWxsPSIjM2I2MTIxIiBzdHJva2U9IiMzYjYxMjEiLz48L3N2Zz4=",
      "color": "file",
      "category": "Games",
      "verified": true,
      "installs": 0,
      "notes": "The logo is now the Minecraft grass block, the game's own icon (vector version from Wikimedia Commons, public domain; Minecraft is a trademark of Mojang).",
      "source": "https://github.com/Ervisio/plugins/releases/download/minecraft-0.1.1/minecraft-0.1.1.tar.gz",
      "sha256": "5c951963ea543d7bcd6cd390b57e38432141340b0e9153f80439ccb4ab2d02e0",
      "capabilities": {
        "http": [
          {
            "admin": true,
            "adminUnlessGroup": "ervisio-minecraft",
            "headers": [
              "Content-Type"
            ],
            "maxBody": 1048576,
            "name": "minecraft",
            "rules": [
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1/(health|servers|jobs|activity|catalog/(versions|loaders|addons))"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1/servers"
              },
              {
                "methods": [
                  "GET",
                  "PATCH",
                  "DELETE"
                ],
                "path": "/v1/servers/[a-z0-9-]+"
              },
              {
                "methods": [
                  "GET"
                ],
                "path": "/v1/servers/[a-z0-9-]+/(logs|files|file|properties|addons|players|worlds|backups|backup-file|schedules)"
              },
              {
                "methods": [
                  "POST"
                ],
                "path": "/v1/servers/[a-z0-9-]+/(power|command|files|software|addons|players|worlds|backups)"
              },
              {
                "methods": [
                  "PUT"
                ],
                "path": "/v1/servers/[a-z0-9-]+/(file|properties|schedules)"
              },
              {
                "methods": [
                  "PATCH"
                ],
                "path": "/v1/servers/[a-z0-9-]+/file"
              }
            ],
            "socket": "/run/ervisio-minecraft/control.sock",
            "timeoutSec": 60
          }
        ],
        "sockets": [
          "/run/ervisio-minecraft/control.sock"
        ]
      },
      "contributes": {
        "pages": [
          {
            "icon": "server",
            "id": "minecraft",
            "title": "Minecraft"
          }
        ],
        "widgets": [
          {
            "icon": "server",
            "id": "servers",
            "title": "Minecraft servers"
          }
        ]
      },
      "visibleTo": {
        "groups": [
          "ervisio-minecraft",
          "wheel",
          "sudo",
          "admin"
        ]
      },
      "homepage": "https://github.com/Ervisio/plugin-minecraft",
      "repo": "Ervisio/plugin-minecraft",
      "trust": "team"
    },
    {
      "id": "firewall",
      "name": "Firewall",
      "version": "0.1.0",
      "author": "Ervisio team",
      "description": "Manage ufw, firewalld, nftables and iptables from Ervisio: rules, zones, default policies, firewall logs and fail2ban bans, with a guard that keeps SSH open.",
      "icon": "shield",
      "logo": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHBhdGggZD0iTTMyIDQgNTQgMTJ2MThjMCAxNS05LjUgMjUuNS0yMiAzMEMxOS41IDU1LjUgMTAgNDUgMTAgMzBWMTJ6IiBmaWxsPSIjMjJCOEJFIi8+PHBhdGggZD0iTTMyIDkuNSA0OSAxNS43VjMwYzAgMTEuOC03LjIgMjAuNC0xNyAyNC40QzIyLjIgNTAuNCAxNSA0MS44IDE1IDMwVjE1Ljd6IiBmaWxsPSIjMEI1RTYyIi8+PGcgZmlsbD0iIzdGRTZFQSI+PHJlY3QgeD0iMTgiIHk9IjIwIiB3aWR0aD0iMTIiIGhlaWdodD0iNiIgcng9IjEuNSIvPjxyZWN0IHg9IjM0IiB5PSIyMCIgd2lkdGg9IjEyIiBoZWlnaHQ9IjYiIHJ4PSIxLjUiLz48cmVjdCB4PSIxOCIgeT0iMjkiIHdpZHRoPSI1IiBoZWlnaHQ9IjYiIHJ4PSIxLjUiLz48cmVjdCB4PSIyNiIgeT0iMjkiIHdpZHRoPSIxMiIgaGVpZ2h0PSI2IiByeD0iMS41Ii8+PHJlY3QgeD0iNDEiIHk9IjI5IiB3aWR0aD0iNSIgaGVpZ2h0PSI2IiByeD0iMS41Ii8+PHJlY3QgeD0iMjAiIHk9IjM4IiB3aWR0aD0iMTAiIGhlaWdodD0iNiIgcng9IjEuNSIvPjxyZWN0IHg9IjM0IiB5PSIzOCIgd2lkdGg9IjEwIiBoZWlnaHQ9IjYiIHJ4PSIxLjUiLz48L2c+PC9zdmc+Cg==",
      "color": "usr",
      "category": "Security",
      "verified": true,
      "installs": 0,
      "notes": "First release. ufw, firewalld, nftables and iptables: rules, default policies, zones, chains, saving across reboots. Firewall logs from the kernel journal or a syslog file, with live view, counts and \"Block\" on any address. fail2ban jails: ban and unban. SSH guard, typed confirmation, command preview. Background check with notifications (job `watchdog`). Dashboard widget. English and Italian.",
      "minCore": "0.5.0",
      "source": "https://github.com/Ervisio/plugins/releases/download/firewall-0.1.0/firewall-0.1.0.tar.gz",
      "sha256": "9c2bd0ed699fda1f06437050e54947961a0512b657fa33b4f5290b18cb18555b",
      "capabilities": {
        "commands": [
          {
            "argv": [
              "sh",
              "-c",
              "for c in ufw firewall-cmd nft iptables ip6tables fail2ban-client ss; do p=$(command -v \"$c\" 2>/dev/null) || p=; echo \"bin|$c|$p\"; done\niptables --version 2>/dev/null | head -1 | sed \"s/^/ver|iptables|/\"\nnft --version 2>/dev/null | head -1 | sed \"s/^/ver|nft|/\"\nfirewall-cmd --version 2>/dev/null | head -1 | sed \"s/^/ver|firewalld|/\"\nfail2ban-client --version 2>/dev/null | head -1 | sed \"s/^/ver|fail2ban|/\"\nfor u in ufw firewalld nftables fail2ban netfilter-persistent iptables; do echo \"unit|$u|$(systemctl is-active \"$u\" 2>/dev/null)|$(systemctl is-enabled \"$u\" 2>/dev/null)\"; done\n[ -r /etc/ufw/ufw.conf ] && grep -E \"^ENABLED=\" /etc/ufw/ufw.conf | sed \"s/^/ufwconf|/\"\necho \"tz|$(date +%z)\"\nexit 0"
            ],
            "description": "Find which firewalls are installed and running",
            "name": "detect"
          },
          {
            "admin": true,
            "argv": [
              "ss",
              "-Htulnp"
            ],
            "description": "List the ports that programs listen on (ss)",
            "name": "listening"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "ufw|firewalld|nftables|iptables|fail2ban"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "case \"$1\" in\n  ufw) LC_ALL=C ufw status | head -1 | grep -q 'Status: active' && s=active || s=inactive;;\n  firewalld) firewall-cmd --state >/dev/null 2>&1 && s=active || s=inactive;;\n  nftables) nft list ruleset 2>/dev/null | grep -q 'hook input' && s=active || s=inactive;;\n  iptables) [ \"$(iptables -S INPUT 2>/dev/null | wc -l)\" -gt 1 ] || iptables -S INPUT 2>/dev/null | grep -q '^-P INPUT DROP' && s=active || s=inactive;;\n  fail2ban) fail2ban-client ping >/dev/null 2>&1 && s=active || s=inactive;;\nesac\necho \"$s\"",
              "sh",
              "{0}"
            ],
            "description": "Tell whether a firewall is active (used by the watchdog job)",
            "name": "state"
          },
          {
            "admin": true,
            "argv": [
              "sh",
              "-c",
              "export LC_ALL=C\necho @@VERSION; ufw version 2>/dev/null | head -1\necho @@VERBOSE; ufw status verbose\necho @@NUMBERED; ufw status numbered\necho @@ADDED; ufw show added\necho @@CONF; grep -hE \"^(ENABLED|LOGLEVEL|IPV6|DEFAULT_(INPUT|OUTPUT|FORWARD)_POLICY)=\" /etc/ufw/ufw.conf /etc/default/ufw 2>/dev/null\nexit 0"
            ],
            "description": "Read ufw status, rules and settings",
            "name": "ufw-status"
          },
          {
            "admin": true,
            "argv": [
              "ufw",
              "--force",
              "enable"
            ],
            "description": "Turn ufw on",
            "name": "ufw-enable"
          },
          {
            "admin": true,
            "argv": [
              "ufw",
              "disable"
            ],
            "description": "Turn ufw off",
            "name": "ufw-disable"
          },
          {
            "admin": true,
            "argv": [
              "ufw",
              "reload"
            ],
            "description": "Reload ufw",
            "name": "ufw-reload"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject"
              },
              {
                "maxLen": 8,
                "pattern": "incoming|outgoing|routed"
              }
            ],
            "argv": [
              "ufw",
              "default",
              "{0}",
              "{1}"
            ],
            "description": "Set a default ufw policy",
            "name": "ufw-default"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "off|on|low|medium|high|full"
              }
            ],
            "argv": [
              "ufw",
              "logging",
              "{0}"
            ],
            "description": "Set the ufw log level",
            "name": "ufw-logging"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 4,
                "pattern": "[1-9][0-9]{0,3}"
              }
            ],
            "argv": [
              "ufw",
              "--force",
              "delete",
              "{0}"
            ],
            "description": "Delete a ufw rule by number",
            "name": "ufw-delete"
          },
          {
            "admin": true,
            "argv": [
              "ufw",
              "app",
              "list"
            ],
            "description": "List ufw application profiles",
            "name": "ufw-apps"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 3,
                "pattern": "tcp|udp"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 120,
                "pattern": "[0-9]{1,5}(:[0-9]{1,5})?(,[0-9]{1,5}(:[0-9]{1,5})?){0,14}"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "{0}",
              "{1}",
              "proto",
              "{2}",
              "from",
              "{3}",
              "to",
              "{4}",
              "port",
              "{5}",
              "comment",
              "{6}"
            ],
            "description": "Add a ufw rule for a port and protocol",
            "name": "ufw-port"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 120,
                "pattern": "[0-9]{1,5}(:[0-9]{1,5})?(,[0-9]{1,5}(:[0-9]{1,5})?){0,14}"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "{0}",
              "{1}",
              "from",
              "{2}",
              "to",
              "{3}",
              "port",
              "{4}",
              "comment",
              "{5}"
            ],
            "description": "Add a ufw rule for a port, TCP and UDP",
            "name": "ufw-port-any"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "{0}",
              "{1}",
              "from",
              "{2}",
              "to",
              "{3}",
              "comment",
              "{4}"
            ],
            "description": "Add a ufw rule for addresses only",
            "name": "ufw-host"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z0-9][A-Za-z0-9 ._()+-]{0,63}"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "{0}",
              "{1}",
              "from",
              "{2}",
              "to",
              "{3}",
              "app",
              "{4}",
              "comment",
              "{5}"
            ],
            "description": "Add a ufw rule for an application profile",
            "name": "ufw-app"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 15,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.@-]{0,14}"
              },
              {
                "maxLen": 3,
                "pattern": "tcp|udp"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 120,
                "pattern": "[0-9]{1,5}(:[0-9]{1,5})?(,[0-9]{1,5}(:[0-9]{1,5})?){0,14}"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "{0}",
              "{1}",
              "on",
              "{2}",
              "proto",
              "{3}",
              "from",
              "{4}",
              "to",
              "{5}",
              "port",
              "{6}",
              "comment",
              "{7}"
            ],
            "description": "Add a ufw rule for a port on one interface",
            "name": "ufw-port-if"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 15,
                "pattern": "[a-zA-Z0-9][a-zA-Z0-9_.@-]{0,14}"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "{0}",
              "{1}",
              "on",
              "{2}",
              "from",
              "{3}",
              "to",
              "{4}",
              "comment",
              "{5}"
            ],
            "description": "Add a ufw rule for addresses on one interface",
            "name": "ufw-host-if"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "prepend",
              "{0}",
              "{1}",
              "from",
              "{2}",
              "to",
              "{3}",
              "comment",
              "{4}"
            ],
            "description": "Add a ufw rule for addresses before all others",
            "name": "ufw-first-host"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "allow|deny|reject|limit"
              },
              {
                "maxLen": 3,
                "pattern": "in|out"
              },
              {
                "maxLen": 3,
                "pattern": "tcp|udp"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "any|[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 120,
                "pattern": "[0-9]{1,5}(:[0-9]{1,5})?(,[0-9]{1,5}(:[0-9]{1,5})?){0,14}"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              }
            ],
            "argv": [
              "ufw",
              "prepend",
              "{0}",
              "{1}",
              "proto",
              "{2}",
              "from",
              "{3}",
              "to",
              "{4}",
              "port",
              "{5}",
              "comment",
              "{6}"
            ],
            "description": "Add a ufw rule for a port before all others",
            "name": "ufw-first-port"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "runtime|permanent"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "P=; [ \"$1\" = permanent ] && P=--permanent\necho @@STATE; firewall-cmd --state 2>&1\necho @@VERSION; firewall-cmd --version 2>&1\necho @@DEFAULT; firewall-cmd --get-default-zone 2>&1\necho @@ACTIVE; firewall-cmd --get-active-zones 2>&1\necho @@LOGDENIED; firewall-cmd --get-log-denied 2>&1\necho @@SERVICES; firewall-cmd $P --get-services 2>&1\necho @@ZONES; firewall-cmd $P --list-all-zones 2>&1\nexit 0",
              "sh",
              "{0}"
            ],
            "description": "Read firewalld state, zones and services",
            "name": "fwd-info"
          },
          {
            "admin": true,
            "args": [
              {
                "allowDash": true,
                "maxLen": 11,
                "pattern": "--permanent|--quiet"
              },
              {
                "maxLen": 32,
                "pattern": "[A-Za-z0-9][A-Za-z0-9_-]{0,31}"
              },
              {
                "allowDash": true,
                "maxLen": 96,
                "pattern": "--(add|remove)-(service=[a-zA-Z0-9][a-zA-Z0-9_.+-]{0,63}|port=[0-9]{1,5}(-[0-9]{1,5})?/(tcp|udp|sctp|dccp)|source=[0-9a-fA-F.:/]{2,49}|interface=[a-zA-Z0-9][a-zA-Z0-9_.@-]{0,14}|masquerade)"
              }
            ],
            "argv": [
              "firewall-cmd",
              "{0}",
              "--zone={1}",
              "{2}"
            ],
            "description": "Add or remove a service, port, source, interface or masquerading in a firewalld zone",
            "name": "fwd-change"
          },
          {
            "admin": true,
            "args": [
              {
                "allowDash": true,
                "maxLen": 11,
                "pattern": "--permanent|--quiet"
              },
              {
                "maxLen": 32,
                "pattern": "[A-Za-z0-9][A-Za-z0-9_-]{0,31}"
              },
              {
                "allowDash": true,
                "maxLen": 520,
                "pattern": "--(add|remove)-rich-rule=rule [A-Za-z0-9 =\"./:_,-]{1,480}"
              }
            ],
            "argv": [
              "firewall-cmd",
              "{0}",
              "--zone={1}",
              "{2}"
            ],
            "description": "Add or remove a firewalld rich rule",
            "name": "fwd-rich"
          },
          {
            "admin": true,
            "args": [
              {
                "allowDash": true,
                "maxLen": 11,
                "pattern": "--permanent|--quiet"
              },
              {
                "maxLen": 32,
                "pattern": "[A-Za-z0-9][A-Za-z0-9_-]{0,31}"
              },
              {
                "allowDash": true,
                "maxLen": 120,
                "pattern": "--(add|remove)-forward-port=port=[0-9]{1,5}(-[0-9]{1,5})?:proto=(tcp|udp|sctp|dccp)(:toport=[0-9]{1,5}(-[0-9]{1,5})?)?(:toaddr=[0-9a-fA-F.:]{2,39})?"
              }
            ],
            "argv": [
              "firewall-cmd",
              "{0}",
              "--zone={1}",
              "{2}"
            ],
            "description": "Add or remove a firewalld port forward",
            "name": "fwd-forward"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 32,
                "pattern": "[A-Za-z0-9][A-Za-z0-9_-]{0,31}"
              }
            ],
            "argv": [
              "firewall-cmd",
              "--set-default-zone={0}"
            ],
            "description": "Set the firewalld default zone",
            "name": "fwd-default-zone"
          },
          {
            "admin": true,
            "argv": [
              "firewall-cmd",
              "--reload"
            ],
            "description": "Reload firewalld",
            "name": "fwd-reload"
          },
          {
            "admin": true,
            "argv": [
              "firewall-cmd",
              "--runtime-to-permanent"
            ],
            "description": "Save the firewalld runtime configuration as permanent",
            "name": "fwd-persist"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "all|unicast|broadcast|multicast|off"
              }
            ],
            "argv": [
              "firewall-cmd",
              "--set-log-denied={0}"
            ],
            "description": "Set which denied packets firewalld logs",
            "name": "fwd-log-denied"
          },
          {
            "admin": true,
            "argv": [
              "nft",
              "-a",
              "list",
              "ruleset"
            ],
            "description": "Read the nftables ruleset with rule handles",
            "name": "nft-list"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "add|insert"
              },
              {
                "maxLen": 6,
                "pattern": "ip|ip6|inet|arp|bridge|netdev"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 400,
                "pattern": "[A-Za-z0-9][A-Za-z0-9 .:/,{}_\"@!=<>*+-]{0,399}"
              }
            ],
            "argv": [
              "nft",
              "{0}",
              "rule",
              "{1}",
              "{2}",
              "{3}",
              "{4}"
            ],
            "description": "Add or insert an nftables rule",
            "name": "nft-rule"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "ip|ip6|inet|arp|bridge|netdev"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 9,
                "pattern": "[0-9]{1,9}"
              }
            ],
            "argv": [
              "nft",
              "delete",
              "rule",
              "{0}",
              "{1}",
              "{2}",
              "handle",
              "{3}"
            ],
            "description": "Delete an nftables rule by handle",
            "name": "nft-delete-rule"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "add|flush|delete"
              },
              {
                "maxLen": 6,
                "pattern": "ip|ip6|inet|arp|bridge|netdev"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              }
            ],
            "argv": [
              "nft",
              "{0}",
              "table",
              "{1}",
              "{2}"
            ],
            "description": "Add, flush or delete an nftables table",
            "name": "nft-table"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "add|flush|delete"
              },
              {
                "maxLen": 6,
                "pattern": "ip|ip6|inet|arp|bridge|netdev"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              }
            ],
            "argv": [
              "nft",
              "{0}",
              "chain",
              "{1}",
              "{2}",
              "{3}"
            ],
            "description": "Add, flush or delete a regular nftables chain",
            "name": "nft-chain"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "ip|ip6|inet|arp|bridge|netdev"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 6,
                "pattern": "filter|nat|route"
              },
              {
                "maxLen": 11,
                "pattern": "prerouting|input|forward|output|postrouting|ingress"
              },
              {
                "allowDash": true,
                "maxLen": 8,
                "pattern": "-?[0-9]{1,4}|raw|mangle|dstnat|filter|security|srcnat"
              },
              {
                "maxLen": 6,
                "pattern": "accept|drop"
              }
            ],
            "argv": [
              "nft",
              "add",
              "chain",
              "{0}",
              "{1}",
              "{2}",
              "{",
              "type",
              "{3}",
              "hook",
              "{4}",
              "priority",
              "{5}",
              ";",
              "policy",
              "{6}",
              ";",
              "}"
            ],
            "description": "Add an nftables base chain attached to a hook",
            "name": "nft-base-chain"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 6,
                "pattern": "ip|ip6|inet|arp|bridge|netdev"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 64,
                "pattern": "[A-Za-z_][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 6,
                "pattern": "accept|drop"
              }
            ],
            "argv": [
              "nft",
              "chain",
              "{0}",
              "{1}",
              "{2}",
              "{",
              "policy",
              "{3}",
              ";",
              "}"
            ],
            "description": "Set the policy of an nftables base chain",
            "name": "nft-policy"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 32,
                "pattern": "/etc/nftables\\.conf|/etc/sysconfig/nftables\\.conf"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "f=\"$1\"; t=\"$f.ervisio-tmp\"\n{ echo '#!/usr/sbin/nft -f'; echo 'flush ruleset'; echo; nft list ruleset; } > \"$t\" || { rm -f \"$t\"; exit 1; }\nchmod 0644 \"$t\"\n[ -e \"$f\" ] && cp -p \"$f\" \"$f.bak\"\nmv \"$t\" \"$f\" && echo \"$f\"",
              "sh",
              "{0}"
            ],
            "description": "Save the nftables ruleset to the file loaded at boot (keeps a .bak copy)",
            "name": "nft-save"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "iptables|ip6tables"
              },
              {
                "maxLen": 6,
                "pattern": "filter|nat|mangle|raw"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "exec \"$0\" -w -t \"$1\" -L -n -v -x --line-numbers",
              "{0}",
              "{1}"
            ],
            "description": "Read an iptables table with rule numbers and counters",
            "name": "ipt-list"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "iptables|ip6tables"
              },
              {
                "maxLen": 29,
                "pattern": "[A-Za-z][A-Za-z0-9_-]{0,28}"
              },
              {
                "maxLen": 5,
                "pattern": "[1-9][0-9]{0,4}"
              },
              {
                "maxLen": 3,
                "pattern": "tcp|udp"
              },
              {
                "maxLen": 49,
                "pattern": "[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 120,
                "pattern": "[0-9]{1,5}(:[0-9]{1,5})?(,[0-9]{1,5}(:[0-9]{1,5})?){0,14}"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              },
              {
                "maxLen": 6,
                "pattern": "ACCEPT|DROP|REJECT|LOG|RETURN"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "exec \"$0\" \"$@\"",
              "{0}",
              "-w",
              "-I",
              "{1}",
              "{2}",
              "-p",
              "{3}",
              "-s",
              "{4}",
              "-d",
              "{5}",
              "-m",
              "multiport",
              "--dports",
              "{6}",
              "-m",
              "comment",
              "--comment",
              "{7}",
              "-j",
              "{8}"
            ],
            "description": "Insert an iptables rule for ports",
            "name": "ipt-port"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "iptables|ip6tables"
              },
              {
                "maxLen": 29,
                "pattern": "[A-Za-z][A-Za-z0-9_-]{0,28}"
              },
              {
                "maxLen": 5,
                "pattern": "[1-9][0-9]{0,4}"
              },
              {
                "maxLen": 9,
                "pattern": "tcp|udp|icmp|ipv6-icmp|all"
              },
              {
                "maxLen": 49,
                "pattern": "[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 49,
                "pattern": "[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              },
              {
                "maxLen": 256,
                "pattern": "[\\p{L}\\p{N}][\\p{L}\\p{N} _.,:/@()+-]{0,63}"
              },
              {
                "maxLen": 6,
                "pattern": "ACCEPT|DROP|REJECT|LOG|RETURN"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "exec \"$0\" \"$@\"",
              "{0}",
              "-w",
              "-I",
              "{1}",
              "{2}",
              "-p",
              "{3}",
              "-s",
              "{4}",
              "-d",
              "{5}",
              "-m",
              "comment",
              "--comment",
              "{6}",
              "-j",
              "{7}"
            ],
            "description": "Insert an iptables rule for addresses and a protocol",
            "name": "ipt-host"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "iptables|ip6tables"
              },
              {
                "maxLen": 29,
                "pattern": "[A-Za-z][A-Za-z0-9_-]{0,28}"
              },
              {
                "maxLen": 5,
                "pattern": "[1-9][0-9]{0,4}"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "exec \"$0\" -w -D \"$1\" \"$2\"",
              "{0}",
              "{1}",
              "{2}"
            ],
            "description": "Delete an iptables rule by number",
            "name": "ipt-delete"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 9,
                "pattern": "iptables|ip6tables"
              },
              {
                "maxLen": 7,
                "pattern": "INPUT|FORWARD|OUTPUT"
              },
              {
                "maxLen": 6,
                "pattern": "ACCEPT|DROP"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "exec \"$0\" -w -P \"$1\" \"$2\"",
              "{0}",
              "{1}",
              "{2}"
            ],
            "description": "Set the policy of a built-in iptables chain",
            "name": "ipt-policy"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 24,
                "pattern": "/etc/iptables/rules\\.v[46]|/etc/sysconfig/ip6?tables"
              }
            ],
            "argv": [
              "sh",
              "-c",
              "f=\"$1\"\ncase \"$f\" in *v6|*ip6tables) b=ip6tables-save;; *) b=iptables-save;; esac\nmkdir -p \"$(dirname \"$f\")\" || exit 1\n\"$b\" > \"$f.ervisio-tmp\" || { rm -f \"$f.ervisio-tmp\"; exit 1; }\n[ -e \"$f\" ] && cp -p \"$f\" \"$f.bak\"\nmv \"$f.ervisio-tmp\" \"$f\" && echo \"$f\"",
              "sh",
              "{0}"
            ],
            "description": "Save iptables rules to the file loaded at boot (keeps a .bak copy)",
            "name": "ipt-save"
          },
          {
            "admin": true,
            "argv": [
              "sh",
              "-c",
              "fail2ban-client ping >/dev/null 2>&1 || { echo @@DOWN; exit 0; }\necho \"@@VERSION $(fail2ban-client --version 2>/dev/null | head -1)\"\njails=$(fail2ban-client status | sed -n \"s/.*Jail list:[[:space:]]*//p\" | tr \",\" \" \")\nfor j in $jails; do echo \"@@JAIL $j\"; fail2ban-client status \"$j\"; done\nexit 0"
            ],
            "description": "Read fail2ban jails and banned addresses",
            "name": "f2b-status"
          },
          {
            "admin": true,
            "args": [
              {
                "maxLen": 64,
                "pattern": "[A-Za-z0-9][A-Za-z0-9_.-]{0,63}"
              },
              {
                "maxLen": 7,
                "pattern": "banip|unbanip"
              },
              {
                "maxLen": 49,
                "pattern": "[0-9]{1,3}(\\.[0-9]{1,3}){3}(/[0-9]{1,2})?|[0-9a-fA-F:]*:[0-9a-fA-F:.]*(/[0-9]{1,3})?"
              }
            ],
            "argv": [
              "fail2ban-client",
              "set",
              "{0}",
              "{1}",
              "{2}"
            ],
            "description": "Ban or unban an address in a fail2ban jail",
            "name": "f2b-set"
          },
          {
            "admin": true,
            "adminUnlessGroup": "adm",
            "args": [
              {
                "allowDash": true,
                "maxLen": 19,
                "pattern": "-[0-9]{1,4}(min|h|d)|today|yesterday|[0-9]{4}-[0-9]{2}-[0-9]{2}( [0-9]{2}:[0-9]{2}(:[0-9]{2})?)?"
              },
              {
                "maxLen": 5,
                "pattern": "[1-9][0-9]{0,4}"
              },
              {
                "allowDash": true,
                "maxLen": 16,
                "pattern": "--grep=IN=\\.\\*OUT=|--no-hostname"
              }
            ],
            "argv": [
              "journalctl",
              "-k",
              "--no-pager",
              "-o",
              "short-iso-precise",
              "--since={0}",
              "-n",
              "{1}",
              "{2}"
            ],
            "description": "Read firewall lines from the kernel journal",
            "name": "log-journal",
            "timeoutSec": 60
          },
          {
            "admin": true,
            "adminUnlessGroup": "adm",
            "args": [
              {
                "allowDash": true,
                "maxLen": 16,
                "pattern": "--grep=IN=\\.\\*OUT=|--no-hostname"
              }
            ],
            "argv": [
              "journalctl",
              "-k",
              "-f",
              "-n",
              "0",
              "--no-pager",
              "-o",
              "short-iso-precise",
              "{0}"
            ],
            "description": "Follow firewall lines in the kernel journal",
            "name": "log-journal-follow",
            "timeoutSec": 600
          },
          {
            "admin": true,
            "adminUnlessGroup": "adm",
            "args": [
              {
                "maxLen": 5,
                "pattern": "[1-9][0-9]{0,4}"
              },
              {
                "maxLen": 40,
                "pattern": "/var/log/(ufw\\.log|kern\\.log|messages|syslog|firewalld|fail2ban\\.log)(\\.1)?"
              }
            ],
            "argv": [
              "tail",
              "-n",
              "{0}",
              "{1}"
            ],
            "description": "Read the last lines of a firewall log file",
            "name": "log-file",
            "timeoutSec": 60
          },
          {
            "admin": true,
            "adminUnlessGroup": "adm",
            "args": [
              {
                "maxLen": 40,
                "pattern": "/var/log/(ufw\\.log|kern\\.log|messages|syslog|firewalld|fail2ban\\.log)(\\.1)?"
              }
            ],
            "argv": [
              "tail",
              "-n",
              "0",
              "-F",
              "{0}"
            ],
            "description": "Follow a firewall log file",
            "name": "log-file-follow",
            "timeoutSec": 600
          }
        ],
        "files": {
          "write": [
            {
              "create": true,
              "path": "~/.config/ervisio/plugins/firewall"
            }
          ]
        },
        "jobs": [
          {
            "description": "Check that a firewall is running and send a notification when its state changes.",
            "name": "watchdog",
            "params": [
              {
                "description": "ufw, firewalld, nftables, iptables or fail2ban",
                "maxLen": 9,
                "name": "backend",
                "pattern": "ufw|firewalld|nftables|iptables|fail2ban"
              }
            ],
            "steps": [
              {
                "args": [
                  "{param.backend}"
                ],
                "command": "state",
                "id": "check"
              },
              {
                "id": "tell",
                "if": {
                  "step": "check",
                  "when": "changed"
                },
                "notify": {
                  "body": "The {param.backend} firewall is now {step.check.stdout} on this server.",
                  "level": "warn",
                  "link": "/p/firewall/firewall",
                  "title": "Firewall {param.backend}: {step.check.stdout}"
                }
              }
            ],
            "timeoutSec": 60
          }
        ],
        "notify": true
      },
      "contributes": {
        "pages": [
          {
            "icon": "shield",
            "id": "firewall",
            "title": "Firewall"
          }
        ],
        "snippets": [
          {
            "command": "sudo ufw status verbose",
            "name": "ufw status"
          },
          {
            "command": "sudo firewall-cmd --list-all-zones",
            "name": "firewalld zones"
          },
          {
            "command": "sudo nft -a list ruleset",
            "name": "nftables ruleset"
          },
          {
            "command": "sudo journalctl -k -f --grep='IN=.*OUT='",
            "name": "Firewall log (live)"
          }
        ],
        "widgets": [
          {
            "icon": "shield",
            "id": "status",
            "title": "Firewall"
          }
        ]
      },
      "visibleTo": {},
      "homepage": "https://github.com/Ervisio/plugin-firewall",
      "repo": "Ervisio/plugin-firewall",
      "trust": "team"
    }
  ]
}
