Aller au contenu

Modules système

Le module DNF principal.

Met en place :

  • systemd-boot et le noyau LTS (dernier mainline via enableLatestKernel);
  • JetBrains Mono Nerd Font, avec kmscon pour le TTY;
  • optimisation nightly du store, GC hebdomadaire avec rétention de 30 jours (keep-last-N via gcKeepGenerations sur les hôtes au disque contraint);
  • le pare-feu et SSH à clé uniquement;
  • le terminfo xterm-ghostty de Ghostty, pour que chaque hôte accepte le terminal;
  • la politique de veille (disableSuspend pour les serveurs);
  • les règles polkit laissant wheel arrêter/redémarrer;
  • un groupe/utilisateur common-files partagé pour les dossiers médias inter-services.
  • enable bool Système principal du framework Darkone (activé par défaut)
  • enableSystemdBoot bool Activer le chargeur de démarrage par défaut
  • enableLatestKernel bool Utiliser le dernier noyau mainline (linuxPackages_latest) au lieu du LTS
  • enableFstrim bool Optimisation SSD avec fstrim
  • enableFirewall bool Activer le pare-feu (true par défaut)
  • enableSops bool Activer le module dnf sops (true par défaut)
  • enableFlatpak bool Activer la configuration DNF flatpak (uniquement pour les environnements graphiques)
  • enableKmscon bool Activer la nerd font pour le TTY
  • gcKeepGenerations int Si > 0, limiter le profil système aux N dernières générations avant chaque collecte de déchets, et collecter quotidiennement sans rétention basée sur l’âge. Pour les hôtes avec une partition racine étroite (passerelles).
  • enableBoost bool Activer l’overclocking, corectl
  • enableAutoSuspend bool Activer la veille automatique (pour les portables, ignoré si disableSuspend est true)
  • disableSuspend bool Désactivation complète de la veille (pour les serveurs)
  • enableCommonFilesUser bool Activer l’utilisateur common-files utilisé par plusieurs services
darkone.system.core = {
enable = true;
enableSystemdBoot = true;
enableLatestKernel = false;
enableFstrim = true;
enableFirewall = true;
enableSops = true;
enableFlatpak = true;
enableKmscon = true;
gcKeepGenerations = 5;
enableBoost = false;
enableAutoSuspend = false;
disableSuspend = false;
enableCommonFilesUser = false;
};

Estampille la version du framework sur l’hôte : /etc/dnf-release et l’étiquette d’amorçage.

Répond à la question « quelle version de DNF fait tourner cette machine ? » sans journal de déploiement, sur une flotte où les hôtes sont mis à jour un par un et où une restauration peut laisser un nœud à plusieurs versions en arrière.


Documentation (man) pour les développeurs et les administrateurs.

  • enable bool Activer la documentation technique utile (man, nixos)
darkone.system.documentation.enable = false;

Améliorations de la configuration matérielle.

Tous les firmwares sont chargés (hardware.enableAllFirmware), sauf sur une machine virtuelle : just detect-hw la détecte (systemd-detect-virt --vm) et définit darkone.system.hardware.virtual dans hardware/detected-hardware.nix. Un invité permet aussi à Zed de tourner sur son GPU émulé (ZED_ALLOW_EMULATED_GPU).

  • enable bool Activer les optimisations matérielles
  • enableIntel bool Activer les mises à jour de microcode intel
  • enableAmd bool Activer les mises à jour de microcode amd
  • virtual bool Machine virtuelle, aucun firmware chargé. Défini par just detect-hw
darkone.system.hardware = {
enable = false;
enableIntel = false;
enableAmd = false;
virtual = false;
};

Configuration de la localisation et de la langue.

  • enable bool Active l’internationalisation avec la configuration de la zone réseau par défaut
  • locale strMatching dnfLib.localeRegex Locale réseau, doit respecter la forme xx_YY.UTF-8.
  • timeZone str Fuseau horaire réseau
  • keyboard raw Clavier de la locale : mappage console, disposition xkb et variante xkbVariant.
