Paramètres dynamiques

Ce document explique comment fonctionne la configuration de la plateforme, les deux mécanismes en jeu, leur précédence, et les pièges à connaître.


Vue d'ensemble : deux systèmes distincts

Gitrust utilise deux systèmes de configuration indépendants :

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)

Il existe un cas hybride pour OAuth (DB prioritaire, .env en fallback).


1. Configuration statique (.env)

Principe

Les variables d'environnement sont chargées une seule fois au démarrage par dotenvy::dotenv() et stockées dans des structs Rust immuables. Elles ne sont jamais relues après le boot.

Fichiers de référence

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

Structs de chargement

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_*

Comportement

  • Chargées dans RustwardenApp::build() au démarrage
  • Stockées dans des Arc<Config> partagés via l'état axum
  • Modifier le .env sans redémarrer n'a aucun effet
  • Ces variables ne sont PAS affichées dans l'interface admin

Liste des variables statiques (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. Configuration dynamique (table app_settings)

Principe

Les réglages dynamiques sont stockés en base PostgreSQL dans la table app_settings et lus à chaque requête via AppSettingsService. Ils sont modifiables à chaud par un administrateur depuis l'interface /admin/settings.

Schéma de la table

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
);

Initialisation au démarrage

Au boot, AppSettingsService::initialize_default_settings() crée les réglages par défaut uniquement s'ils n'existent pas encore en base :

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

Conséquence critique : une fois qu'un réglage existe en base (créé au premier démarrage ou modifié via l'UI), il n'est jamais écrasé par un redémarrage ultérieur.

Liste des réglages dynamiques et leurs valeurs par défaut

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. Cas hybride : OAuth

OAuth est le seul sous-système qui combine les deux sources. La logique dans 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() est appelé au démarrage. Le résultat est stocké dans un Arc et n'est pas relu dynamiquement. Donc :

  • Modifier un réglage OAuth via l'UI admin nécessite un redémarrage pour que OAuthConfig soit rechargé
  • Le .env n'est qu'un fallback si la DB n'a pas de valeur

4. Précédence et comportement au redémarrage

Réglages statiques (.env uniquement)

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

Réglages dynamiques (DB uniquement)

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

Réglages hybrides (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. Piège principal : les variables .env fantômes

Certaines variables présentes dans .env.example donnent l'illusion de configurer des réglages qui sont en réalité gérés par la base de données :

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

Pour ALLOW_REGISTRATION et EMAIL_VALIDATION_REQUIRED :

  • La variable .env est documentée dans .env.example mais n'est jamais consultée par initialize_default_settings()
  • La valeur initiale est codée en dur dans le service
  • Seule la valeur en base de données (modifiable via /admin/settings) fait foi

Pour app_domain, c'est le seul réglage dynamique qui lit le .env comme seed initial.


6. Diagramme de décision

                    ┌─────────────────────────────┐
                    │  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 pour les administrateurs

Modifier un réglage d'infrastructure

Éditer .env (ou .env.production) puis redémarrer le service :

# Éditer
vim .env

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

Modifier un réglage fonctionnel

Se connecter en tant qu'administrateur, aller dans /admin/settings, modifier la valeur, et cliquer "Save". L'effet est immédiat, aucun redémarrage nécessaire.

Modifier un réglage OAuth

Via /admin/settings, modifier les valeurs OAuth puis redémarrer le service (la config OAuth est chargée en mémoire au boot et n'est pas relue dynamiquement).

Vérifier la valeur effective d'un réglage

Pour les réglages dynamiques, interroger la base directement :

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

Pour les réglages statiques, vérifier les logs au démarrage (RUST_LOG=debug).


8. Guide pour les développeurs

Ajouter un nouveau réglage dynamique

  1. Ajouter le tuple (clé, valeur_défaut, description) dans AppSettingsService::initialize_default_settings() :

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

  2. Lire la valeur dans le code via le 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. La valeur apparaîtra automatiquement dans /admin/settings.

Ajouter un nouveau réglage statique

  1. Ajouter la variable dans .env.example avec documentation complète
  2. Lire la variable dans le struct Config correspondant via env::var()
  3. La valeur ne sera pas visible dans l'interface admin

Ajouter un réglage hybride (DB + fallback .env)

Suivre le pattern OAuth dans 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()),
};