Skip to main content

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:

VariableConfig PathDescription
DISCORD_BOT_TOKENtokenDiscord bot token
DISCORD_BOT_API_KEYapi.keyHeimdall API key
DISCORD_BOT_API_GRAPHQL_ENDPOINTapi.graphql_endpointGraphQL endpoint
DISCORD_BOT_API_REST_ENDPOINTapi.rest_endpointREST endpoint
DISCORD_BOT_API_WEBSOCKET_ENDPOINTapi.websocket_endpointWebSocket endpoint
DISCORD_BOT_API_BIND_ADDRESSapi.bind_addressHTTP server bind address
DISCORD_BOT_API_BIND_PORTapi.bind_portHTTP server bind port
DISCORD_BOT_SENTRY_DSNsentry.dsnSentry DSN
DISCORD_BOT_SENTRY_TRACES_SAMPLE_RATEsentry.traces_sample_rateSentry traces sample rate
DISCORD_BOT_LOGGING_LEVELlogging.levelLog level
DISCORD_BOT_RUN_MODE-Active configuration mode

Loading Configuration

The configuration is loaded in this order (later sources override earlier):

  1. config/default.toml (base configuration)
  2. config/{RUN_MODE}.toml (environment-specific)
  3. 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:

  1. Delete the key from the database: DELETE FROM "ApiKey" WHERE id = 'discord-bot-key';
  2. 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 .env files - For local development, use .env files (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)