darkone.system.i18n = {
enable = false;
locale = "fr_FR.UTF-8";
timeZone = "Europe/Paris";
keyboard = if cfg.enable then
dnfLib.keyboardFromLocale cfg.locale
else
{
console = config.console.keyMap;
xkb = config.console.keyMap;
xkbVariant = "";
};
};

Politique de mots de passe LUKS et déverrouillage à distance : mots de passe partagés et par hôte, SSH en initrd.

Complète modules/system/yubikey.nix (emplacements de clés FIDO2) avec la partie mots de passe de la politique LUKS du parc. Chaque volume chiffré dispose exactement de deux mots de passe gérés, ainsi que d’un emplacement de clé par YubiKey enregistré :

KeyslotSecretAudience
sharedsops luks-passphrasel’administrateur du parc. Autorise également les opérations sur les emplacements (luksAddKey / luksKillSlot) pour les unités ci-dessous, et est la clé utilisée par disko pour formater le disque.
per-hostsops luks/<host>/passphrasel’utilisateur de cette machine uniquement, sans donner accès au parc entier.
FIDO2registry usr/secrets/yubikeys.jsonun emplacement par clé, sur chaque hôte chiffré.

Deux mots de passe gérés plutôt qu’un seul, car ils s’adressent à des publics différents ; et parce que la rotation de l’un peut toujours être autorisée par l’autre, le service de synchronisation n’est jamais laissé sans autorisateur (il ajoute le nouvel emplacement avant de supprimer l’ancien).

Il n’existe pas de « mot de passe d’installation » distinct : just install formate le volume via nixos-anywhere --disk-encryption-keys avec le mot de passe partagé, de sorte que l’emplacement de clé de formatage est l’emplacement partagé. Sur le premier hôte (darkone new, just install-here), le mot de passe saisi dans l’assistant devient ce mot de passe.

just luks <host> provisionne un hôte (idempotent, également appelé par just configure) ; sa phase pre s’exécute hors ligne et est appelée par just install avant la compilation. Elle enregistre l’hôte dans le manifeste public usr/secrets/luks.json (committé), stocke le mot de passe par hôte et la clé hôte SSH initrd dans sops, et s’assure que le luks-passphrase partagé existe. just luks <host> show affiche la table des emplacements de clés et vérifie le déverrouillage à distance. Le module assure ensuite la convergence à chaque application :

  • keyslots : un service oneshot synchronise les mots de passe partagé et par hôte dans l’en-tête LUKS2 de chaque volume déclaré par disko. Toute modification d’une valeur dans sops fait tourner l’emplacement correspondant (ancien supprimé, nouveau ajouté). Les opérations sur les emplacements sont autorisées par le mot de passe géré qui déverrouille encore le volume ; l’un d’eux doit donc rester valide. Les emplacements hors de son registre (FIDO2, enrôlements manuels) ne sont jamais modifiés.
  • remote unlock : sshd en initrd sur un port dédié (2222), avec sa propre clé hôte persistante (/var/lib/luks-initrd/), connexion root autorisée avec la clé de déploiement nix (usr/secrets/nix.pub). just unlock <host> répond à l’invite de mot de passe sans intervention humaine (via l’assistant dnf-unlock inclus dans l’initrd), ou interactivement depuis un terminal si aucun mot de passe stocké ne fonctionne. En cas d’échec de la route VPN, il se replie sur l’IP WAN enregistrée dans le manifeste. Les hôtes classiques obtiennent leur IP par DHCP sur les interfaces filaires (un portable en Wi-Fi n’a pas de réseau en initrd et se replie sur le déverrouillage par console) ; les passerelles de zone répliquent leur configuration de production : IP LAN statique sur le pont lan0, DHCP côté WAN, car elles sont elles-mêmes le serveur DHCP et l’initrd attendrait sinon.
  • enable bool Politique de mots de passe LUKS du parc (inactive tant que just luks <host> n’a pas provisionné l’hôte)
  • sshPort port Port sshd en initrd ; distinct de 22 afin que la clé hôte initrd ne entre jamais en conflit avec celle du système
  • volumes listOf str Volumes LUKS déclarés par la configuration disko de l’hôte.
