Configuration
Altruist uses YAML configuration files to define server behavior. No builder pattern, no fluent API — just a clean config.yml that drives everything.
Config File Loading
On startup, AppConfigLoader loads configuration in this order (later sources override earlier ones):
config.yml(required)config.{environment}.yml(optional, e.g.,config.Development.yml; see Environment)- Environment variables (
ALTRUIST__...and those of your own config roots, see Environment Variables) - Command-line arguments
Note:
Config files are loaded from AppContext.BaseDirectory — typically your build output directory. Make sure config.yml is copied to the output.
Full Configuration Reference
Server & HTTP
altruist:
server:
http:
host: "0.0.0.0" # Bind address
port: 8080 # HTTP port
path: "/" # Base path for HTTP controllers
errors: { format: simple } # {error, message, field?} for every API failure (default: none)
hardening: { ... } # forwarded headers, security headers, body limits (all off by default)
rate-limit: { ... } # named HTTP rate-limit policies
See HTTP API for errors, hardening and rate-limit.
Transport
Enable any combination of transports. Each has its own enabled flag:
altruist:
server:
transport:
codec:
provider: json # json or messagepack (shared across all transports)
websocket:
enabled: true # Enable WebSocket
path: /ws # WebSocket endpoint path
tcp:
enabled: true # Enable TCP
port: 13000 # TCP listen port
udp:
enabled: false # Disable UDP
port: 13001 # UDP listen port
Persistence
altruist:
persistence:
cache:
provider: inmemory # In-memory cache (default)
database:
provider: postgres # Database provider
host: localhost
port: 5432
username: myuser
password: mypassword
database: mydb
Security
altruist:
security:
mode: "jwt" # Enables JWT authentication
key: "" # signing key; Production refuses a missing, default or short one
Passwords, access tokens, refresh tokens, cookies and tickets have their own keys under altruist:security — see Authentication, Accounts & Sessions and Connection Tickets. Failure lockouts (lockout) and the captcha (captcha): Lockout & Captcha. Email delivery is altruist:email: Email.
Game Engine
altruist:
environment:
mode: "3D" # 2D or 3D mode
game:
engine:
diagnostics: true # Enable engine diagnostics logging
framerateHz: 30 # Engine tick rate
unit: "hz" # Cycle unit
gravity: { x: 0, y: -9.81 }
worlds:
partitioner: { width: 256, height: 256, depth: 256 }
items:
- index: 0
id: "main-world"
size: { x: 1000, y: 1000, z: 1000 }
gravity: { x: 0, y: -9.81, z: 0 }
position: { x: 0, y: 0, z: 0 }
data-path: "Resources/world.json" # Optional world data file
Server Node & Fleet
Capacity, drain and the fleet of servers (see Capacity, Drain & Health and Fleet & Scaling):
altruist:
server:
capacity:
max-load: 0 # load budget; 0 = unlimited
world-load: 0 # load per persistent world
drain:
timeout: 30 # seconds running work may take when the server stops
force-grace: 5
on-shutdown: true
token: "" # POST /altruist/server/drain from outside loopback
fleet:
redis: "" # shared backplane; empty = a fleet of one (in-memory)
cluster: "default"
region: ""
heartbeat: 2
internal-address: "" # default: machine address + HTTP port; "{ip}:8000" allowed
public-address: "" # "{node-id}" placeholder
node-param: "node"
secret: "" # relay signature key (default: altruist:security:key)
game:
engine:
step-workers: 1 # cores for room / unit steps: a number or auto
Environment Variables
Any config value can be overridden with an environment variable named after its path: upper-case, __ for : and _ for -:
# altruist:server:http:port
ALTRUIST__SERVER__HTTP__PORT=9090
# altruist:persistence:database:max-pool-size
ALTRUIST__PERSISTENCE__DATABASE__MAX_POOL_SIZE=80
This works for your own sections too: every top-level section of config.yml / config.{environment}.yml is a root. Keep secrets out of the files and set them in the environment:
myapp:
public-base-url: "http://localhost:5173"
email:
provider: log
MYAPP__PUBLIC_BASE_URL=https://play.example.com # myapp:public-base-url
MYAPP__EMAIL__API_KEY=re_... # myapp:email:api-key (not in the file at all)
A _ stays a _ where the YAML already has that key with an underscore. For a root that has no section in the files, list it:
altruist:
config:
env-roots: [secrets] # SECRETS__API_KEY -> secrets:api-key
AppConfigLoader.EnvironmentVariableName("myapp:email:api-key") returns the variable name (MYAPP__EMAIL__API_KEY), e.g. for error messages.
Placeholders
A value in the YAML files may read an environment variable — useful when your deploys already set variables under other names:
altruist:
security:
captcha:
secret-key: "${MYGAME__TURNSTILE__SECRET_KEY}" # empty when unset
email:
provider: "${MYGAME__EMAIL__PROVIDER:-log}" # default when unset or empty
from: "${MYGAME__EMAIL__FROM:-My Game <no-reply@mygame.gg>}"
$${ writes a literal ${. Only values from the YAML files are expanded, and a variable for the key itself (ALTRUIST__SECURITY__CAPTCHA__SECRET_KEY) still wins over the expanded value.
Environment
AltruistEnvironment.Name is the hosting environment: DOTNET_ENVIRONMENT, else ASPNETCORE_ENVIRONMENT, else Production. It picks config.{environment}.yml and decides which safety checks are fatal (the JWT key guard). Use it instead of reading the variables yourself:
if (AltruistEnvironment.IsDevelopment)
await seeder.SeedTestAccountsAsync();
if (AltruistEnvironment.IsProduction && string.IsNullOrEmpty(settings.ApiKey))
throw new InvalidOperationException("Refusing to start: no API key.");
Binding Config to Classes
Use [ConfigurationProperties] to automatically bind a YAML section to a class:
[ConfigurationProperties("myapp:smtp")]
public class SmtpOptions
{
public string Host { get; set; } = "";
public int Port { get; set; } = 587;
public bool EnableSsl { get; set; } = true;
public string From { get; set; } = "";
}
myapp:
smtp:
host: "smtp.example.com"
port: 587
enableSsl: true
from: "noreply@example.com"
The bound class is automatically registered as a singleton in DI and can be injected anywhere.
Conditional Service Registration
Services can be conditionally registered based on config values using [ConditionalOnConfig]:
// Only registered when transport mode is "websocket"
[Service(typeof(ITransport))]
[ConditionalOnConfig("altruist:server:transport:mode", havingValue: "websocket")]
public class WebSocketTransport : ITransport { ... }
// Only registered when the transport section exists
[Service(typeof(IConnectionManager))]
[ConditionalOnConfig("altruist:server:transport")]
public class ConnectionManager : IConnectionManager { ... }
Injecting Config Values
Individual config values can be injected into constructors using [AppConfigValue]:
public class MyService
{
public MyService(
[AppConfigValue("altruist:server:http:port", "8080")] string port,
[AppConfigValue("altruist:server:transport:timeout", "10")] int timeout)
{
// port and timeout are resolved from config with fallback defaults
}
}