{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://remotenx.app/mdm/remotenx-config.schema.json",
  "title": "remoteNX managed configuration",
  "description": "What an organisation distributes to remoteNX through its MDM: policies, connections, groups, identities, tunnels, Horizon servers, snippets and SSH host trust. Delivered as the data asset of a `com.apple.configuration.app.managed` declaration (iOS 18.4+) or as the JSON string `remotenxConfiguration` of a classic Managed App Configuration. Forward compatible: remoteNX ignores keys it does not know, and an entry it cannot read is skipped while the others apply. Secrets never appear here: passwords, identities and certificates are ManagedApp assets referenced by identifier. See docs/MDM.md.",
  "type": "object",
  "properties": {
    "schemaVersion": {
      "type": "integer",
      "const": 1,
      "description": "The schema version the configuration is written for. Required. A higher version still loads; settings this build does not know are ignored."
    },
    "organization": {
      "type": "object",
      "description": "Shown on Settings > Managed by Your Organisation.",
      "properties": {
        "name": {
          "type": "string"
        },
        "supportEmail": {
          "type": "string",
          "format": "email"
        },
        "supportURL": {
          "type": "string",
          "format": "uri",
          "pattern": "^https?://"
        }
      },
      "additionalProperties": true
    },
    "userInfo": {
      "type": "object",
      "description": "Values of the {{username}}, {{email}} and {{deviceName}} variables, normally filled by the MDM's own per-user variables (Intune {{userprincipalname}}, Jamf $USERNAME, Workspace ONE {UserName}).",
      "properties": {
        "username": {
          "type": "string"
        },
        "email": {
          "type": "string"
        },
        "deviceName": {
          "type": "string"
        }
      },
      "additionalProperties": true
    },
    "policies": {
      "$ref": "#/$defs/policies"
    },
    "connections": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/connection"
      }
    },
    "groups": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/group"
      }
    },
    "identities": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/identity"
      }
    },
    "tunnels": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/tunnel"
      }
    },
    "horizonServers": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/horizonServer"
      }
    },
    "snippets": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/snippet"
      }
    },
    "knownHosts": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/knownHost"
      }
    },
    "sshCertificateAuthorities": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/certificateAuthority"
      }
    },
    "trust": {
      "$ref": "#/$defs/trust"
    }
  },
  "required": [
    "schemaVersion"
  ],
  "additionalProperties": true,
  "$defs": {
    "policies": {
      "type": "object",
      "description": "Every policy is optional. An absent policy is not enforced and the user's own choice applies. An enforced switch shows a lock and 'Set by your organisation'.",
      "properties": {
        "allowUnmanagedConnections": {
          "type": "boolean",
          "description": "false: only the organisation's connections are shown; the user's own stay on the device but hidden, and no new ones can be made."
        },
        "allowSavingPasswords": {
          "type": "boolean"
        },
        "allowClipboard": {
          "type": "boolean",
          "description": "Clipboard redirection in RDP and copy and paste in the terminal."
        },
        "allowDriveRedirection": {
          "type": "boolean"
        },
        "allowMicrophone": {
          "type": "boolean"
        },
        "minimumTLSVersion": {
          "type": "string",
          "enum": [
            "1.2",
            "1.3"
          ]
        },
        "requireNLA": {
          "type": "boolean",
          "description": "RDP Network Level Authentication on every connection."
        },
        "requireAppLock": {
          "type": "boolean",
          "description": "Face ID, Touch ID or the device passcode to open remoteNX."
        },
        "appLockTimeoutSeconds": {
          "type": "integer",
          "minimum": 0,
          "description": "How long the app may stay in the background before the lock asks again."
        },
        "allowSessionPreviews": {
          "type": "boolean",
          "description": "false: no session thumbnails are stored."
        },
        "allowCommandHistory": {
          "type": "boolean",
          "description": "false: SSH terminals keep no command history on the device."
        },
        "allowFileTransfer": {
          "type": "boolean",
          "description": "false: no file downloads from or uploads to SSH hosts (SFTP/SCP). Unset: follows allowDriveRedirection."
        },
        "hideHostnamesInLiveActivity": {
          "type": "boolean"
        },
        "telemetryAllowed": {
          "type": "boolean",
          "description": "false: crash reports and usage statistics are off and locked."
        },
        "reviewPromptAllowed": {
          "type": "boolean"
        },
        "allowKeyExport": {
          "type": "boolean",
          "description": "false: private keys cannot be exported or shared."
        },
        "sessionIdleTimeoutMinutes": {
          "type": "integer",
          "minimum": 1
        },
        "businessEntitlement": {
          "type": "boolean",
          "description": "The organisation licensed remoteNX Business for this device."
        },
        "crashReportDSN": {
          "type": "string",
          "format": "uri",
          "description": "Opt-in crash reports go to this Sentry-compatible DSN instead of the built-in one."
        },
        "feedbackURL": {
          "type": "string",
          "format": "uri"
        }
      },
      "additionalProperties": true
    },
    "connection": {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "integer"
          ],
          "description": "The administrator's own identifier, stable across pushes. The device derives the entry's identity from it, so renaming keeps favourites, colours and notes. Integers are read as strings."
        },
        "name": {
          "type": "string"
        },
        "protocol": {
          "type": "string",
          "enum": [
            "rdp",
            "ssh",
            "blast"
          ]
        },
        "host": {
          "type": "string",
          "minLength": 1
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        },
        "username": {
          "type": "string",
          "description": "May contain the variables {{username}}, {{email}} and {{deviceName}} (filled from `userInfo`)."
        },
        "domain": {
          "type": "string"
        },
        "group": {
          "type": "string",
          "minLength": 1,
          "description": "The `id` of an entry in `groups`."
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "color": {
          "type": "string",
          "description": "Colour tag of the app's palette (for example `blue`, `green`, `orange`, `red`, `purple`)."
        },
        "tunnel": {
          "type": "string",
          "minLength": 1,
          "description": "The `id` of an entry in `tunnels`."
        },
        "identity": {
          "type": "string",
          "minLength": 1,
          "description": "The `id` of an entry in `identities`."
        },
        "rdp": {
          "type": "object",
          "properties": {
            "gateway": {
              "type": "string",
              "description": "RD Gateway host[:port]."
            },
            "nla": {
              "type": "boolean"
            },
            "clipboard": {
              "type": "boolean"
            },
            "driveRedirection": {
              "type": "boolean"
            },
            "driveRedirectionReadOnly": {
              "type": "boolean"
            },
            "microphone": {
              "type": "boolean"
            },
            "audio": {
              "type": "boolean"
            },
            "colorDepth": {
              "type": "integer",
              "enum": [
                16,
                32
              ]
            },
            "scaleFactor": {
              "type": "number",
              "minimum": 1,
              "maximum": 3,
              "description": "1.0 native points, 2.0 Retina pixels; absent: automatic."
            },
            "kerberosRealm": {
              "type": "string"
            },
            "kerberosKDC": {
              "type": "string"
            }
          },
          "additionalProperties": true
        },
        "ssh": {
          "type": "object",
          "properties": {
            "identity": {
              "type": "string",
              "minLength": 1,
              "description": "The `id` of an entry in `identities`."
            },
            "tunnel": {
              "type": "string",
              "minLength": 1,
              "description": "The `id` of an entry in `tunnels`."
            },
            "startCommand": {
              "type": "string"
            },
            "terminalType": {
              "type": "string"
            },
            "keepAliveSeconds": {
              "type": "integer",
              "minimum": 0
            }
          },
          "additionalProperties": true
        },
        "blast": {
          "type": "object",
          "properties": {
            "server": {
              "type": "string",
              "minLength": 1,
              "description": "The `id` of an entry in `horizonServers`."
            },
            "desktopName": {
              "type": "string"
            },
            "launch": {
              "type": "string",
              "enum": [
                "htmlAccess",
                "officialClient"
              ]
            },
            "application": {
              "type": "boolean"
            }
          },
          "additionalProperties": true
        }
      },
      "required": [
        "id",
        "protocol"
      ],
      "anyOf": [
        {
          "required": [
            "host"
          ]
        },
        {
          "description": "A Horizon desktop or app without a host of its own: host, port, domain, launch and tunnel come from blast.server.",
          "required": [
            "protocol",
            "blast"
          ],
          "properties": {
            "protocol": {
              "const": "blast"
            },
            "blast": {
              "required": [
                "server"
              ]
            }
          }
        }
      ],
      "additionalProperties": true
    },
    "group": {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "integer"
          ],
          "description": "The administrator's own identifier, stable across pushes. The device derives the entry's identity from it, so renaming keeps favourites, colours and notes. Integers are read as strings."
        },
        "name": {
          "type": "string"
        },
        "color": {
          "type": "string",
          "description": "Colour tag of the app's palette (for example `blue`, `green`, `orange`, `red`, `purple`)."
        },
        "symbol": {
          "type": "string",
          "description": "SF Symbol name, for example server.rack or building.2."
        },
        "sortOrder": {
          "type": "integer"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": true
    },
    "identity": {
      "type": "object",
      "description": "A login. The secret never travels in the configuration: `password` names a ManagedApp password asset, `managedIdentity` a ManagedApp identity asset (certificate plus private key; for SSH its RSA or ECDSA P-256/P-384 key signs the login and is never exported). Without either, the app asks at every connection.",
      "properties": {
        "id": {
          "type": [
            "string",
            "integer"
          ],
          "description": "The administrator's own identifier, stable across pushes. The device derives the entry's identity from it, so renaming keeps favourites, colours and notes. Integers are read as strings."
        },
        "name": {
          "type": "string"
        },
        "username": {
          "type": "string",
          "description": "May contain the variables {{username}}, {{email}} and {{deviceName}} (filled from `userInfo`)."
        },
        "managedIdentity": {
          "type": "string",
          "description": "Identifier of an entry of AppConfig.Identities."
        },
        "password": {
          "type": "string",
          "description": "Identifier of an entry of AppConfig.Passwords."
        }
      },
      "required": [
        "id",
        "username"
      ],
      "additionalProperties": true
    },
    "tunnel": {
      "type": "object",
      "description": "An SSH tunnel (ssh -L) connections reach their host through.",
      "properties": {
        "id": {
          "type": [
            "string",
            "integer"
          ],
          "description": "The administrator's own identifier, stable across pushes. The device derives the entry's identity from it, so renaming keeps favourites, colours and notes. Integers are read as strings."
        },
        "name": {
          "type": "string"
        },
        "host": {
          "type": "string",
          "minLength": 1
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        },
        "identity": {
          "type": "string",
          "minLength": 1,
          "description": "The `id` of an entry in `identities`."
        },
        "username": {
          "type": "string",
          "description": "Used when there is no identity; the app asks for the password."
        },
        "localPort": {
          "type": "integer",
          "minimum": 0,
          "maximum": 65535,
          "description": "0 or absent: any free port."
        },
        "remoteHost": {
          "type": "string"
        },
        "remotePort": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        },
        "keepAliveSeconds": {
          "type": "integer",
          "minimum": 0
        },
        "viaTunnel": {
          "type": "string",
          "description": "The `id` of a jump tunnel (ssh -J)."
        }
      },
      "required": [
        "id",
        "host"
      ],
      "additionalProperties": true
    },
    "horizonServer": {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "integer"
          ],
          "description": "The administrator's own identifier, stable across pushes. The device derives the entry's identity from it, so renaming keeps favourites, colours and notes. Integers are read as strings."
        },
        "name": {
          "type": "string"
        },
        "kind": {
          "type": "string",
          "enum": [
            "connectionServer",
            "uag",
            "access"
          ],
          "default": "connectionServer"
        },
        "host": {
          "type": "string",
          "minLength": 1
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        },
        "domain": {
          "type": "string"
        },
        "launch": {
          "type": "string",
          "enum": [
            "htmlAccess",
            "officialClient"
          ]
        },
        "tunnel": {
          "type": "string",
          "minLength": 1,
          "description": "The `id` of an entry in `tunnels`."
        },
        "identity": {
          "type": "string",
          "minLength": 1,
          "description": "The `id` of an entry in `identities`."
        },
        "portalPath": {
          "type": "string",
          "description": "Portal page when it is not the default for the kind, for example /catalog-portal/ui."
        }
      },
      "required": [
        "id",
        "host"
      ],
      "additionalProperties": true
    },
    "snippet": {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "integer"
          ],
          "description": "The administrator's own identifier, stable across pushes. The device derives the entry's identity from it, so renaming keeps favourites, colours and notes. Integers are read as strings."
        },
        "name": {
          "type": "string"
        },
        "script": {
          "type": "string"
        },
        "sendReturn": {
          "type": "boolean"
        },
        "folder": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "script"
      ],
      "additionalProperties": true
    },
    "knownHost": {
      "type": "object",
      "description": "A pinned SSH host key: the host connects without the first-contact prompt, and any other key is refused without a prompt.",
      "properties": {
        "host": {
          "type": "string",
          "minLength": 1
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        },
        "keyType": {
          "type": "string",
          "description": "ssh-ed25519, ecdsa-sha2-nistp256, ssh-rsa, ..."
        },
        "key": {
          "type": "string",
          "contentEncoding": "base64",
          "description": "The SSH wire-format public key, base64: the second field of a known_hosts line."
        }
      },
      "required": [
        "host",
        "keyType",
        "key"
      ],
      "additionalProperties": true
    },
    "certificateAuthority": {
      "type": "object",
      "description": "An OpenSSH certificate authority (@cert-authority). Host certificates it signed for a host named in hostPatterns are trusted without a prompt. Not effective yet with libssh2 1.11 (see docs/MDM.md).",
      "properties": {
        "name": {
          "type": "string"
        },
        "publicKey": {
          "type": "string",
          "pattern": "^(ssh-ed25519|ecdsa-sha2-nistp(256|384|521)|ssh-rsa) [A-Za-z0-9+/=]+( .*)?$",
          "description": "The CA's public key as an OpenSSH line."
        },
        "hostPatterns": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Host patterns with * and ? (a leading ! excludes); absent or empty: every host."
        }
      },
      "required": [
        "publicKey"
      ],
      "additionalProperties": true
    },
    "trust": {
      "type": "object",
      "description": "TLS trust from ManagedApp assets (Welle G2).",
      "properties": {
        "anchorCertificates": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Identifiers of AppConfig.Certificates entries that are TLS trust anchors. Absent: every managed certificate is an anchor."
        },
        "clientIdentities": {
          "type": "array",
          "description": "Which managed identity answers a TLS client certificate request (UAG, Access, Horizon).",
          "items": {
            "type": "object",
            "properties": {
              "hostPatterns": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "minItems": 1
              },
              "managedIdentity": {
                "type": "string",
                "description": "Identifier of an AppConfig.Identities entry."
              }
            },
            "required": [
              "hostPatterns",
              "managedIdentity"
            ],
            "additionalProperties": true
          }
        }
      },
      "additionalProperties": true
    }
  }
}
