Backend configuration #
Backend configuration controls how services, plugins, and databases behave at runtime. Configuration values are declared in Kotlin classes with typed fields and optional code-level defaults, which can then be overridden in per-environment YAML files.
Loading and runtime behavior #
Override precedence and merge #
Configuration values are resolved in order:
- Code defaults - the
initblock in the config class. - YAML files - applied left to right in the order they are passed at startup.
To support the ability to merge configuration values, configuration supports only the map type, even for elements that are logically just lists. This enables the ability for a config value to add elements that merge with pre-existing defaults. To clear an entry, set a map value to NULL.
As a best practice, organize YAML files with as little overlap as possible. Minimal overlap makes managing and understanding configuration easier.
Dynamic config updates #
The engine watches for config file changes and reloads them without a restart. Services receive updated values through the onConfigChanged event:
DemoService.kt
package unicorn.demo
override suspend fun onConfigChanged(serviceConfig: DemoServiceConfig) {
config = serviceConfig
}
Some resources like database connections, thread pools, and external client bindings are bound once at startup and cannot be swapped at runtime. Changing their configuration requires a restart. Understand which values in your config are consumed only during initialization before relying on dynamic reload.
Encrypt secrets #
Utilize the encrypted config feature for sensitive config values (Api keys, passwords, credentials). Use the Pragma Homebase Api to encrypt values for each shard and set the encrypted output in the relevant config file.
All secrets must be encrypted and decrypted per shard since shards use different encryption keys.
Encrypt a value:
- Connect to the VPN using the
*.ovpnfile provided during onboarding. - Navigate to your Homebase Api Url (e.g.
https://api.homebase.<studioname>.pragmaengine.com/). - Choose
/secrets/encryptV1. - Enter the
shardId,titleId, andvaluein the request body.
The encrypted value appears in the response.
Use encrypted values in a service config:
Declare a field using types.encryptedString in your config class, then call getDecryptedValue() at the point of use:
var clientSecret by types.encryptedString("Your app's secret key.")
val decrypted = config.clientSecret.getDecryptedValue()
In the YAML config file, the encrypted ciphertext is the field value:
game:
pluginConfigs:
MyService.myPlugin:
class: com.example.MyPlugin
config:
clientSecret: "encryptedCipherTextHere"
Shared config #
Shared config reduces duplication of config values that appear in different config blocks, including service, plugin, or node-service configs.
Define shared config blocks in YAML #
Shared config blocks are defined within the top-level shared config section. A unique value identifies each block and is referenced elsewhere in order to utilize the shared config values. The example below defines a SteamShared block that specifies values for the SteamCredentialsConfig class. The identity and order plugins utilize the shared values by saving the SteamShared key within the plugin config and using it to retrieve the shared config block within the onSharedConfigChanged handler described below.
unicorn/config/test/test.yml
social:
shared:
configs:
SteamShared:
class: pragma.shared.SteamCredentialsConfig
config:
steamWebAPIKey: "<encrypted-string>"
appId: "1234567890"
pluginConfigs:
AccountService.identityProviderPlugins:
plugins:
Steam:
class: pragma.account.SteamIdentityProviderPlugin
config:
sharedConfigKey: SteamShared
ThirdPartyNodeService.orderProviderPlugins:
plugins:
Steam:
class: pragma.order.SteamOrderProviderPlugin
config:
sharedConfigKey: SteamShared
Listen for shared config changes #
Implement the SharedConfigHandler interface to listen for shared config updates.
If you have a plugin that is part of a ProviderPluginCollection, use the getConfig function that accepts a string to find the matching section of shared config. Id and order provider plugins are the most common types of ProviderPluginCollection, and the base class ProviderPluginConfig has the field sharedConfigKey to make it easy to specify the key in your YAML.
class SteamOrderProviderPlugin(
override val service: Service,
override val contentDataNodeService: ContentDataNodeService,
// ...
) : /* ... , */ SharedConfigHandler {
override var config: SteamOrderProviderPluginConfig = defaultConfig
var steamCredentialsConfig: SteamCredentialsConfig? = null
override suspend fun onSharedConfigChanged(sharedConfig: ConfigCollection) {
steamCredentialsConfig = sharedConfig.getConfig(this.config.sharedConfigKey)
}
// ...
}
If there is only a single instance of a given config class in the shared config, the config object can be looked up with the generic getConfig function. If there are 0 entries for the type, null is returned; multiple entries for the type throws an exception.
class EpicTokenService(
pragmaNode: PragmaNode,
instanceId: UUID,
// ...
) : /* ... , */ ConfigHandler<EpicTokenServiceConfig> {
private var sharedConfig: EpicCredentialsConfig? = null
override suspend fun onSharedConfigChanged(sharedConfig: ConfigCollection) {
if (this.sharedConfig == null && sharedConfig.getConfig<EpicCredentialsConfig>() == null) {
// error if shared config not available on startup
error("Required config EpicCredentialsConfig not provided, exiting.")
}
this.sharedConfig = sharedConfig.getConfig<EpicCredentialsConfig>()
}
// ...
}
onSharedConfigChanged is called during startup with the initial config and again when config is redeployed via the infra tools (or edited while running locally). Exceptions thrown in onSharedConfigChanged during startup prevent Pragma from starting.
Configuration examples #
For concrete config examples, see:
- Custom services for the service config class and YAML structure.
- Plugins for the plugin config class.
- Persistence for the DAO config class and YAML structure.
Related topics #
- SDK configuration - client-side config for the Unreal and Unity SDKs.
- Plugins - declare and implement plugins that consume plugin configs.
- Persistence - DAO node services that consume database configs.