Quand j’étais jeune le débat était: programmation spaghetti vs programmation structurée. Ça parait irréel mais quand on a 1ko de mémoire et 8ko de rom l’art du jonglage est plus prisé que l’art de la structure. Aujourd’hui le débat est programmation procédurale vs programmation fonctionnelle pure. Ce ne sera plus un débat dans quelques dizaines d’années et on se demandera comme les gens ne voyait pas cette évidence.
On vit la même chose pour les distributions: ça a commencé par pas grand chose de plus que le référencement de fichiers tar pour devenir de plus en plus conceptuel afin d’apporter de la cohérence, de la sécurité et de l’ergonomie. Un saut majeur est réalisé par NixOs. Le même saut que le passage de la programmation procédurale à la programmation fonctionnelle pure (qui est au cœur de NixOs).
NixOs a une représentation conceptuelle de la distribution: une installation est le résultat d’une fonction pure qui prend en argument la description de ce que vous désirez avoir. En pratique vous décrivez ce que vous désirez avoir dans des fichiers dans /etc/nixos et nixos l’installe.
Par exemple:
- Pas de “apt-get install thunderbird”, vous écrivez “thunderbird” dans une liste dans un des fichiers dans /etc/nixos. Donc vous n’avez plus à mémoriser ce que vous avez installé ou configuré, tout est dans /etc/nixos. Gros SOULAGEMENT!
- Même si vous installez apache2, tout se fait dans /etc/nixos, mais vous pouvez reprendre des grandes parties de configuration apache, on peut voir NixOs comme, entre autres, une surcouche à toutes les configurations se trouvant dans etc.
- Fini les problèmes de configuration du type “il faut mettre ça avant ça” ou “ce flag est à true donc on ne peut pas définir ce paramètre”: Le langage Nix est mathématique et les incohérences sont détectées ou impossibles.
- NixOs fournit une archive de taille paramétrable des installations antérieures, donc si vous avez cassé quelque chose il suffit de booter sur une ancienne version (le menu du boot est mis à jour).
- D’un ordinateur on peut en configurer et gérer d’autres (ou des conteneurs), autant que l’on veut. Fini l’IP de la gateway ou le nom du réseau local dupliqué: vous pouvez l’écrire à un endroit unique.
- Dupliquer une installation est super simple puisqu’elle est entièrement décrite.
- Quand le langage de configuration d’un logiciel est archaïque (comme l’excellent rsnapshot), vous pouvez utiliser le langage Nix pour contourner les problèmes.
- NixOs compartimente les paquets, cela évite les problèmes d’incompatibilité et permet d’installer plusieurs versions (par exemple plusieurs PosgreSQL ou plusieurs apache2). Fini les “je veux bien t’installer ceci mais il faudra déinstaller cela”. Il permet quand même de partager des fichiers identiques entre paquets.
- Le côté très structuré favorise la sécurité. Par exemple créer un paquet est une description : url des sources, dépendances etc. Les possibilités d’introduire des vulnérabilités sont réduites. Par exemple pour apache2, le mainteneur n’a de liberté que dans la production de ce fichier nix (accessible par le lien “Source” sur https://search.nixos.org/packages. Vous pouvez facilement vérifier par vous même que le mainteneur n’a pas introduit de vulnérabilité.
Quelques concepts
Un fichier .nix
Votre répertoire /etc/nixos (le chemin est un standard mais il peut être ailleur) sera peuplé de fichiers “.nix” que vous organisez comme vous le souhaitez. Un “.nix” est simplement un bout de code en langage Nix même si souvent ça ressemble à un fichier de configuration. Et comme Nix est un langage fonctionnel pure un bout de code nix est toujours une valeur (un nombre, un ensemble d’attributs, une fonction etc). Cela rend la description de vos machines très libre. Très souvent un .nix est une fonction, par exemple:
{ host }: { boot_partition="BOOT_RESC"; root_partition="rescue"; pruneGenerations = host.zone.pruneGenerations.enduser; }
La partie { host }: indique que c’est une fonction à un argument (host).
Le reste “{ …. }” est la valeur retournée par la fonction, ici un ensemble d’attributs.
Mais ce .nix est aussi valide “{x,y}:x+y”, c’est la fonction addition. Cette fonction prend en argument une ensemble d’attributs et retourne un nombre.
Mais on peut définir une fonction prenant directement les nombres: “x: y: x+y”. Ça se lit: c’est la fonction qui prend x en argument et qui retourne la fonction qui prend y en argument et qui retourne x+y. Par exemple:
let
add_v1= {x,y}:x+y;
add_v2= x:y: x+y;
in
(add_v1 {1,2}) + (add_v2 3 4)
Est une valeur qui vaut 10. (add_v2 3) est une fonction, elle ajoute 3 à son argument. Par exemple:
let
add_v2= x:y: x+y;
f = add_v2 3;
in
f 4
Est une valeur qui vaut 7.
/etc/nixos/flake.nix
Quand on utilise Flakes (c’est optionnel mais devient un standard), le fichier flake.nix (nom conventionnel) contient la description des différents ordinateurs. Pour que NixOs fournisse une fonction pure (sans effet de bord) alors que les paquets peuvent changer, il faut fournir en argument les paramètres décrivant “de quoi on part”. C’est pourquoi un flake.nix comporte deux parties: inputs et outputs. C’est à dire : de quoi ont part et ce que l’on obtient. Donc vous dites que vous partez de telle version de NixOs (inputs) et dans outputs vous décrivez les machines que vous désirez.
Dans ce petit exemple on part de la distribution instable de NixOs et de “nur” puis ont décrit les machines (une seule ici). “mysystem” est le nom de la machine, elle est décrite dans “configuration.nix” et pourra utiliser “inputs” comme argument dans ses fonctions.
Pour que ce flake.nix soit une fonction pure il manque une chose: en effet la distribution change et produirait donc des installations différentes. C’est pourquoi un /etc/nixos/flake.lock est créé automatiquement à la première interprétation pour fixer la version. Donc si vous recopier votre /etc/nixos sur une autre machine cela produira la même installation aux mêmes versions de logiciels.
Le principe de flake.nix sert aussi à tout ce qui “produit à partir de”. Par exemple on peut utiliser un flake.nix pour produire un package.
Organisation
Nous avons vu que nous pouvons organiser la configuration de manière très libre. Voici une base d’organisation que je trouve confortable et puissante. Imaginons la gestion de toutes les machines de GreenPeace:
- zone/<nom-zone>/host/<nom-machine>: le répertoire de configuration d’une machine. On découpe en zones, par exemple “paris” pour le siège à Paris.
- lib/ : vos .nix partagés entre machines
- store/ : vos fichiers non .nix partagés
Chaque zone pourra aussi avoir un dossier lib, store etc si il y a des spécificités.
Vous avez aussi besoin de paramètres, au niveau du host, de la zone et globalement. Exemple de paramètres: l’email de l’administrateur principal ou des IPs de DNS. C’est une très bonne chose de séparer les paramètres du reste car ils peuvent être utilisés par vos librairies. On utilisera le même découpage:
- organisation.nix : le fichier principal de configuration de greenpeace (comme le nom, l’email de l’administrateur principal etc).
- zone/<nom-zone>/zone.nix : les paramètres de la zone.
- zone/<nom-zone>/host/<nom-machine>/host.nix : les paramètres de la machine.
Pour éviter que les librairies aient trois arguments (orga,zone,host) et pour rendre le système plus souple je trouve préférable que chaque host.nix reprenne les paramètres de la zone (par une variable “zone”) et la zone de l’organisation. Ainsi “host” contient tous les paramètres.