{
  "schema_version": 1,
  "controller": {
    "id": "runnervm8k3b6",
    "capabilities": {
      "tls.certificate": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "renew": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": {
              "timeout_seconds": 180,
              "interval_seconds": 5
            }
          }
        }
      },
      "tls.uploaded_certificate": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "npm.proxy_host": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "github.delivery": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          }
        }
      },
      "portainer.stack": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "portainer.container": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "Defined by a compose file outside HQ. HQ can start, stop and restart it.",
            "verification": null
          },
          "restart": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          },
          "start": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          },
          "stop": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "tailscale.device": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": {
              "timeout_seconds": 60,
              "interval_seconds": 10
            }
          },
          "approve-routes": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "tailscale.policy": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": {
              "timeout_seconds": 60,
              "interval_seconds": 10
            }
          }
        }
      },
      "network": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "HQ does not create subnets. This entry records one.",
            "verification": null
          }
        }
      },
      "pki.authority": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "The authority is kept offline. HQ does not reach it.",
            "verification": null
          }
        }
      },
      "machine": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "HQ does not create machines. This entry records one.",
            "verification": null
          }
        }
      },
      "tls.delivery_target": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "Nothing to reconcile. Reconcile the certificates installed here.",
            "verification": null
          }
        }
      },
      "caddy.route": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          }
        }
      },
      "adguard.rewrite": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "cloudflare.dns_record": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "cloudflare.zone": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "A domain has no settings to reconcile.",
            "verification": null
          }
        }
      }
    }
  },
  "providers": [
    {
      "kind": "tls.certificate",
      "label": "TLS certificate",
      "summary": "Issues a Let's Encrypt certificate, renews it, and installs it wherever these names are served.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "renew": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": {
              "timeout_seconds": 180,
              "interval_seconds": 5
            }
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "One certificate HQ issues, deploys and keeps renewed.\n\nEverything about it is stated here: what it is called, which names it\ncovers, and where it installs. Adding a name is saving the form.\n\nTitles and descriptions live on the model because the form is generated\nfrom it.",
        "properties": {
          "certificate_name": {
            "description": "Lowercase, no spaces.",
            "maxLength": 160,
            "pattern": "^[a-z0-9][a-z0-9-]*$",
            "title": "Certificate name",
            "type": "string"
          },
          "domains": {
            "description": "Wildcards are allowed. Each domain must be in a Cloudflare zone HQ can edit, for the Let's Encrypt DNS challenge.",
            "items": {
              "type": "string"
            },
            "title": "Domains",
            "type": "array"
          },
          "install_on": {
            "description": "Where the certificate is deployed. Delivery settings are on each target.",
            "items": {
              "type": "string"
            },
            "title": "Install it on",
            "type": "array"
          },
          "renewal_window_days": {
            "default": 30,
            "description": "Days before expiry that renewal starts. HQ also renews at once if a consumer serves the wrong certificate.",
            "maximum": 60,
            "minimum": 1,
            "title": "Renew this many days early",
            "type": "integer"
          }
        },
        "required": [
          "certificate_name"
        ],
        "title": "TLSCertificateSpec",
        "type": "object"
      }
    },
    {
      "kind": "tls.uploaded_certificate",
      "label": "Uploaded certificate",
      "summary": "Installs a certificate you generated elsewhere. HQ keeps a copy so you can install it on more targets later.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "A certificate generated elsewhere, that HQ installs and keeps.\n\nSeparate from ``tls.certificate`` because the lifecycle is different, not\nbecause the certificate is. This one cannot be renewed by HQ (the CA that\nsigns it is deliberately air-gapped) so it has no renewal window and no\nautomatic renew action, and pretending otherwise would put a countdown on a\nthing HQ cannot act on.",
        "properties": {
          "certificate_name": {
            "description": "The certificate's name where it is installed.",
            "maxLength": 160,
            "minLength": 1,
            "pattern": "^[a-z0-9][a-z0-9.-]*$",
            "title": "Name",
            "type": "string"
          },
          "install_on": {
            "description": "Where to deploy it. You can add more later.",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "title": "Install it on",
            "type": "array"
          },
          "domains": {
            "description": "Read from the certificate on each upload. Remove names HQ should not treat as covered.",
            "items": {
              "type": "string"
            },
            "title": "Names it covers",
            "type": "array"
          }
        },
        "required": [
          "certificate_name",
          "install_on"
        ],
        "title": "UploadedCertificateSpec",
        "type": "object"
      }
    },
    {
      "kind": "npm.proxy_host",
      "label": "Proxy host",
      "summary": "Forwards a hostname to a service on your network over HTTPS. HQ creates it in Nginx Proxy Manager if it does not exist.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "properties": {
          "domain_names": {
            "description": "One per line.",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "title": "Hostnames",
            "type": "array"
          },
          "forward_scheme": {
            "description": "How the proxy connects to your service.",
            "enum": [
              "http",
              "https"
            ],
            "title": "Reach it over",
            "type": "string"
          },
          "forward_host": {
            "description": "The service's address, usually an internal IP.",
            "maxLength": 255,
            "minLength": 1,
            "title": "Send traffic to",
            "type": "string"
          },
          "forward_port": {
            "maximum": 65535,
            "minimum": 1,
            "title": "Port",
            "type": "integer"
          },
          "certificate_resource": {
            "default": "",
            "description": "Secures these names. Required when Force HTTPS is on.",
            "title": "Certificate",
            "type": "string"
          },
          "force_ssl": {
            "default": true,
            "description": "Redirect HTTP to HTTPS.",
            "title": "Force HTTPS",
            "type": "boolean"
          },
          "http2": {
            "default": true,
            "title": "HTTP/2",
            "type": "boolean"
          },
          "websocket": {
            "default": false,
            "description": "Needed for live updates, terminals and chat.",
            "title": "Allow websockets",
            "type": "boolean"
          },
          "caching_enabled": {
            "default": false,
            "title": "Cache assets",
            "type": "boolean"
          },
          "block_exploits": {
            "default": true,
            "description": "Nginx Proxy Manager's built-in request filtering.",
            "title": "Block common exploits",
            "type": "boolean"
          },
          "access_list_id": {
            "default": 0,
            "description": "An Nginx Proxy Manager access list id. 0 means none.",
            "minimum": 0,
            "title": "Access list",
            "type": "integer"
          },
          "advanced_config": {
            "default": "",
            "description": "Passed through as-is. Usually blank.",
            "title": "Extra nginx configuration",
            "type": "string"
          },
          "hsts_enabled": {
            "default": false,
            "title": "Hsts Enabled",
            "type": "boolean"
          },
          "hsts_subdomains": {
            "default": false,
            "title": "Hsts Subdomains",
            "type": "boolean"
          },
          "trust_forwarded_proto": {
            "default": false,
            "title": "Trust Forwarded Proto",
            "type": "boolean"
          },
          "serving": {
            "default": true,
            "title": "Serving",
            "type": "boolean"
          }
        },
        "required": [
          "domain_names",
          "forward_scheme",
          "forward_host",
          "forward_port"
        ],
        "title": "NPMProxyHostSpec",
        "type": "object"
      }
    },
    {
      "kind": "github.delivery",
      "label": "HQ deploys",
      "summary": "Checks that production runs the latest approved commit of each plugin, and reports it on the commit and its pull request.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "properties": {
          "repository": {
            "description": "The repository whose deploy workflow deploys HQ.",
            "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$",
            "title": "Repository",
            "type": "string"
          },
          "workflow": {
            "default": ".github/workflows/compose.yml",
            "pattern": "^\\.github/workflows/[A-Za-z0-9_.-]+\\.ya?ml$",
            "title": "Deploy workflow",
            "type": "string"
          },
          "branch": {
            "default": "main",
            "pattern": "^[A-Za-z0-9_./-]+$",
            "title": "Branch",
            "type": "string"
          },
          "production": {
            "const": "Every extension's latest admission, confirmed on GitHub",
            "default": "Every extension's latest admission, confirmed on GitHub",
            "title": "Production",
            "type": "string"
          }
        },
        "required": [
          "repository"
        ],
        "title": "GitHubDeliverySpec",
        "type": "object"
      }
    },
    {
      "kind": "portainer.stack",
      "label": "Container stack",
      "summary": "A set of containers on one machine. HQ creates it in Portainer if it does not exist.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": true,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "$defs": {
          "PortainerStackEnvVar": {
            "additionalProperties": false,
            "properties": {
              "name": {
                "maxLength": 200,
                "minLength": 1,
                "title": "Name",
                "type": "string"
              },
              "value": {
                "default": "",
                "maxLength": 4000,
                "title": "Value",
                "type": "string"
              }
            },
            "required": [
              "name"
            ],
            "title": "PortainerStackEnvVar",
            "type": "object"
          }
        },
        "additionalProperties": false,
        "properties": {
          "connection_ref": {
            "description": "The Portainer environment this runs in.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Portainer",
            "type": "string"
          },
          "host": {
            "description": "The machine this runs on.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Runs on",
            "type": "string"
          },
          "name": {
            "description": "Lowercase and hyphenated. Used as the compose project name.",
            "maxLength": 120,
            "minLength": 1,
            "pattern": "^[a-z0-9][a-z0-9-]*$",
            "title": "Stack name",
            "type": "string"
          },
          "compose": {
            "description": "The docker compose file, as it would be on disk.",
            "minLength": 1,
            "title": "Compose file",
            "type": "string"
          },
          "environment": {
            "description": "Values the compose file reads. Keep secrets in 1Password.",
            "items": {
              "$ref": "#/$defs/PortainerStackEnvVar"
            },
            "title": "Environment",
            "type": "array"
          },
          "hostnames": {
            "description": "Hostnames that reach this stack from outside, if any.",
            "items": {
              "type": "string"
            },
            "title": "Serves",
            "type": "array"
          },
          "port": {
            "anyOf": [
              {
                "maximum": 65535,
                "minimum": 1,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "The port on the machine. Needed only for host-network containers. Published ports are read from the running container.",
            "title": "Answers on port"
          }
        },
        "required": [
          "connection_ref",
          "host",
          "name",
          "compose"
        ],
        "title": "PortainerStackSpec",
        "type": "object"
      }
    },
    {
      "kind": "portainer.container",
      "label": "Container",
      "summary": "A container HQ watches and can start, stop or restart. Its compose file still defines it.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "Defined by a compose file outside HQ. HQ can start, stop and restart it.",
            "verification": null
          },
          "restart": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          },
          "start": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          },
          "stop": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "One container HQ is responsible for keeping up, not for defining.\n\nDeliberately identity and nothing else. A container's definition lives in\nwhatever compose file created it, which HQ does not see and must not\npretend to own: declaring one here says \"this is mine to watch and to\ncycle\", and reconciliation is locked because there is nothing to converge.\n\nA container Portainer did not create cannot be declared as a stack, but\nevery container can be started, stopped and restarted, because those are\nDocker's verbs rather than Portainer's.",
        "properties": {
          "connection_ref": {
            "description": "The Portainer that manages this machine.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Portainer",
            "type": "string"
          },
          "host": {
            "description": "The machine this runs on.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Runs on",
            "type": "string"
          },
          "name": {
            "description": "The container name, as Docker reports it.",
            "maxLength": 200,
            "minLength": 1,
            "title": "Container",
            "type": "string"
          },
          "on_demand": {
            "default": false,
            "description": "Usually stopped and removed. A sweep that misses it is not a finding.",
            "title": "Runs on demand",
            "type": "boolean"
          },
          "holds_docker_socket": {
            "default": false,
            "description": "Its job is Docker: a socket proxy or an agent. The socket and Docker's data stay listed, never an action item.",
            "title": "Holds the Docker socket",
            "type": "boolean"
          },
          "by_design": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "A check of the container standard it fails on purpose, by id, each with why: DNS on the host network, a proxy that binds 443 as root. HQ shows it with the reason and never as a gap.",
            "title": "Checks its job requires",
            "type": "object"
          },
          "hidden": {
            "default": false,
            "description": "Collapse it on the machine's page. HQ still watches and controls it.",
            "title": "Collapse on machine page",
            "type": "boolean"
          },
          "serves_ports": {
            "description": "Host-network containers only. Docker reports no ports for them, so list the ports here to link a proxy to it.",
            "items": {
              "type": "integer"
            },
            "title": "Answers on",
            "type": "array"
          },
          "source": {
            "default": "",
            "description": "The GitHub repository its image is built from, for an image that does not say. HQ reads its releases and advisories.",
            "maxLength": 300,
            "title": "Built from",
            "type": "string"
          }
        },
        "required": [
          "connection_ref",
          "host",
          "name"
        ],
        "title": "PortainerContainerSpec",
        "type": "object"
      }
    },
    {
      "kind": "tailscale.device",
      "label": "Tailnet device",
      "summary": "Settings HQ keeps for one device on your tailnet, such as key expiry.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": {
              "timeout_seconds": 60,
              "interval_seconds": 10
            }
          },
          "approve-routes": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "A machine on the tailnet whose settings HQ keeps, not one it created.\n\nThe same shape as a watched container: the device joined the tailnet by\nsomebody running `tailscale up` on it, and HQ has no business pretending\notherwise. What it can hold is the handful of decisions about that device\nwhich are made once and then quietly forgotten, and which have no symptom\nuntil the day they matter.\n\nNamed as the tailnet names it. That is often not what HQ calls the machine,\nand the join between the two is the address they share; using HQ's name here\nwould mean the controller had to guess which device was meant.",
        "properties": {
          "connection_ref": {
            "default": "",
            "description": "The connection HQ uses to change this device.",
            "maxLength": 160,
            "title": "Tailscale",
            "type": "string"
          },
          "name": {
            "description": "The device name, as the tailnet reports it.",
            "maxLength": 200,
            "minLength": 1,
            "title": "Device",
            "type": "string"
          },
          "key_expiry_disabled": {
            "default": false,
            "description": "Disables node key expiry. Without it, the device becomes unreachable when its key expires.",
            "title": "Disable key expiry",
            "type": "boolean"
          }
        },
        "required": [
          "name"
        ],
        "title": "TailnetDeviceSpec",
        "type": "object"
      }
    },
    {
      "kind": "tailscale.policy",
      "label": "Tailnet policy",
      "summary": "The tailnet's access policy. HQ runs the policy's tests before applying a change.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": true,
      "created_from": "tailnet",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": {
              "timeout_seconds": 60,
              "interval_seconds": 10
            }
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "The tailnet's access policy, as HQ last read it.\n\nNot something an operator adds here: it exists because a tailnet does.\n``created_from`` keeps it out of the \"add a resource\" picker for that\nreason: there is exactly one, and it arrived with the credential.",
        "properties": {
          "connection_ref": {
            "default": "",
            "maxLength": 160,
            "title": "Tailscale",
            "type": "string"
          },
          "document": {
            "default": "",
            "description": "The tailnet's access policy.",
            "title": "Policy",
            "type": "string"
          }
        },
        "title": "TailnetPolicySpec",
        "type": "object"
      }
    },
    {
      "kind": "network",
      "label": "Network",
      "summary": "An address range. HQ uses it to tell which network an address is on.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "HQ does not create subnets. This entry records one.",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "A range of addresses this estate is built on, and what it means.\n\nDeclared, because nothing sweeps a network. HQ learns addresses from the\nthings that answer at them (a container's published port, a device's\ntailnet address) and never learns what range they belong to or what that\nrange implies. An address on the LAN and one on the tailnet are reachable\nby different people, and only the ranges say which is which.",
        "properties": {
          "name": {
            "description": "A short name for this range.",
            "maxLength": 120,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "cidr": {
            "description": "The range in CIDR form, e.g. 198.51.100.0/24.",
            "maxLength": 64,
            "minLength": 1,
            "title": "Range",
            "type": "string"
          },
          "gateway": {
            "default": "",
            "description": "The range's router, if it has one.",
            "maxLength": 64,
            "title": "Gateway",
            "type": "string"
          },
          "purpose": {
            "default": "",
            "description": "One line. What uses this range.",
            "maxLength": 300,
            "title": "Purpose",
            "type": "string"
          }
        },
        "required": [
          "name",
          "cidr"
        ],
        "title": "NetworkSpec",
        "type": "object"
      }
    },
    {
      "kind": "pki.authority",
      "label": "Certificate authority",
      "summary": "A certificate authority you trust, including an offline root. HQ records it and does not renew it.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "The authority is kept offline. HQ does not reach it.",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "A certificate authority this estate trusts, and where its key lives.\n\nNot `tls.certificate`: that is a certificate HQ renews and installs. This is\nthe authority a certificate was issued *by*, including one HQ can never\nreach, because the whole point of an offline root is that nothing can. An\nauthority nothing sweeps still has an expiry, and an expiry nobody is\nwatching is the failure this records.",
        "properties": {
          "name": {
            "description": "The issuer name, as it appears in certificates.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Authority",
            "type": "string"
          },
          "covers": {
            "default": "",
            "description": "One line. The certificates this authority signs.",
            "maxLength": 300,
            "title": "Issues",
            "type": "string"
          },
          "expires_on": {
            "default": "",
            "description": "ISO date, e.g. 2036-05-02. Blank if it does not expire.",
            "maxLength": 10,
            "title": "Expires",
            "type": "string"
          },
          "key_location": {
            "default": "",
            "description": "Where the private key is stored. Never paste the key.",
            "maxLength": 300,
            "title": "Key location",
            "type": "string"
          },
          "issued_with": {
            "default": "",
            "description": "The signing tool, if any.",
            "maxLength": 160,
            "title": "Issued with",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "title": "CertificateAuthoritySpec",
        "type": "object"
      }
    },
    {
      "kind": "machine",
      "label": "Machine",
      "summary": "A machine no sweep finds, and its addresses. Machines behind Portainer or another connection are listed already.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "HQ does not create machines. This entry records one.",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "A machine HQ should know about, whether or not it can reach one.\n\nMost machines need no declaration: a swept Portainer names the ones it\nmanages, and a connection names what it points at. This is for the rest\n(the printer, the offline CA, the phone) and for saying what an address\nbelongs to, which is the difference between a proxy forwarding to a machine\nand a proxy forwarding into the dark.",
        "properties": {
          "name": {
            "description": "The machine's name in HQ. No slashes.",
            "maxLength": 160,
            "minLength": 1,
            "pattern": "^[^/]+$",
            "title": "Name",
            "type": "string"
          },
          "role": {
            "default": "",
            "description": "One line. Shown wherever the machine is listed.",
            "maxLength": 200,
            "title": "Purpose",
            "type": "string"
          },
          "addresses": {
            "description": "Every address it answers on: LAN, tailnet, public. Resources forwarding to these addresses link to this machine. Marked addresses are also observed by HQ.",
            "items": {
              "type": "string"
            },
            "title": "Addresses",
            "type": "array"
          }
        },
        "required": [
          "name"
        ],
        "title": "MachineSpec",
        "type": "object"
      }
    },
    {
      "kind": "tls.delivery_target",
      "label": "Certificate target",
      "summary": "A place certificates are installed, and how they get there. Certificates can only install on declared targets.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "Nothing to reconcile. Reconcile the certificates installed here.",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "One place a certificate can be installed, and how it arrives there.\n\nA Caddy host wants its certificate in a particular directory; a cPanel\naccount takes only the names it actually hosts. Those are properties of the\ntarget, true of every certificate it will ever serve, so they are stated\nonce here instead of on each certificate that installs there.\n\nFlat rather than a union per kind, because the form an operator fills is\ngenerated from this model's fields: a union has none, and the four shapes\ndiffer by one or two fields each.\n\nOne kind receives no certificate at all. A password manager is where an\noperator already looks up what a credential is and when it runs out, and a\ncertificate is both of those, so it is a place a certificate can be\n*recorded*, reached the same way, declared the same way, and listed in the\nsame menu. It carries no material; see ``OnePasswordPublication``.",
        "properties": {
          "kind": {
            "description": "Sets how the certificate is delivered and verified.",
            "enum": [
              "npm",
              "caddy",
              "cpanel",
              "onepassword"
            ],
            "title": "Type",
            "type": "string"
          },
          "connection_ref": {
            "description": "The connection HQ uses to reach this target.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Connection",
            "type": "string"
          },
          "name": {
            "description": "The certificate's name on the target. Only the certificate below uses it. Others installed here keep their own names.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Name at target",
            "type": "string"
          },
          "certificate_resource": {
            "default": "",
            "description": "The certificate that uses the name above. Blank if none.",
            "maxLength": 160,
            "title": "Certificate using this name",
            "type": "string"
          },
          "verify_domains": {
            "description": "Names HQ connects to here to confirm the certificate arrived. Leave empty to check every name it covers.",
            "items": {
              "type": "string"
            },
            "title": "Check these names",
            "type": "array"
          },
          "certificate_directory": {
            "default": "",
            "description": "Caddy only. The directory the certificate and key are written to.",
            "maxLength": 500,
            "title": "Certificate directory",
            "type": "string"
          },
          "discover_covered_hosts": {
            "default": false,
            "description": "Nginx Proxy Manager only. Check every proxy host whose name this certificate covers, in addition to the names above.",
            "title": "Check every proxy host it covers",
            "type": "boolean"
          },
          "install_domains": {
            "description": "cPanel only. cPanel holds one certificate per site, shared by the site's aliases. Leave empty to install on every site serving a name this certificate is checked at.",
            "items": {
              "type": "string"
            },
            "title": "Install only on the sites serving these names",
            "type": "array"
          },
          "vault": {
            "default": "",
            "description": "1Password only. The vault that holds the item.",
            "maxLength": 160,
            "title": "Vault",
            "type": "string"
          },
          "item": {
            "default": "",
            "description": "1Password only. The item HQ writes the certificate's details to. It must already exist. HQ does not create it.",
            "maxLength": 160,
            "title": "Item",
            "type": "string"
          }
        },
        "required": [
          "kind",
          "connection_ref",
          "name"
        ],
        "title": "TLSDeliveryTargetSpec",
        "type": "object"
      }
    },
    {
      "kind": "caddy.route",
      "label": "Caddy route",
      "summary": "A hostname an edge Caddy serves, and where it sends requests.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "properties": {
          "connection_ref": {
            "default": "",
            "description": "The connection to the host that serves this route.",
            "maxLength": 160,
            "title": "Caddy",
            "type": "string"
          },
          "domain": {
            "description": "The name this route answers for.",
            "maxLength": 253,
            "minLength": 1,
            "pattern": "^(?:\\*\\.)?[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*\\.?$",
            "title": "Hostname",
            "type": "string"
          },
          "upstream": {
            "default": "",
            "description": "Where Caddy sends the request, usually a container and port.",
            "maxLength": 253,
            "pattern": "^(?:|(?:(?:https?|h2c)://)?[A-Za-z0-9](?:[A-Za-z0-9._-]*|\\[[0-9A-Fa-f:.]+\\])(?::[0-9]{1,5})?)$",
            "title": "Hands off to",
            "type": "string"
          }
        },
        "required": [
          "domain"
        ],
        "title": "CaddyRouteSpec",
        "type": "object"
      }
    },
    {
      "kind": "adguard.rewrite",
      "label": "Internal DNS record",
      "summary": "Resolves a hostname to an IP on your network. HQ creates it in AdGuard if it does not exist.",
      "destructive": false,
      "public_effect": false,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "properties": {
          "domain": {
            "description": "The name that should resolve on your network.",
            "maxLength": 253,
            "minLength": 1,
            "title": "Hostname",
            "type": "string"
          },
          "answer": {
            "description": "The IP address this hostname resolves to.",
            "maxLength": 253,
            "minLength": 1,
            "title": "Points at",
            "type": "string"
          }
        },
        "required": [
          "domain",
          "answer"
        ],
        "title": "AdGuardRewriteSpec",
        "type": "object"
      }
    },
    {
      "kind": "cloudflare.dns_record",
      "label": "Public DNS record",
      "summary": "A DNS record anyone on the internet can look up.",
      "destructive": false,
      "public_effect": true,
      "requires_approval": false,
      "created_from": "zone",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "apply",
            "automatic": true,
            "reason": "",
            "verification": null
          },
          "delete": {
            "mode": "apply",
            "automatic": false,
            "reason": "",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "properties": {
          "zone": {
            "description": "The record's domain, e.g. example.com.",
            "maxLength": 253,
            "minLength": 1,
            "title": "Zone",
            "type": "string"
          },
          "name": {
            "description": "The full name, e.g. app.example.com.",
            "maxLength": 253,
            "minLength": 1,
            "title": "Hostname",
            "type": "string"
          },
          "record_type": {
            "enum": [
              "A",
              "AAAA",
              "CNAME",
              "TXT",
              "MX",
              "CAA"
            ],
            "title": "Record type",
            "type": "string"
          },
          "content": {
            "description": "An IP address for A and AAAA, a hostname for CNAME and MX, quoted text for TXT, or for CAA, e.g. 0 issue \"letsencrypt.org\".",
            "maxLength": 2048,
            "minLength": 1,
            "title": "Value",
            "type": "string"
          },
          "priority": {
            "anyOf": [
              {
                "maximum": 65535,
                "minimum": 0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "MX only. Lower numbers are tried first.",
            "title": "Priority"
          },
          "proxied": {
            "default": false,
            "description": "A, AAAA and CNAME only. On: Cloudflare answers, hides your address and adds caching, WAF and its certificate. Off: visitors reach your address directly.",
            "title": "Proxy through Cloudflare",
            "type": "boolean"
          },
          "ttl": {
            "default": 1,
            "description": "Seconds resolvers may cache this. 1 is automatic.",
            "maximum": 86400,
            "minimum": 1,
            "title": "TTL",
            "type": "integer"
          }
        },
        "required": [
          "zone",
          "name",
          "record_type",
          "content"
        ],
        "title": "CloudflareDNSRecordSpec",
        "type": "object"
      }
    },
    {
      "kind": "cloudflare.zone",
      "label": "Domain",
      "summary": "A domain HQ manages. Only declared domains have their records managed, even if the connection can see more zones.",
      "destructive": false,
      "public_effect": true,
      "requires_approval": false,
      "created_from": "",
      "controller": {
        "actions": {
          "reconcile": {
            "mode": "locked",
            "automatic": false,
            "reason": "A domain has no settings to reconcile.",
            "verification": null
          }
        }
      },
      "spec_schema": {
        "additionalProperties": false,
        "description": "A domain HQ is responsible for, and the connection that serves it.\n\nDeclaring one is what makes a zone HQ's business. The credential can see\nevery zone on the account, which is not the same as HQ having been asked to\nmanage them: a parked domain and a live one look identical to a token, and\nonly an operator knows which is which.\n\nIt carries no settings yet. Zone posture (TLS mode, minimum version, HSTS)\nis read through `cloudflare_api`; declaring and reconciling it is the next\nfield set here.",
        "properties": {
          "zone": {
            "description": "The domain itself, e.g. example.com.",
            "maxLength": 253,
            "minLength": 1,
            "title": "Domain",
            "type": "string"
          },
          "connection_ref": {
            "description": "The connection that holds this zone.",
            "maxLength": 160,
            "minLength": 1,
            "title": "Served by",
            "type": "string"
          }
        },
        "required": [
          "zone",
          "connection_ref"
        ],
        "title": "CloudflareZoneSpec",
        "type": "object"
      }
    }
  ]
}