Dynamic parameters

This document explains how the configuration of the platform works, the two mechanisms in play, their precedence, and the pitfalls to be aware of.


Overview: two separate systems

Gitrust uses two independent configuration systems:

Système Source Modifiable à chaud Visible dans l'UI admin
Configuration statique Fichier .env + variables d'environnement Non (restart requis) Non
Configuration dynamique Table app_settings (PostgreSQL) Oui (effet immédiat) Oui (/admin/settings)

There is a hybrid case for OAuth (priority DB, .env in fallback).


1. Static configuration (.env)

Principle

Environment variables are loaded once at startup by dotenvy::dotenv() and stored in immutable Rust structs. They are never read again after boot.

Reference files

Fichier Rôle
.env.example Template documenté avec toutes les variables
.env Configuration locale (gitignore)
.env.production Configuration de déploiement
.env.test Configuration de tests

Loading structures

Struct Variables concernées
GitrustConfig GIT_REPOS_BASE_PATH, SSH_PORT, SSH_LISTEN_ADDR, SSH_HOST_KEY_PATH, CI_*, IMPORT_*
AppConfig APP_NAME, APP_THEME, APP_DEBUG
AuthConfig JWT_SECRET, JWT_EXPIRATION_MINUTES, SESSION_*, RATE_LIMIT_*, COOKIE_*
EmailConfig SMTP_*, EMAIL_*, IMAP_*

Behavior

  • Loaded into RustwardenApp::build() at startup
  • Stored in Arc<Config> shared via axum state
  • Changing the .env without restarting has no effect
  • These variables are NOT displayed in the admin interface

List of static variables (non-exhaustive)

DATABASE_URL, SERVER_HOST, SERVER_PORT, RUST_LOG,
SSH_HOST_KEY_PATH, SSH_PORT, SSH_LISTEN_ADDR, SSH_PUBLIC_HOST,
JWT_SECRET, JWT_EXPIRATION_MINUTES, JWT_ISSUER,
REFRESH_TOKEN_EXPIRATION_DAYS, REMEMBER_ME_EXPIRATION_DAYS,
SESSION_TIMEOUT_MINUTES, SESSION_BACKEND,
RATE_LIMIT_LOGIN_PER_MINUTE, RATE_LIMIT_REFRESH_PER_MINUTE,
RATE_LIMIT_GENERAL_PER_MINUTE,
APP_DEBUG, COOKIE_SECURE, COOKIE_SAME_SITE,
ADMIN_USERNAME, ADMIN_EMAIL, ADMIN_PASSWORD,
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM,
EMAIL_BASE_URL, EMAIL_QUEUE_*,
APP_NAME, APP_THEME, DEFAULT_LOCALE,
GIT_REPOS_BASE_PATH,
CI_ENABLED, CI_MAX_CONCURRENT, CI_REMOTE_HOST, CI_WORKSPACE_PATH

2. Dynamic configuration (app_settings table)

Principle

Dynamic settings are stored in the PostgreSQL database in the app_settings table and read on each request via AppSettingsService. They can be modified on-the-fly by an administrator from the /admin/settings interface.

Table diagram

CREATE TABLE app_settings (
    id          UUID PRIMARY KEY,
    key         VARCHAR(100) UNIQUE NOT NULL,
    value       TEXT NOT NULL,
    description TEXT,
    updated_by  UUID REFERENCES users(id) ON DELETE SET NULL,
    updated_at  TIMESTAMPTZ NOT NULL
);

Initialization at startup

At boot, AppSettingsService::initialize_default_settings() creates the default settings only if they do not yet exist in base:

let existing = Self::get_setting(db, key).await?;
if existing.is_none() {
    // ... insert valeur par défaut
}

Critical consequence: once a setting exists in base (created on first startup or modified via the UI), it is never overwritten by a subsequent restart.

List of dynamic settings and their default values

Clé Défaut Description
app_domain env::var("APP_DOMAIN") ou "localhost" Domaine de l'application
allow_registration false Autoriser l'inscription publique
validation_email_required true Exiger la validation email
audit_log_level INFO Niveau de log d'audit
audit_log_actions ["create","update","delete","reset_password"] Actions auditées
password_min_length 8 Longueur minimum mot de passe
password_require_uppercase false Exiger des majuscules
password_require_lowercase false Exiger des minuscules
password_require_digits false Exiger des chiffres
password_require_special false Exiger des caractères spéciaux
password_change_require_email true Confirmation email pour changement mdp
password_expiration_enabled false Activer l'expiration des mots de passe
password_expiration_days 0 Durée d'expiration (jours)
password_expiration_alert_enabled false Alerte email avant expiration
password_expiration_alert_days_before 7 Jours avant expiration pour alerter
oauth_enabled false Activer OAuth/SSO
oauth_google_enabled false Activer Google OAuth
oauth_github_enabled false Activer GitHub OAuth
oauth_discord_enabled false Activer Discord OAuth
oauth_microsoft_enabled false Activer Microsoft OAuth
oauth_redirect_base_url "" URL de base pour les callbacks OAuth
oauth_auto_register true Créer un compte auto au premier login OAuth
oauth_link_existing_account true Lier un compte OAuth à un utilisateur existant
oauth_{provider}_client_id "" Client ID du provider OAuth
oauth_{provider}_client_secret "" Client secret (chiffré AES-256-GCM en base)
oauth_microsoft_tenant "" Tenant Azure AD