darkone.system.luks = {
enable = true;
sshPort = 2222;
volumes = [ ];
};

Côté invité MicroVM : hyperviseur, lien routé, partages, clé hôte persistante.


Côté hyperviseur MicroVM : invités, taps routés, isolation.


Côté hyperviseur MicroVM : serveur whisper.cpp pour les invités qui le demandent.


Invités MicroVM (microvm.nix, cloud-hypervisor) sur un hôte hyperviseur DNF.

Un invité est un hôte DNF ordinaire de l’inventaire, marqué de la fonctionnalité microvm-guest:<hypervisor> dans etc/config.yaml. Il garde son IP de zone, ses services et sa supervision ; l’hyperviseur l’exécute et le déploie.

- hostname: "space1"
zone: "cp:1.40" # fixed IP: tap + MAC are derived from it
profile: "ai-space"
features: ["microvm-guest:ms-a2", "monitoring-node"] # monitored like any host
  • Côté invité (microvm/guest.nix) : partages virtiofs sous <stateDir>/<guest>/{persist,var-lib,home}, store de l’hôte en lecture seule, clé de l’hôte sur /persist, lien statique /32 routé par l’hyperviseur.
  • Côté hyperviseur (microvm/host.nix) : un tap routé par invité (pas de pont), tout l’hôte sur networkd avec la passerelle de zone comme résolveur statique, proxy ARP sur le LAN, isolation nftables (table dnf-microvm), <stateDir> fermé aux utilisateurs de l’hyperviseur (0770) et monté nosuid,nodev (fichiers setuid, nœuds de périphérique d’un invité inertes).
  • Parole vers texte (microvm/whisper.nix) : un serveur whisper.cpp sur l’hyperviseur pour les invités avec darkone.system.microvm.guest.whisper (notes vocales OpenClaw), démarré et ouvert pour eux seuls ; whisper.model, whisper.threads le règlent.
  • hypervisor nullOr str Hyperviseur exécutant cet hôte comme invité microVM, null sur machine physique.
  • stateDir str Répertoire de l’hyperviseur contenant un sous-répertoire de partages par invité.
  • guest.vcpu ints.positive Processeurs virtuels de l’invité.
  • guest.mem ints.positive RAM de l’invité, en MiB.
  • guest.whisper bool Utiliser le serveur whisper.cpp de l’hyperviseur (parole vers texte) : démarré et ouvert à cet invité.
  • guestInputRules lines Règles nftables de la chaîne d’entrée dnf-microvm de l’hyperviseur, avant le drop du trafic des invités.
  • whisper.model path Modèle ggml whisper.cpp du serveur de l’hyperviseur, chargé une fois pour tous les invités.
  • whisper.threads ints.positive Threads du serveur whisper.cpp de l’hyperviseur, une transcription à la fois.
darkone.system.microvm = {
hypervisor = dnfLib.getMicrovmHypervisor hosts host;
stateDir = "/var/lib/microvms";
guest.vcpu = 2;
guest.mem = 4096;
guest.whisper = false;
guestInputRules = "";
whisper.model = pkgs.fetchurl {
url = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin";
sha256 = "1fc70f774d38eb169993ac391eea357ef47c88757ef72ee5943879b7e8e2bc69";
};
whisper.threads = 8;
};

Durcissement système selon l’ANSSI BP-028 v2.0 (GNU/Linux). (wip)

Module recommandé, activé dans certains profils hôte ou manuellement selon les besoins. Applique progressivement les recommandations ANSSI selon le niveau choisi et la catégorie de machine. Chaque profil hôte définit son niveau et sa catégorie :

