# Portal Configuration

This page describes how UDiTH Portal is configured and lists all available settings.

Portal uses the standard .NET configuration system. Throughout this page, settings are written using the .NET hierarchical notation, with a colon (`:`) separating each level of the hierarchy, for example `KeycloakAdmin:Name`. Array elements are addressed by their index, starting at `0`, for example `Serilog:WriteTo:0:Name`. Where a setting represents an array, the index is shown as `[]`.

## Loading Configuration

How a setting is provided depends on the platform.

### Linux (Docker)

On Linux, Portal is configured through environment variables, optionally backed by Docker secret files.

#### Environment variables

Each setting is provided as an environment variable. Because an environment variable name cannot contain a colon, replace every `:` in the setting name with a double underscore (`__`).

For example, the setting `KeycloakAdmin:Name` becomes:

```env
KeycloakAdmin__Name=admin
```

And an array element such as `Serilog:WriteTo:0:Name` becomes:

```env
Serilog__WriteTo__0__Name=Console
```

#### Docker secret files

Any variable can also be read from a file, similar to Docker secrets. To do this, append the `_file` suffix to the variable name and reference the file that contains the secret.

```env
KeycloakAdmin__Password=<pw>
KeycloakAdmin__Password_file=/run/secrets/keycloak_password
```

#### Escaping `$` in Docker Compose

In Docker Compose, a value containing `$` is interpreted as a variable reference. A literal `$` must therefore be escaped as `$$`.

### Windows

On Windows, settings are stored in the `Settings/sharedsettings.json` file inside the Portal installation directory. The configuration uses standard JSON, with the hierarchy expressed as nested objects. For example, the setting `KeycloakAdmin:Name` is written as:

```json
{
  "KeycloakAdmin": {
    "Name": "admin"
  }
}
```

Array elements become JSON arrays:

```json
{
  "Serilog": {
    "WriteTo": [
      { "Name": "Console" }
    ]
  }
}
```

## Settings Reference