3. Hybrid case: OAuth

OAuth is the only subsystem that combines both sources. The logic in OAuthConfig::load():

Pour chaque réglage OAuth :
  1. Chercher dans app_settings (DB)
     → si trouvé et non vide : utiliser cette valeur
  2. Sinon : fallback sur la variable d'environnement
     → si absente : utiliser la valeur par défaut codée en dur

Important: OAuthConfig::load() is called at startup. The result is stored in an Arc and is not replayed dynamically. SO :

  • Changing an OAuth setting via the admin UI requires a restart for OAuthConfig to be reloaded
  • The .env is only a fallback if the DB has no value

4. Precedence and restart behavior

Static settings (.env only)

Action Effet immédiat Après restart
Modifier .env Non Oui

Dynamic settings (DB only)

Action Effet immédiat Après restart
Modifier via UI admin Oui Oui (valeur en DB persiste)
Modifier le .env Aucun effet Aucun effet

Hybrid Settings (OAuth)

Action Effet immédiat Après restart
Modifier via UI admin Non (OAuthConfig est en Arc) Oui (DB prime sur .env)
Modifier .env Non Seulement si aucune valeur en DB

5. Main pitfall: ghost .env variables

Certain variables present in .env.example give the illusion of configuring settings which are in reality managed by the database:

Variable .env Réglage DB correspondant La variable .env est-elle lue ?
ALLOW_REGISTRATION=true allow_registration Non — valeur par défaut codée en dur ("false")
EMAIL_VALIDATION_REQUIRED=true validation_email_required Non — valeur par défaut codée en dur ("true")
OAUTH_ENABLED=true oauth_enabled Oui, mais seulement en fallback si la DB n'a pas de valeur

For ALLOW_REGISTRATION and EMAIL_VALIDATION_REQUIRED:

  • The .env variable is documented in .env.example but is never accessed by initialize_default_settings()
  • The initial value is hardcoded in the service
  • Only the value in the database (modifiable via /admin/settings) is authentic

For app_domain, this is the only dynamic setting that reads .env as the initial seed.


6. Decision diagram

                    ┌─────────────────────────────┐
                    │  Quel type de réglage ?      │
                    └──────────┬──────────────────-┘
              ┌────────────────┼────────────────┐
              ▼                ▼                ▼
     Infrastructure       Fonctionnel        OAuth
     (port, JWT, SMTP)    (inscription,      (providers,
                          mots de passe)      secrets)
              │                │                │
              ▼                ▼                ▼
         .env seul        DB seule         DB + fallback .env
              │                │                │
              ▼                ▼                ▼
      Restart requis    Effet immédiat    Restart requis
                        via /admin/       (OAuthConfig en Arc)
                        settings

7. Guide for administrators

Change an infrastructure setting

Edit .env (or .env.production) then restart the service:

# Éditer
vim .env

# Redémarrer
systemctl restart gitrust    # production
# ou Ctrl+C + cargo run      # dev

Change a functional setting

Log in as administrator, go to /admin/settings, modify the value, and click "Save". The effect is immediate, no restart necessary.

Change an OAuth setting

Via /admin/settings, modify the OAuth values ​​then restart the service (the OAuth config is loaded into memory at boot and is not reread dynamically).

Check the actual value of a setting

For dynamic settings, query the database directly:

SELECT key, value, updated_at, updated_by
FROM app_settings
WHERE key = 'allow_registration';

For static settings, check the logs at startup (RUST_LOG=debug).


8. Guide for developers

Add a new dynamic setting

  1. Add the (key, default_value, description) tuple in AppSettingsService::initialize_default_settings():

    ("ma_nouvelle_cle", "valeur_par_defaut", Some("Description pour l'UI admin")),
    

  2. Read the value in the code via the service:

    // Boolean avec défaut
    let enabled = AppSettingsService::get_bool(&db, "ma_nouvelle_cle", false).await?;
    
    // String
    let val = AppSettingsService::get_setting(&db, "ma_nouvelle_cle").await?;
    

  3. The value will automatically appear in /admin/settings.

Add a new static setting

  1. Add variable in .env.example with full documentation
  2. Read the variable in the corresponding Config struct via env::var()
  3. The value will not be visible in the admin interface

Add hybrid setting (DB + fallback .env)

Follow the OAuth pattern in crates/rustwarden-core/src/config/oauth.rs:

let ma_valeur = match db_val("ma_cle").await {
    Some(v) => v,
    None => env::var("MA_CLE").unwrap_or_else(|_| "defaut".to_string()),
};