{
    "$id": "https://cartavis.org/schemas/controller_config_schema_2.json",
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "CARTA controller configuration schema",
    "description": "Schema defining configuration options for the CARTA controller (Version 2)",
    "type": "object",
    "definitions": {
        "keyAlgorithm": {
            "description": "Algorithm used for public/private keys",
            "type": "string",
            "default": "RS256",
            "enum": ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"]
        },
        "googleAuthProvider": {
            "title": "Google AuthProvider",
            "description": "Authentication configuration when using Google authentication",
            "type": "object",
            "additionalProperties": false,
            "required": ["clientId", "userLookupTable", "publicKeyLocation", "privateKeyLocation", "issuer"],
            "properties": {
                "clientId": {
                    "description": "Google application client ID",
                    "type": "string",
                    "pattern": "^\\S+.apps.googleusercontent.com$",
                    "examples": ["my-app-id.apps.googleusercontent.com"]
                },
                "validDomain": {
                    "description": "Valid domains to accept. If this is empty or undefined, all domains are accepted. Domain specified by `hd` field in Google authentication configuration.",
                    "type": "string",
                    "examples": ["gmail.com", "my-google-domain.com", ""]
                },
                "useEmailAsId": {
                    "description": "Whether to use the email field as a unique identifier",
                    "type": "boolean",
                    "default": true,
                    "examples": [true, false]
                },
                "userLookupTable": {
                    "description": "Path to user lookup table, a text file with one entry per line, in the format <unique user ID> <system user>. System usernames may not contain whitespace.",
                    "type": "string",
                    "examples": ["/etc/carta/userlookup.txt"]
                },
                "publicKeyLocation": {
                    "description": "Path to public key (in PEM format) used for verifying JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_public.pem"]
                },
                "privateKeyLocation": {
                    "description": "Path to private key (in PEM format) used for signing JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_private.pem"]
                },
                "keyAlgorithm": {
                    "$ref": "#/definitions/keyAlgorithm",
                    "default": "RS256"
                },
                "issuer": {
                    "description": "Issuer field for JWT",
                    "type": "string",
                    "examples": ["my-carta-server"]
                },
                "refreshTokenAge": {
                    "description": "Lifetime of refresh tokens",
                    "type": "string",
                    "default": "1w",
                    "examples": ["1w", "15h", "2d"]
                },
                "accessTokenAge": {
                    "description": "Lifetime of access tokens",
                    "type": "string",
                    "default": "15m",
                    "examples": ["90s", "1h", "15m"]
                },
                "scriptingTokenAge": {
                    "description": "Lifetime of scripting tokens",
                    "type": "string",
                    "default": "1w",
                    "examples": ["1w", "5d", "10h"]
                }
            }
        },
        "pamAuthProvider": {
            "title": "Local AuthProvider",
            "description": "Authentication configuration when using PAM-based authentication",
            "type": "object",
            "additionalProperties": false,
            "required": ["publicKeyLocation", "privateKeyLocation", "issuer"],
            "properties": {
                "publicKeyLocation": {
                    "description": "Path to public key (in PEM format) used for verifying JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_public.pem"]
                },
                "privateKeyLocation": {
                    "description": "Path to private key (in PEM format) used for signing JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_private.pem"]
                },
                "keyAlgorithm": {
                    "$ref": "#/definitions/keyAlgorithm",
                    "default": "RS256"
                },
                "issuer": {
                    "description": "Issuer field for JWT",
                    "type": "string",
                    "examples": ["my-carta-server"]
                },
                "refreshTokenAge": {
                    "description": "Lifetime of refresh tokens",
                    "type": "string",
                    "default": "1w",
                    "examples": ["1w", "15h", "2d"]
                },
                "accessTokenAge": {
                    "description": "Lifetime of access tokens",
                    "type": "string",
                    "default": "15m",
                    "examples": ["90s", "1h", "15m"]
                },
                "scriptingTokenAge": {
                    "description": "Lifetime of scripting tokens",
                    "type": "string",
                    "default": "1w",
                    "examples": ["1w", "5d", "10h"]
                }
            }
        },
        "ldapAuthProvider": {
            "title": "LDAP AuthProvider",
            "description": "Authentication configuration when using LDAP-based authentication",
            "type": "object",
            "additionalProperties": false,
            "required": ["publicKeyLocation", "privateKeyLocation", "issuer", "ldapOptions"],
            "properties": {
                "publicKeyLocation": {
                    "description": "Path to public key (in PEM format) used for verifying JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_public.pem"]
                },
                "privateKeyLocation": {
                    "description": "Path to private key (in PEM format) used for signing JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_private.pem"]
                },
                "keyAlgorithm": {
                    "$ref": "#/definitions/keyAlgorithm",
                    "default": "RS256"
                },
                "issuer": {
                    "description": "Issuer field for JWT",
                    "type": "string",
                    "examples": ["my-carta-server"]
                },
                "refreshTokenAge": {
                    "description": "Lifetime of refresh tokens",
                    "type": "string",
                    "default": "1w",
                    "examples": ["1w", "15h", "2d"]
                },
                "accessTokenAge": {
                    "description": "Lifetime of access tokens",
                    "type": "string",
                    "default": "15m",
                    "examples": ["90s", "1h", "15m"]
                },
                "scriptingTokenAge": {
                    "description": "Lifetime of scripting tokens",
                    "type": "string",
                    "default": "1w",
                    "examples": ["1w", "5d", "10h"]
                },
                "ldapOptions": {
                    "description": "Options to path through to the LDAP auth instance",
                    "type": "object",
                    "additionalProperties": true,
                    "required": ["url", "searchBase"],
                    "properties": {
                        "url": {
                            "description": "LDAP connection URI",
                            "type": "string",
                            "format": "uri",
                            "pattern": "^ldaps?://"
                        },
                        "searchBase": {
                            "description": "Search base",
                            "type": "string"
                        },
                        "searchFilter": {
                            "description": "Search filter to use",
                            "type": "string",
                            "default": "uid={{username}}"
                        },
                        "starttls": {
                            "description": "Whether to start TLS when making a connection",
                            "type": "boolean",
                            "default": true
                        },
                        "reconnect": {
                            "description": "Whether to automatically reconnect to LDAP",
                            "type": "boolean",
                            "default": true
                        },
                        "bindProperty": {
                            "description": "Property of the LDAP user object to use when binding to verify the password",
                            "type": "string",
                            "default": "dn"
                        },
                        "searchScope": {
                            "description": "Scope of the search",
                            "type": "string",
                            "enum": ["base", "one", "sub"],
                            "default": "sub"
                        },
                        "bindDN": {
                            "description": "Admin connection DN, e.g. uid=myapp,ou=users,dc=example,dc=org. If not given at all, admin client is not bound.",
                            "type": "string"
                        },
                        "bindCredentials": {
                            "description": "Password for bindDN",
                            "type": "string"
                        },
                        "cache": {
                            "description": "If true, then up to 100 credentials at a time will be cached for 5 minutes",
                            "type": "boolean",
                            "default": false
                        },
                        "strictDN": {
                            "description": "Force strict DN parsing for client methods",
                            "type": "boolean",
                            "default": true
                        },
                        "idleTimeout": {
                            "description": "Milliseconds after last activity before client emits idle event",
                            "type": "number"
                        }
                    }
                }
            }
        },
        "externalAuthProvider": {
            "title": "External AuthProvider",
            "description": "OAuth2-compatible authentication configuration",
            "type": "object",
            "additionalProperties": false,
            "required": ["issuers", "publicKeyLocation", "tokenRefreshAddress", "uniqueField"],
            "properties": {
                "issuers": {
                    "description": "List of valid issuers in JWT field",
                    "type": "array",
                    "examples": [["my-auth-server", "my-other-auth-server"]],
                    "minItems": 1,
                    "items": {
                        "type": "string"
                    }
                },
                "publicKeyLocation": {
                    "description": "Path to public key (in PEM format) used for verifying JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/my_auth_server_public_key.pem"]
                },
                "keyAlgorithm": {
                    "$ref": "#/definitions/keyAlgorithm",
                    "default": "RS256"
                },
                "uniqueField": {
                    "description": "Name of unique field to use as user ID",
                    "type": "string",
                    "examples": ["user", "sub", "user_id"]
                },
                "tokenRefreshAddress": {
                    "description": "Route for refreshing access tokens",
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https?://"
                },
                "logoutAddress": {
                    "description": "Route for logging out",
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https?://"
                },
                "userLookupTable": {
                    "description": "Path to user lookup table, a text file with one entry per line, in the format <unique user ID> <system user>. System usernames may not contain whitespace. If no user lookup is needed, this should be omitted.",
                    "type": "string",
                    "examples": ["/etc/carta/userlookup.txt"]
                }
            }
        },
        "oidcAuthProvider": {
            "title": "OpenID Connect AuthProvider",
            "description": "OpenID Connect authentication configuration",
            "type": "object",
            "additionalProperties": false,
            "required": ["idpUrl", "clientId", "localPublicKeyLocation", "localPrivateKeyLocation", "issuer", "symmetricKeyLocation"],
            "properties": {
                "idpUrl": {
                    "description": "Base URL for identity provider endpoint",
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https?://",
                    "examples": ["https://domain.xyz/auth/realms/example"]
                },
                "uniqueField": {
                    "description": "Name of unique field to use as user ID.  Note that as per the OpenID Connect specification only sub/issuer combination is guaranteed to be stable and unique for an arbitrary issuer, though other values such as preferred_username may be usable when the team running the CARTA installation and the issuer are the same.",
                    "type": "string",
                    "examples": ["sub", "preferred_username"],
                    "default": "sub"
                },
                "clientId": {
                    "description": "Client ID as registered with identity provider",
                    "type": "string",
                    "minLength": 1,
                    "examples": ["carta"]
                },
                "clientSecret": {
                    "description": "Client secret as registered with identity provider",
                    "type": "string",
                    "minLength": 1
                },
                "scope": {
                    "description": "Scopes to request from the OpenID Connect server",
                    "type": "string",
                    "default": "openid",
                    "examples": ["openid", "openid groups"]
                },
                "userLookupTable": {
                    "description": "Path to user lookup table, a text file with one entry per line, in the format <unique user ID> <system user>. System usernames may not contain whitespace. If no user lookup is needed, this should be omitted.",
                    "type": "string",
                    "examples": ["/etc/carta/userlookup.txt"]
                },
                "groupsField": {
                    "description": "Name of field containing list of user roles/groups",
                    "type": "string",
                    "examples": ["groups", "roles"]
                },
                "requiredGroup": {
                    "description": "Role to ensure is included among the values in groupsField",
                    "type": "string",
                    "examples": ["carta-users", "carta-testers"]
                },
                "localPublicKeyLocation": {
                    "description": "Path to public key (in PEM format) used for verifying JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_public.pem"]
                },
                "localPrivateKeyLocation": {
                    "description": "Path to private key (in PEM format) used for signing JWTs",
                    "type": "string",
                    "examples": ["/etc/carta/carta_private.pem"]
                },
                "keyAlgorithm": {
                    "$ref": "#/definitions/keyAlgorithm",
                    "default": "RS256"
                },
                "issuer": {
                    "description": "Issuer field for JWT",
                    "type": "string",
                    "examples": ["my-carta-server"]
                },
                "cacheAccessTokenMinValidity": {
                    "description": "If an access token was previously issued from the upstream server with at least this many seconds of lifetime remaining, a new upstream query will not be performed and a local token with the previous token's remaining lifetime will be issued instead",
                    "type": "integer",
                    "default": 100
                },
                "symmetricKeyLocation": {
                    "description": "Path to symmetric key (base64-encoded) used for refresh tokens. At present this uses the A256GCM algorithm which requires 32 bytes of random data which can be generated using `openssl rand -base64 32`",
                    "type": "string",
                    "examples": ["/etc/carta/carta_symmetric.pem"]
                },
                "symmetricKeyType": {
                    "description": "Selected from the 'JSON Web Signature and Encryption Algorithms' section of https://www.iana.org/assignments/jose/jose.xhtml",
                    "type": "string",
                    "default": "A256GCM"
                },
                "additionalAuthParams": {
                    "description": "Additional parameters to include in authentication requests to deal with identity providers. The example contains the additional arguments required to ensure that Google provide a refresh token when using it with OIDC.",
                    "type": "array",
                    "default": [],
                    "examples": [
                        [
                            [
                                ["access_type", "offline"],
                                ["prompt", "consent"]
                            ]
                        ]
                    ],
                    "items": {
                        "type": "array",
                        "minItems": 2,
                        "maxItems": 2
                    }
                },
                "postLogoutRedirect": {
                    "description": "Optional parameter for specifying an alternate address to redirect to after logout",
                    "type": "string",
                    "format": "uri-reference",
                    "examples": ["https://my-organisation.com/"]
                }
            }
        }
    },
    "additionalProperties": false,
    "properties": {
        "$schema": {
            "description": "Reference to configuration schema file",
            "type": "string"
        },
        "controllerConfigVersion": {
            "description": "The version of this controller configuration",
            "type": "string",
            "default": "2.0"
        },
        "authProviders": {
            "title": "AuthProviders",
            "description": "Configuration option for authentication providers",
            "type": "object",
            "properties": {
                "google": {
                    "description": "Google AuthProvider",
                    "$ref": "#/definitions/googleAuthProvider"
                },
                "pam": {
                    "description": "PAM AuthProvider",
                    "$ref": "#/definitions/pamAuthProvider"
                },
                "ldap": {
                    "description": "LDAP AuthProvider",
                    "$ref": "#/definitions/ldapAuthProvider"
                },
                "external": {
                    "description": "External AuthProvider",
                    "$ref": "#/definitions/externalAuthProvider"
                },
                "oidc": {
                    "description": "OpenID Connect AuthProvider",
                    "$ref": "#/definitions/oidcAuthProvider"
                }
            },
            "default": {
                "pam": {
                    "publicKeyLocation": "/etc/carta/carta_public.pem",
                    "privateKeyLocation": "/etc/carta/carta_private.pem",
                    "issuer": "carta"
                }
            }
        },
        "database": {
            "description": "Database configuration",
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "uri": {
                    "description": "MongoDB connection URI used to connect to a MongoDB deployment",
                    "type": "string",
                    "format": "uri",
                    "pattern": "^mongodb://",
                    "default": "mongodb://localhost:27017"
                },
                "databaseName": {
                    "description": "Default database to connect to",
                    "type": "string",
                    "default": "CARTA"
                }
            },
            "default": {
                "uri": "mongodb://localhost:27017",
                "databaseName": "CARTA"
            }
        },
        "serverPort": {
            "description": "Port to listen on. It is advised to listen on a port other than 80 or 443, behind an SSL proxy",
            "anyOf": [
                {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 65535
                },
                {
                    "type": "string",
                    "minLength": 2
                }
            ],
            "default": 8000,
            "examples": [8000, 8080, "/run/carta-controller", "var/run/carta"]
        },
        "serverInterface": {
            "description": "Host interface to listen on. If empty, all interfaces are used",
            "type": "string",
            "examples": ["localhost", "127.0.0.1"]
        },
        "httpOnly": {
            "description": "Allow HTTP-only connections. For testing or internal networks only",
            "type": "boolean",
            "default": false
        },
        "serverAddress": {
            "description": "Public-facing server address. If this is specified, all requests will be redirected to this address, otherwise any address used will be preserved",
            "type": "string",
            "format": "uri"
        },
        "dashboardAddress": {
            "description": "Optional parameter for explicitly configuring the dashboard address. This can be absolute or relative. This is required if running the controller on a subdirectory",
            "type": "string",
            "format": "uri-reference",
            "examples": ["https://my-server.com/carta/dashboard", "/carta/dashboard", "/carta-versions/dev/dashboard"]
        },
        "apiAddress": {
            "description": "Optional parameter for explicitly configuring a custom API base address. This can be absolute or relative. This is required if running the controller on a subdirectory",
            "type": "string",
            "format": "uri-reference",
            "examples": ["https://my-server.com/carta/api", "/carta/api", "/carta-versions/dev/api"]
        },
        "frontendPath": {
            "description": "Path to the built frontend folder. If no path is provided, the packaged version will be used",
            "type": "string"
        },
        "backendPorts": {
            "description": "Port range to use for the CARTA backend process",
            "type": "object",
            "additionalProperties": false,
            "default": {
                "min": 3003,
                "max": 3500
            },
            "required": ["min", "max"],
            "properties": {
                "min": {
                    "minimum": 1024,
                    "maximum": 65535,
                    "type": "integer"
                },
                "max": {
                    "minimum": 1024,
                    "maximum": 65535,
                    "type": "integer"
                }
            }
        },
        "processCommand": {
            "description": "Path to CARTA backend executable",
            "type": "string",
            "examples": ["/usr/bin/carta_backend", "/usr/local/bin/carta_backend"],
            "default": "/usr/bin/carta_backend"
        },
        "preserveEnv": {
            "description": "Use the --preserve-env argument when calling sudo",
            "type": "boolean",
            "default": true
        },
        "killCommand": {
            "description": "Path to CARTA kill script",
            "type": "string",
            "examples": ["/usr/local/bin/carta-kill-script"],
            "default": "/usr/local/bin/carta-kill-script"
        },
        "rootFolderTemplate": {
            "description": "Top-level path of directories accessible to CARTA. The `{username}` placeholder will be replaced with the username. Defaults to `/usr/share/carta` if it exists, or `/usr/local/share/carta` if it exists. If neither exists and no default is provided, the controller exits with an error",
            "type": "string",
            "examples": ["/home/{username}", "/"]
        },
        "baseFolderTemplate": {
            "description": "Starting directory of CARTA. Must be a subfolder of rootFolderTemplate. The `{username}` placeholder will be replaced with the username. Defaults to the same value as `rootFolderTemplate`",
            "type": "string",
            "examples": ["/home/{username}/CARTA", "/data", "/"]
        },
        "logFileTemplate": {
            "description": "(Deprecated) Location of log file for backends. This has been renamed to `backendLogFileTemplate` and will be removed in a future version. The `{username}`, `{pid}` and `{datetime}` placeholders will be replaced with the username, process ID. and dat/time formatted as `YYYYMMDD.h_mm_ss` respectively",
            "type": "string"
        },
        "backendLogFileTemplate": {
            "description": "Location of log file for backends. The `{username}`, `{pid}` and `{datetime}` placeholders will be replaced with the username, process ID. and dat/time formatted as `YYYYMMDD.h_mm_ss` respectively",
            "type": "string",
            "default": "/var/log/carta/{username}_{datetime}_{pid}.log",
            "examples": ["/var/log/carta/{username}_{pid}.log", "/home/{username}/CARTA/log/{datatime}_{pid}.log"]
        },
        "logLevelConsole": {
            "description": "Level of messages to log to console.",
            "type": "string",
            "enum": ["none", "emerg", "alert", "crit", "error", "warning", "notice", "info", "debug"],
            "default": "info"
        },
        "logTypeConsole": {
            "description": "Output format to console",
            "type": "string",
            "enum": ["text", "json"],
            "default": "text"
        },
        "logLevelFile": {
            "description": "Level of messages to log to file.",
            "type": "string",
            "enum": ["none", "emerg", "alert", "crit", "error", "warning", "notice", "info", "debug"],
            "default": "info"
        },
        "logTypeFile": {
            "description": "Output format to file",
            "type": "string",
            "enum": ["text", "json"],
            "default": "json"
        },
        "logFile": {
            "description": "Name of file to send log messages to.",
            "type": "string",
            "examples": ["/var/log/carta-controller.log", "/var/spool/carta/controller.log"]
        },
        "timezone": {
            "description": "Timezone to use in logs. If not specified, the local timezone is used.",
            "type": "string",
            "examples": ["Africa/Johannesburg", "America/New_York", "Asia/Tokyo", "Europe/London"]
        },
        "additionalArgs": {
            "description": "Additional arguments to be passed to the backend process, defined as an array of strings. See backend documentation for details.",
            "type": "array",
            "examples": [["--omp_threads", "4", "--initial_timeout", "30", "--exit_timeout", "0"]],
            "items": {
                "type": "string"
            }
        },
        "startDelay": {
            "description": "Wait time before checking whether started process is still running and sending a response to the connecting client",
            "type": "integer",
            "minimum": 0,
            "default": 250
        },
        "dashboard": {
            "description": "Dashboard appearance configuration",
            "type": "object",
            "properties": {
                "backgroundColor": {
                    "description": "Background color for the dashboard",
                    "type": "string",
                    "default": "#f6f8fa",
                    "examples": ["red", "rgb(171 66 66)", "#ff11ee"]
                },
                "bannerColor": {
                    "description": "Background color for the institutional logo banner",
                    "type": "string",
                    "default": "#606f7e",
                    "examples": ["red", "rgb(171 66 66)", "#ff11ee"]
                },
                "bannerImage": {
                    "description": "Path to institutional logo in PNG or SVG format",
                    "type": "string"
                },
                "infoText": {
                    "description": "Text displayed before and after sign in. Plain text or HTML",
                    "type": "string",
                    "examples": ["Welcome to the server", "<span>Welcome to <b>the</b> server</span>"]
                },
                "loginText": {
                    "description": "Text displayed before sign-in only. Plain text or HTML",
                    "type": "string",
                    "examples": ["Please enter your username and password", "<span>Click <b>Sign in</b> to log in via Google</span>"]
                },
                "footerText": {
                    "description": "Footer text. Plain text or HTML",
                    "type": "string",
                    "examples": [
                        "Please contact the CARTA helpdesk for more information",
                        "<span>If you would like to access the server, or have any problems, comments or suggestions, please <a href='mailto:test@test.com'>contact us.</a></span>"
                    ]
                }
            }
        },
        "scriptingAccess": {
            "description": "Control scripting access for users.",
            "type": "string",
            "enum": ["enabled-all-users", "disabled-all-users", "opt-in"],
            "default": "disabled-all-users"
        }
    },
    "if": {
        "properties": {
            "serverPort": {
                "type": "string"
            }
        }
    },
    "then": {
        "properties": {
            "serverInterface": {
                "type": "null"
            }
        }
    }
}
