Configuration
The Discord bot uses a layered configuration system with TOML files and environment variable overrides, similar to the Heimdall API.
Configuration Files
Configuration files are stored in platform/discord_bot/config/:
config/
├── default.toml # Base configuration
├── development.toml # Development overrides
└── production.toml # Production overrides
The active configuration is determined by the DISCORD_BOT_RUN_MODE environment variable (defaults to development).
Configuration Structure
default.toml
[bot]
# Command prefix for text commands
prefix = "!"
# Discord bot token (set via DISCORD_BOT_TOKEN env var)
token = ""
[api]
# Heimdall API endpoints
graphql_endpoint = "http://localhost:3000/v1/gql"
rest_endpoint = "http://localhost:3000/v1"
websocket_endpoint = "ws://localhost:3000/v1/ws"
# System API key (set via DISCORD_BOT_API_KEY env var)
key = ""
# HTTP server bind settings (for health endpoint)
bind_address = "0.0.0.0"
bind_port = 3006
[sentry]
# Sentry DSN for error tracking (set via DISCORD_BOT_SENTRY_DSN env var)
dsn = ""
# Traces sample rate (0.0 to 1.0)
traces_sample_rate = 0.1
[logging]
# Log level: trace, debug, info, warn, error
level = "info"
# Environment name (development, staging, production)
environment = "development"
production.toml
[api]
graphql_endpoint = "https://api.elcto.com/v1/gql"
rest_endpoint = "https://api.elcto.com/v1"
websocket_endpoint = "wss://api.elcto.com/v1/ws"
[sentry]
traces_sample_rate = 1.0
[logging]
level = "warn"
environment = "production"
Environment Variables
Environment variables override TOML configuration. All variables use the DISCORD_BOT_ prefix with single underscore (_) separator:
| Variable | Config Path | Description |
|---|---|---|
DISCORD_BOT_TOKEN | token | Discord bot token |
DISCORD_BOT_API_KEY | api.key | Heimdall API key |
DISCORD_BOT_API_GRAPHQL_ENDPOINT | api.graphql_endpoint | GraphQL endpoint |
DISCORD_BOT_API_REST_ENDPOINT | api.rest_endpoint | REST endpoint |
DISCORD_BOT_API_WEBSOCKET_ENDPOINT | api.websocket_endpoint | WebSocket endpoint |
DISCORD_BOT_API_BIND_ADDRESS | api.bind_address | HTTP server bind address |
DISCORD_BOT_API_BIND_PORT | api.bind_port | HTTP server bind port |
DISCORD_BOT_SENTRY_DSN | sentry.dsn | Sentry DSN |
DISCORD_BOT_SENTRY_TRACES_SAMPLE_RATE | sentry.traces_sample_rate | Sentry traces sample rate |
DISCORD_BOT_LOGGING_LEVEL | logging.level | Log level |
DISCORD_BOT_RUN_MODE | - | Active configuration mode |
Loading Configuration
The configuration is loaded in this order (later sources override earlier):
config/default.toml(base configuration)config/{RUN_MODE}.toml(environment-specific)- Environment variables
use config::{Config, ConfigError, Environment, File};
impl Settings {
pub fn load() -> Result<Self, ConfigError> {
let run_mode = std::env::var("DISCORD_BOT_RUN_MODE")
.unwrap_or_else(|_| "development".into());
let s = Config::builder()
.add_source(File::with_name("config/default"))
.add_source(
File::with_name(&format!("config/{}", run_mode))
.required(false)
)
.add_source(
Environment::with_prefix("DISCORD_BOT")
.prefix_separator("_")
.separator("_")
)
.build()?;
s.try_deserialize()
}
}
Settings Types
#[derive(Debug, Deserialize, Clone)]
pub struct Settings {
pub bot: BotSettings,
pub token: String,
pub api: ApiSettings,
pub sentry: SentrySettings,
pub logging: LoggingSettings,
pub environment: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct BotSettings {
pub prefix: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ApiSettings {
pub graphql_endpoint: String,
pub rest_endpoint: String,
pub websocket_endpoint: String,
pub key: String,
pub bind_address: String,
pub bind_port: u16,
}
#[derive(Debug, Deserialize, Clone)]
pub struct SentrySettings {
pub dsn: String,
pub traces_sample_rate: f32,
}
#[derive(Debug, Deserialize, Clone)]
pub struct LoggingSettings {
pub level: String,
}
Accessing Configuration
Configuration is stored in Serenity's TypeMap for access from command handlers:
use serenity::prelude::TypeMapKey;
use std::sync::Arc;
pub struct SettingsKey;
impl TypeMapKey for SettingsKey {
type Value = Arc<Settings>;
}
// In a command handler:
async fn some_command(ctx: &Context, msg: &Message) -> CommandResult {
let data = ctx.data.read().await;
let settings = data.get::<SettingsKey>()
.expect("Settings not in TypeMap");
// Use settings...
Ok(())
}
API Key Auto-Generation
The Discord bot's API key is automatically generated when the Heimdall API starts for the first time. The key is created with:
- ID:
discord-bot-key - Role:
role_system(full access via*:*wildcard) - Description: "API key for the Smutje Discord bot"
First Launch Output
When the API starts for the first time, it outputs the generated key:
═══════════════════════════════════════════════════════════
🔑 Discord Bot API Key (platform/discord_bot/.env)
═══════════════════════════════════════════════════════════
DISCORD_BOT_API_KEY=hm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
═══════════════════════════════════════════════════════════
Copy this value to your platform/discord_bot/.env file.
If You Missed the Key
If you missed the output, you can regenerate it by:
- Delete the key from the database:
DELETE FROM "ApiKey" WHERE id = 'discord-bot-key'; - Restart the API - it will generate a new key
Or create a new key manually via the API/GraphQL.
Security Notes
- Never commit tokens - Always use environment variables for secrets
- Use
.envfiles - For local development, use.envfiles (gitignored) - Rotate tokens - Regenerate tokens if they may have been exposed
- System API keys - Cannot be deleted via the API for safety (only manually via database)