The following list contains all available settings, written in .NET (`:`) notation. Remember to translate each name to the form required by your platform, as described under [Loading Configuration](#loading-configuration).

```env
###############################################################################
# Keycloak
###############################################################################

# URL used to connect to Keycloak. This is the public URL that both the server
# and browser clients use for authentication (e.g. https://keycloak.example.com).
KeycloakUrl=https://...
# Keycloak realm to use for authentication. This realm is created automatically
# during initial setup if it does not exist yet.
KeycloakRealm=portal
# Keycloak admin user name. Needs permission to manage the realm specified above.
KeycloakAdmin:Name=admin
# Keycloak admin user password.
KeycloakAdmin:Password=...

###############################################################################
# Server
###############################################################################

# MSSQL database connection string. Must point to an existing database with a
# user that has DB Owner permissions.
# Example: Server=mssql;Database=portal;User=portaluser;Password=secret;TrustServerCertificate=true
ConnectionStrings:AdminConnection=...
# The public URL under which Portal is reachable by clients
# (e.g. https://portal.example.com/Portal).
ServerUrl=https://...
# If Portal is served behind a reverse proxy that changes the base path,
# set this to the new relative path (e.g. /Portal).
RelativePath=/ (default)
# The TCP port on which the Kestrel web server listens.
# Only relevant when running as standalone (RunAsStandAlone=true).
ServerPort=5000 (default)
# UDiTH licence key for automatic activation on startup.
# This can also be configured later through the web interface.
LicenseKey=...
# DANGEROUS: Accept invalid TLS certificates for outgoing connections to
# Keycloak and licensing servers. Use only in test environments.
IgnoreCertificateErrors=false (default)
# When enabled, user names are included in server logs for HTTP requests and
# BBV sessions. Disable in environments where logging personal data is restricted.
ShowPII=false (default)
# Additional domains for which CORS Access-Control-Allow-Origin headers are
# returned. Use one entry per domain. Required when embedding Portal in an
# external website or calling the API from a different origin.
AccessControlAllowOrigin:[]=...
# Maximum size in bytes for any HTTP request body accepted by the server
# (Kestrel limit). If tus uploads are enabled, TusSettings:MaxRequestBodySizeClient
# must be smaller than or equal to this value, otherwise Portal will not start.
MaxRequestBodySizeServer=31457280 (default, 30 MiB)
# Periodically synchronise Keycloak user accounts with Portal (interval in
# milliseconds). Set to 0 or a negative value to synchronise once at startup
# and then stop.
AccountRefreshInterval=600000 (default, 10 minutes)

###############################################################################
# Proxy
###############################################################################

# If a forward proxy is required for outgoing connections to Keycloak or the
# licensing server, configure it here.
Proxy:Address=...
Proxy:UserName=...
Proxy:Password=...

###############################################################################
# Standalone HTTPS
###############################################################################

# When running without a reverse proxy, or when fully encrypted connections are
# required, you can terminate TLS directly in Kestrel. Provide either a PFX
# file (with password) or a PEM key and certificate pair. For PEM, a password
# is only required if the private key is encrypted.
StandaloneHttpsSettings:PfxFile=...
StandaloneHttpsSettings:PemKeyFile=...
StandaloneHttpsSettings:PemCertFile=...
StandaloneHttpsSettings:Password=...

###############################################################################
# Model Import (Central Model Repository)
###############################################################################

# Directory that is scanned for new model files to import.
ModelImport:ImportDirectory=/import (default)
# Directory where imported and processed model files are stored permanently.
ModelImport:ProviderStoragePath=/storage (default)
# Interval in milliseconds at which the import directory is scanned for new files.
ModelImport:IntervalInMilliseconds=5000 (default)

###############################################################################
# Model Processing
###############################################################################

# Globally disable processing (attribute extraction) of imported models.
# Models will still be imported, but no attributes will be written to the database.
ModelProcessing:ForcedSkipProcessing=false (default)
# Force reprocessing of previously failed model files (e.g. those marked as
# InvalidInput) after the given time in milliseconds. Leave unset to disable.
ModelProcessing:ForcedReprocessingTime=...
# Maximum number of model versions kept per model. Older versions beyond this
# count are deleted automatically after a successful import. Leave unset to
# keep all versions. Can be overridden per model version in the
# Modeldefinition.json file or through the web interface.
ModelProcessing:MaxRetainedModelVersions=...

###############################################################################
# Model File Overwrites
###############################################################################

# Replace specific files inside every model during import. The key is the
# relative file path inside the model archive, the value is the path to the
# replacement file. Multiple entries can be added.
ModelFileOverwrites:Values:Data_default_upvf=/overwrittendefault.upvf
ModelFileOverwrites:Values:Data_appcontrols_json=/appcontrols.json

###############################################################################
# CMR Uploads
###############################################################################

# Enable the tus upload endpoint and the resumable upload client in the frontend.
# When enabled, large files are uploaded in chunks that can be resumed after
# interruptions.
TusSettings:TusEnabled=true (default)
# Directory where incomplete tus upload chunks are stored before processing.
TusSettings:StoreRootPath=... (default)
# Maximum size in bytes of a single upload chunk sent from client to server.
# Must be smaller than or equal to MaxRequestBodySizeServer, otherwise Portal
# will refuse to start.
TusSettings:MaxRequestBodySizeClient=20971520 (default, 20 MiB)
# Number of chunks uploaded in parallel from the client to the server.
# Values greater than 1 enable parallel chunk uploading, which can improve
# upload speed on high-bandwidth connections.
TusSettings:ParallelUploads=1 (default)
# Interval in milliseconds between cleanup runs that remove expired incomplete
# uploads from the tus storage directory.
TusSettings:CleanupInterval=900000 (default, 15 minutes)
# Time in milliseconds before an incomplete upload without activity is
# considered expired and eligible for cleanup.
TusSettings:SlidingExpiration=86400000 (default, 24 hours)
# Time in milliseconds, measured from the last received chunk, after which a
# fully uploaded file that is still present in the tus storage directory is
# treated as orphaned and deleted. Completed uploads are normally removed as
# soon as they have been handed over to the import directory; this is a safety
# net for the case where that hand-over failed.
TusSettings:CompletedFileRetention=3600000 (default, 1 hour)
# Time in milliseconds an upload may stay locked before the lock is considered
# abandoned and can be taken over by a new request. This prevents an upload
# from staying locked for the lifetime of the process when a client disconnects
# abruptly. The value must be larger than the time a single chunk
# (MaxRequestBodySizeClient) can take on a slow connection.
TusSettings:FileLockTimeout=180000 (default, 3 minutes)
# Number of completed uploads that are transferred into the model import
# directory at the same time. These are large sequential copies, so higher
# values usually reduce total throughput and increase peak disk usage.
TusSettings:MaxParallelTransfers=1 (default)
# Buffer size in bytes used when a completed upload has to be copied instead of
# moved, which is the case whenever the tus storage directory and the model
# import directory are on different volumes.
TusSettings:TransferBufferSizeBytes=1048576 (default, 1 MiB)
# Interval in milliseconds at which the frontend polls the upload status while
# the server finishes processing a completed upload.
TusSettings:StatusPollInterval=3000 (default)
# Time in milliseconds the frontend keeps waiting for a completed upload to
# finish processing before it stops polling and asks the user to check back
# later. This does not affect the upload itself.
TusSettings:StatusWaitTimeout=1800000 (default, 30 minutes)

###############################################################################
# Import Notifications (Webhook)
###############################################################################

# Portal can report the progress and the result of every model import to an
# external HTTPS endpoint. Every delivery carries an HMAC-SHA256 signature
# derived from the shared secret below. The feature is disabled while
# Notification:Webhook:Url is empty.
# The target can only be set through configuration; there is no web interface
# for it.

# Absolute HTTPS URL that receives the notifications. Plain HTTP and URLs that
# contain credentials are rejected on startup and disable the feature.
Notification:Webhook:Url=https://...
# Shared secret used to sign every delivery. Required as soon as a URL is set.
# Use at least 32 random bytes.
Notification:Webhook:Secret=...
# Timeout in milliseconds for a single delivery attempt.
Notification:Webhook:Timeout=10000 (default)
# Number of attempts per event before the event is dropped. Set to 1 to disable
# retries.
Notification:Webhook:MaxAttempts=5 (default)
# Number of consecutive failed deliveries after which notifications are
# suspended until Portal is restarted. Set to 0 to keep retrying indefinitely.
Notification:Webhook:SuspendAfterConsecutiveFailures=50 (default)
# Maximum number of pending deliveries held in memory. When the queue is full
# the oldest pending delivery is dropped and a warning is logged.
Notification:Webhook:QueueCapacity=1000 (default)
# Minimum time in milliseconds between two progress notifications for the same
# import. Step changes and the final result are always sent immediately.
Notification:Webhook:MinimumProgressInterval=5000 (default)
# Minimum change in progress (a fraction between 0 and 1) before a progress
# notification is sent.
Notification:Webhook:MinimumProgressDelta=0.05 (default)

###############################################################################
# Azure Key Vault
###############################################################################

# Any setting on this page can be read from an Azure Key Vault instead of being
# stored locally. A secret name maps to a setting name by replacing each colon
# with a double dash, for example the secret ConnectionStrings--AdminConnection
# provides the setting ConnectionStrings:AdminConnection.
# The Key Vault is only queried when a vault URI is configured.

# Absolute URI of the Key Vault.
AzureKeyVault:VaultUri=https://my-vault.vault.azure.net/
# Optional secret name prefix. When set, only secrets whose name starts with
# this prefix are read, and the prefix is removed from the resulting setting
# name. This allows one vault to serve several environments.
AzureKeyVault:Prefix=prod-
# Credentials used to authenticate against the vault. Leave unset to use the
# ambient Azure identity (e.g. a managed identity). Provide either a client
# secret or a client certificate.
AzureKeyVault:TenantID=...
AzureKeyVault:ClientID=...
AzureKeyVault:ClientSecret=...
AzureKeyVault:ClientCertificatePath=...

###############################################################################
# Frontend Customisation
###############################################################################

# Page title shown in the browser tab.
FrontendSettings:Title=UDiTH Portal (default)
# Show or hide the theme switcher in the user interface.
FrontendSettings:EnableThemeSelect=true (default)
# Theme applied when no user preference is set. Must be one of the values in
# AvailableThemes.
FrontendSettings:DefaultTheme=light (default)
# List of themes available for selection. Each entry is a theme name.
FrontendSettings:AvailableThemes:[]=light,dark
# Hide specific navigation menu items by key. Refer to the frontend source
# (nav-menu.definition.ts) for the list of available keys.
FrontendSettings:HiddenNavigation:[]=...
# URI scheme prefix used for the "Open locally" button. This is used to launch
# the local UDiTH application from the browser.
FrontendSettings:OpenLocallyPrefix=upvapi:// (default)
# When enabled, the external identifier and model version UID fields are
# read-only in the frontend and cannot be edited by users.
FrontendSettings:LockIdentifiers=false (default)

###############################################################################
# BBV / ICE Server
###############################################################################

# For BBV (Browser-Based Viewing), one or more ICE (STUN/TURN) servers must be
# configured so that WebRTC connections can be established between client and
# render server. Multiple ICE servers can be added by incrementing the array
# index.

# Authentication type for the ICE server. Currently only "password" is supported.
StreamingConfig:IceConfig:IceServers:[]:CredentialType=password (default)
# One or more STUN/TURN URLs. Most TURN servers require separate entries for
# UDP and TCP (e.g. turn:server:3478 and turn:server:3478?transport=tcp).
StreamingConfig:IceConfig:IceServers:[]:Urls:[]=turn:...
# Username for TURN server authentication.
StreamingConfig:IceConfig:IceServers:[]:Username=caxturn
# Password for TURN server authentication.
StreamingConfig:IceConfig:IceServers:[]:Credential=...

###############################################################################
# Logging (Serilog)
###############################################################################

# Serilog structured logging configuration. Logs can be written to console
# and/or files. Multiple sinks can be configured by incrementing the WriteTo
# array index. Forwarding logs to an OTLP endpoint does not need a sink here;
# it is configured under OpenTelemetry.

# Default minimum log level. Valid values: Verbose, Debug, Information, Warning,
# Error, Fatal.
Serilog:MinimumLevel:Default=Information
# Override log levels for specific namespaces to reduce noise.
Serilog:MinimumLevel:Override:Microsoft.AspNetCore.SignalR=Information
Serilog:MinimumLevel:Override:Microsoft=Warning
Serilog:MinimumLevel:Override:System=Warning
Serilog:MinimumLevel:Override:Windows=Warning
# Console sink — writes log output to stdout.
Serilog:WriteTo:0:Name=Console

###############################################################################
# OpenTelemetry (Metrics, Traces & Logs)
###############################################################################

# The OpenTelemetry SDK reads its configuration from OTEL_* environment
# variables. Portal therefore accepts these settings under the OpenTelemetry
# section and writes them to the matching environment variable on startup,
# which makes them available from every configuration source, including Azure
# Key Vault and Docker secret files. Setting OpenTelemetry:Endpoint also
# enables the Serilog OpenTelemetry log sink.
# The OTEL_* environment variables listed underneath each setting can still be
# used directly. They have no hierarchical (`:`) form.

# OTLP endpoint for exporting metrics, traces and logs.
# Environment variable: OTEL_EXPORTER_OTLP_ENDPOINT
OpenTelemetry:Endpoint=https://...
# Service name reported in telemetry data.
# Environment variable: OTEL_SERVICE_NAME
OpenTelemetry:ServiceName=...
# Additional resource attributes attached to all telemetry data
# (e.g. environment=production).
# Environment variable: OTEL_RESOURCE_ATTRIBUTES
OpenTelemetry:ResourceAttributes=environment=...
# Interval in milliseconds at which metrics are exported.
# Environment variable: OTEL_METRIC_EXPORT_INTERVAL
OpenTelemetry:MetricExportInterval=10000
# Protocol used for all signals. Valid values: http/protobuf, grpc.
# Environment variable: OTEL_EXPORTER_OTLP_PROTOCOL
OpenTelemetry:Protocol=http/protobuf
# Authorization header(s) for the OTLP endpoint. Typically stored as a secret.
# Environment variable: OTEL_EXPORTER_OTLP_HEADERS
OpenTelemetry:Headers=Authorization=...
# Any further OTEL_* variable can be set verbatim. The key is used as the
# environment variable name and the value as its value.
OpenTelemetry:AdditionalVariables:OTEL_TRACES_SAMPLER=always_on

###############################################################################
# Advanced / Internal — Do not change these values unless instructed.
###############################################################################

# Duration in milliseconds after which a BBV watchdog session without heartbeat
# is considered gone and removed.
StreamingConfig:WatchdogSessionTimeOut=30000
# SignalR handshake timeout in milliseconds. Set to 0 to use the internal default.
StreamingConfig:ConnectionHandshakeTimeout=0
# Maximum SignalR hub message size in bytes. Set to 0 to use the internal
# default (32 KB). Increase only if you encounter message size errors.
StreamingConfig:MaximumReceiveMessageSize=0

# ZipArchive pool settings for model hosting. Controls how many ZipArchive
# instances are kept in memory for concurrent model file access.
# Minimum number of ZipArchive instances always kept in the pool.
ModelHosting:ZipArchivePoolMinSize=2
# Maximum number of ZipArchive instances allowed in the pool.
ModelHosting:ZipArchivePoolMaxSize=10
# Interval in milliseconds to check if the pool needs additional instances.
ModelHosting:ZipArchiveCreateDuration=1000
# Interval in milliseconds to check for and remove idle instances from the pool.
ModelHosting:ZipArchiveRemoveDuration=30000
# Time in milliseconds the pool must go without being fully used before a
# surplus instance is removed. This prevents instances that were needed very
# recently from being dropped again immediately.
ModelHosting:ZipArchiveDrainCooldown=60000
# Time in milliseconds without any model file access after which the pool is
# emptied completely, ignoring ZipArchivePoolMinSize.
ModelHosting:ZipArchiveIdleTimeout=600000
# Run as standalone Kestrel web server (true) or behind IIS (false).
RunAsStandAlone=true (default)
# Automatically run database migrations on startup. When enabled, the database
# schema is created or updated to the current version.
RunMigrationsOnStartup=true (default)
```