darkone.system.security = {
level = "intermediary"; # minimal | intermediary | reinforced | high
category = "server"; # base | client | server
};

Les règles incompatibles avec l’environnement sont exclues par tag :

darkone.system.security.excludes = [ "needs-jit" "needs-hibernation" ];

Une règle spécifique peut être contournée avec une justification obligatoire :

darkone.system.security.exceptions = {
R9.rationale = "Docker rootless required during development.";
};
  • enable bool Active le module de durcissement ANSSI BP-028 v2.0.
  • level enum [ "minimal" "intermediary" "reinforced" "high" ] Niveau de durcissement ANSSI ciblé.
  • category enum [ "base" "client" "server" ] Catégorie de machine qui sélectionne les sous-ensembles de règles.
  • excludes listOf str Tags qui désactivent des groupes entiers de règles.
  • exceptions attrs Exceptions par règle avec justification obligatoire.
  • adminMailbox str Adresse e-mail de l’administrateur (sudo R39, alias MTA R75).
  • useHardenedKernel bool Utilise linuxPackages_hardened (R60, C1) au lieu du noyau par défaut.
  • allowedActiveUsers listOf str Comptes humains autorisés à s’authentifier (R30). Par défaut, tout utilisateur déclaré dans config.yaml (host.users) plus le compte de déploiement du framework nix. Les comptes désactivés (pas de credential mot de passe) et les comptes de service (pas des utilisateurs normaux) n’ont pas besoin d’être listés.
darkone.system.security = {
enable = false;
level = "minimal";
category = "base";
excludes = [ ];
exceptions = { };
adminMailbox = "admin@exemple.fr";
useHardenedKernel = false;
allowedActiveUsers = [ ];
};

Services DNF : reverse proxy Caddy. Doc : en-tête de ../services.nix.


Services DNF : SSO oauth2-proxy. Doc : en-tête de ../services.nix.


Registre des services DNF : chaque module de service s’y enregistre, et le registre est résolu par rapport à la topologie réseau.

Sous-modules (services/) :

  • caddy.nix : vhosts du reverse proxy, TLS, pare-feu HTTP(S) ;
  • oauth2-proxy.nix : SSO Kanidm des services protégés.

Un vhost Caddy par service résolu, plus le pare-feu HTTP(S) des passerelles de zone et du HCS.

oauth2-proxy devant les services protégés d’une zone, adossé au client OAuth2 internal-service de Kanidm.

  • enable bool Activer le gestionnaire de services DNF pour enregistrer et exposer les services
  • service attrs Configuration globale des services <name>
    • enable bool Activer le proxy du service
    • defaultParams submodule Ces options sont calculées par dnfLib.srv.extractServiceParams
      • domain str Nom de domaine du service
      • title str Nom affiché sur la page d’accueil
      • description str Description du service pour la page d’accueil
      • icon str Nom de l’icône pour la page d’accueil 🡕
      • global bool Le service global est accessible sur Internet
      • noRobots bool Empêcher les robots de scanner si global est vrai
      • fqdn str FQDN calculé du service avant le reverse proxy
      • href str URL calculée du service avant le reverse proxy
      • ip str IP calculée pour contacter le service
    • displayOnHomepage bool Afficher un lien sur la page d’accueil
    • reverseProxy bool Atteint via le reverse proxy de la passerelle de zone (le DNS pointe sur l’IP LAN de la passerelle)
    • uniquePerZone bool Au plus une instance autorisée par zone (validation du générateur)
    • externalAccess bool Service www-zone joignable depuis le LAN via une IP hôte fixe (ex. headscale, turn)
    • persist.dirs listOf str Répertoires persistants du service
    • persist.files listOf str Fichiers persistants du service
    • persist.dbDirs listOf str Répertoires persistants du service avec base(s) de données
    • persist.dbFiles listOf str Fichier(s) de base de données du service
    • persist.varDirs listOf str Fichiers secondaires variables (log, cache, etc.)
    • persist.mediaDirs listOf str Répertoires médias du service (images, vidéos, gros fichiers)
    • proxy.enable bool Créer la configuration virtualHost (false pour les services qui gèrent le leur)
    • proxy.isProtected bool Service protégé par Oauth2
    • proxy.allowedGroups listOf str Groupes Kanidm autorisés sur ce service protégé (vide = tout utilisateur authentifié)
    • proxy.spaceMembersOnly bool Restreindre aux membres de l’espace IA de l’hôte de l’instance (groupe Kanidm space-&lt;host&gt;) ; implique isProtected
    • proxy.isInternal bool Lier le service sur l’interface interne uniquement (pas accessible depuis Internet)
    • proxy.hasReverseProxy bool C’est un reverse proxy (ou une autre configuration virtualhost via extraConfig)
    • proxy.defaultService bool Est le service par défaut
    • proxy.servicePort nullOr port Port interne du service
    • proxy.preExtraConfig lines Configuration caddy virtualHost supplémentaire (préfixe)
    • proxy.extraConfig lines Configuration caddy virtualHost supplémentaire
    • proxy.extraGlobalConfig lines Configuration caddy supplémentaire
    • proxy.scheme str Schéma du service interne (http / https)
  • resolved raw Registre des services résolus.
darkone.system.services = {
enable = false;
service.enable = false;
service.defaultParams.domain = "";
service.defaultParams.title = "";
service.defaultParams.description = "";
service.defaultParams.icon = "";
service.defaultParams.global = false;
service.defaultParams.noRobots = true;
service.defaultParams.fqdn = "";
service.defaultParams.href = "";
service.defaultParams.ip = "";
service.displayOnHomepage = true;
service.reverseProxy = true;
service.uniquePerZone = false;
service.externalAccess = false;
service.persist.dirs = [ ];
service.persist.files = [ ];
service.persist.dbDirs = [ ];
service.persist.dbFiles = [ ];
service.persist.varDirs = [ ];
service.persist.mediaDirs = [ ];
service.proxy.enable = true;
service.proxy.isProtected = false;
service.proxy.allowedGroups = [ ];
service.proxy.spaceMembersOnly = false;
service.proxy.isInternal = false;
service.proxy.hasReverseProxy = true;
service.proxy.defaultService = false;
service.proxy.servicePort = null;
service.proxy.preExtraConfig = "";
service.proxy.extraConfig = "";
service.proxy.extraGlobalConfig = "";
service.proxy.scheme = "https";
resolved = { inherit services hasProtectedServices authHost; };
};

sops DNF, gestion des mots de passe et des secrets.

Câble sops-nix sur usr/secrets/secrets.yaml et le déverrouille avec la clé SSH hôte (ssh_host_ed25519_key) plus la clé age d’infrastructure dédiée (/etc/sops/age/infra.key). Déclare un secret user/<login>/password-hash par utilisateur hôte, avec neededForUsers = true pour que le hash soit disponible avant la création des comptes utilisateurs.

Rien d’autre n’est matérialisé ici : un secret appartient aux hôtes qui le consomment, déclaré par le module qui le lit.

  • enable bool Activer la configuration automatisée sops pour DNF
  • isolated bool Fichier de secrets propre, sans clé infra, sans hash de mots de passe
darkone.system.sops = {
enable = false;
isolated = false;
};

Service de gestion des répertoires partagés.

  • enable bool Activer les répertoires partagés, créer le répertoire racine (défaut /srv)
  • enableNfs bool Activer les chemins du service NFS (nfs/common, nfs/homes)
  • enableMedias bool Activer les chemins des services média (medias/[videos|music|incomming/…])
  • enableStk bool Activer le chemin de partage des pistes SuperTuxKart (nfs/stk-tracks)
  • root str Répertoire racine des données persistantes (/srv)
  • nfs str Répertoire racine NFS (/srv/nfs)
  • homes str Répertoire des domiciles partagés (/srv/nfs/homes)
  • common str Répertoire commun partagé (/srv/nfs/common lié à ~/Public)
  • stkTracks str Répertoire des pistes SuperTuxKart partagées (/srv/nfs/stk-tracks)
  • medias str Répertoire racine des médias (/srv/medias)
  • music str Répertoire des fichiers musicaux partagés (/srv/medias/music)
  • videos str Répertoire des fichiers vidéo partagés (/srv/medias/videos)
  • incoming str Répertoire des fichiers entrants partagés (/srv/medias/incoming accès en écriture)
  • incomingMusic str Répertoire des fichiers entrants partagés (/srv/medias/incoming/music accès en écriture)
  • incomingVideos str Répertoire des fichiers entrants partagés (/srv/medias/incoming/videos accès en écriture)
darkone.system.srv-dirs = {
enable = cfg.enableNfs || cfg.enableMedias || cfg.enableStk;
enableNfs = false;
enableMedias = false;
enableStk = false;
root = "/srv";
nfs = null;
homes = null;
common = null;
stkTracks = null;
medias = null;
music = null;
videos = null;
incoming = null;
incomingMusic = null;
incomingVideos = null;
};

Mode test autonome pour le NixOS Test Driver.

Quand activé, neutralise les parties irréductiblement externes/exécution afin qu’un nœud puisse être testé dans une VM : headscale/tailscale deviennent des no-op et un certificat TLS fixe peut simuler ACME. Permet également à la configuration propre à workDir (nix.pub, harmonia.pub) d’être ignorée par core/nix-cache. sops reste RÉEL (haute fidélité).

  • standalone bool Mode test autonome — ignore la configuration propre à workDir (nix.pub, harmonia.pub) et neutralise les services externes
  • tlsCert nullOr path Certificat auto-signé (PEM) fourni par le harnais de test pour simuler ACME. Jamais un chemin tests/ intégré dans le framework.
  • tlsKey nullOr path Clé privée (PEM) associée à tlsCert.
darkone.system.testing = {
standalone = false;
tlsCert = null;
tlsKey = null;
};

Authentification forte par YubiKey : PAM U2F à l’échelle du parc + déchiffrement LUKS déclaratif par FIDO2.

Un seul enrôlement physique par (utilisateur, clé) sur l’hôte admin : just yubikey <user> [key] [action] alimente :

  • un registre public usr/secrets/yubikeys.json dans l’espace de travail consommateur : l’identifiant de l’attestation pamu2fcfg, l’id du credential FIDO2 hmac-secret et le sel (inutilisables sans la clé physique, donc committables) ;
  • un secret sops yubikey/<user>/<key>/luks-secret (le mot de passe LUKS dérivé de FIDO2) ainsi que le luks-passphrase partagé (le mot de passe saisi à l’installation disko, requis pour autoriser de nouveaux keyslots).

Le module propage ensuite tout déclarativement (just apply), sans jamais brancher la clé sur les hôtes cibles :

  • connexion / sudo / greeter : pam_u2f comme alternative au mot de passe (sufficient) : toucher la clé connecte, le mot de passe sops reste le repli automatique. Un seul credential est valable sur tout le parc grâce à l’origine fixe (pam://<network.domain>).
  • hôtes chiffrés : un service oneshot auto-enrôle chaque clé déclarée dans chaque en-tête LUKS2 déclaré par disko (keyslot + jeton systemd-fido2, le format exact écrit par systemd-cryptenroll) et retire les révoquées. La clé physique n’est nécessaire qu’au démarrage, pour déverrouiller le disque.
  • enable bool Authentification par YubiKey (défaut : activée si usr/secrets/yubikeys.json existe)
  • origin str Origine/appid pam_u2f fixe : un seul enrôlement est valable sur tout le parc
  • luks.enable bool Déverrouillage FIDO2 des volumes LUKS de l’hôte (inerte sans LUKS disko + clés enrôlées)
darkone.system.yubikey = {
enable = hasRegistry;
origin = "pam://${network.domain}";
luks.enable = true;
};