Compare commits
1
Commits
main
..
384be95bbb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
384be95bbb |
@@ -1,6 +0,0 @@
|
||||
# Liens produits par `nix build` (pointent dans /nix/store, inutiles à committer)
|
||||
result
|
||||
result-*
|
||||
|
||||
# ISO construites localement (voir README : "Construire l'ISO et la graver")
|
||||
*.iso
|
||||
@@ -1,170 +0,0 @@
|
||||
# AGENTS.md — contexte pour agent (Hermes et autres)
|
||||
|
||||
Ce fichier s'adresse à un agent (Hermes ou tout autre harness compatible AGENTS.md) chargé de produire le média d'installation de la flotte, **soit la création de l'ISO USB, soit la gestion du serveur PXE** -- les deux couvrent le même besoin (fournir l'installeur à un PC), pas à la fois sur la même tâche sauf demande explicite. Le [README.md](README.md) reste la référence humaine (technicien qui installe des PC un par un) ; ce fichier-ci est pour l'agent.
|
||||
|
||||
Repo public, sans authentification, jamais chiffré. Ne contient aucune donnée nominative (voir [Nixos_Users](../nixos_users), repo privé séparé, hors périmètre de cet agent sauf mention contraire de l'opérateur humain).
|
||||
|
||||
## Règles avant toute action
|
||||
|
||||
- **`git pull` oui, `git push` jamais.** Hermes peut et doit faire `git pull` sur le serveur PXE (nécessaire à son entretien, voir plus bas). Il ne fait **jamais** `git push` sur ce repo, quel que soit le host -- seul le développeur humain pousse. Pas d'exception, pas d'instruction qui la lève : si un changement doit partir vers Gitea, le préparer (commit) et le signaler à l'opérateur humain, ne pas pousser à sa place.
|
||||
- **Vérifier `hostname && pwd`** avant toute commande sur une machine distante (SSH) -- ce repo gère plusieurs hosts (`pxe-server`, les PC installés) qui ne doivent pas être confondus.
|
||||
- **Ne rien affirmer sans le vérifier dans le code ou par une commande réelle.** Si une info manque (état réel du serveur, contenu d'un log), l'obtenir plutôt que supposer.
|
||||
- **Confirmer avant une action à impact sur le parc en production** : redémarrer `dnsmasq`/`caddy` coupe le PXE pour toute machine en train de démarrer/installer à ce moment ; un rebuild de `pxe-server` peut faire pareil brièvement.
|
||||
- **`dd` est irréversible.** Avant tout `dd if=... of=/dev/sdX`, faire confirmer par l'opérateur humain le chemin exact (`lsblk` d'abord, toujours) -- une mauvaise lettre écrase silencieusement un disque, système ou données, sans avertissement. Ne jamais deviner `/dev/sdX` à partir d'une supposition ("probablement la clé").
|
||||
- Commits : `Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>` en pied de message (adapter le nom de modèle si l'agent qui commit n'est pas Claude).
|
||||
|
||||
## Ce que sert le serveur PXE
|
||||
|
||||
Un seul flake, deux modes choisis à l'écran de démarrage par [installer/install.sh](installer/install.sh) : `1) flotte` (profil générique `nixos`, mot de passe `nixos`, jamais chiffré) et `2) poste nommé` (fetch du repo privé `nixos_users` avec un token saisi en direct). Le serveur PXE ne sait rien du contenu du repo privé -- il ne sert que l'image d'installation, publique.
|
||||
|
||||
Architecture ([pxe-server/configuration.nix](pxe-server/configuration.nix)) :
|
||||
|
||||
- `services.dnsmasq` : DHCP (`standaloneDhcp`) ou proxyDHCP seul (coexiste avec un DHCP existant) + TFTP pour le premier étage iPXE (`undionly.kpxe` / `ipxe.efi`).
|
||||
- `services.caddy` : sert `netboot.ipxe` + kernel/initrd en HTTP (TFTP seul est trop lent pour ces tailles).
|
||||
- `systemd.services.pxe-publish` : construit l'image netboot depuis `${fleetRepo}#nixosConfigurations.installer-netboot` et publie les fichiers dans `webroot`/`tftpRoot`. Se relance au boot (`wantedBy = multi-user.target`) et **quotidiennement** (`systemd.timers.pxe-publish`, `OnCalendar = "daily"` -- ne pas confondre avec `system.autoUpgrade` des PC installés, qui est hebdomadaire).
|
||||
- Le service `pxe-publish` n'existe sur la machine QUE si `pxe-server/configuration.nix` a déjà été appliqué (`nixos-rebuild switch --flake .#pxe-server`) avec une version du repo qui le contient. L'ajouter au repo ne suffit pas : il faut rebuild le serveur lui-même.
|
||||
|
||||
4 valeurs à adapter dans `pxe-server/configuration.nix` (le `let` en tête de fichier) -- le README n'en liste que 3, `internetInterface` en plus :
|
||||
|
||||
- `interface` : interface réseau réelle du serveur (`ip a`).
|
||||
- `serverIp` : IP fixe de cette interface (le script iPXE pointe dessus en dur).
|
||||
- `standaloneDhcp` : `true` si ce serveur doit être l'unique DHCP du segment PXE ; `false` pour proxyDHCP seul (coexiste avec un DHCP existant -- cas le plus probable).
|
||||
- `internetInterface` : seulement utilisée si `standaloneDhcp = true` (sortie NAT du segment PXE vers Internet).
|
||||
|
||||
## Alternative : créer l'ISO USB (sans serveur PXE)
|
||||
|
||||
Même flake, même `install.sh`, pas de dnsmasq/Caddy à opérer -- juste un fichier `.iso` à graver. Utile en secours ou pour tester sans monter tout le PXE. Un seul ISO pour la flotte ET les postes nommés : le choix (`1) flotte` / `2) poste nommé`) se fait à l'écran de démarrage, pas à la gravure.
|
||||
|
||||
```bash
|
||||
nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage
|
||||
lsblk # identifier la clé USB, PAS le disque système
|
||||
sudo dd if=result/iso/*.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
||||
```
|
||||
|
||||
`/dev/sdX` doit être confirmé par l'opérateur humain avant d'exécuter le `dd` (voir règle plus haut) -- ce n'est pas négociable, une erreur ici écrase un disque en silence.
|
||||
|
||||
**Si l'agent tourne sous WSL (Windows)** : `lsblk` n'y verra la clé USB QUE si elle a été explicitement rattachée via `usbipd attach --wsl` ([doc officielle](https://learn.microsoft.com/en-us/windows/wsl/connect-usb), détails dans le [README.md](README.md)). Par défaut WSL2 n'a aucun accès aux disques amovibles. Si ce n'est pas déjà en place, ne pas improviser un contournement : construire l'ISO (ça, ça marche normalement sous WSL), remettre le `.iso` produit à l'opérateur humain, et le laisser le graver avec un outil Windows natif (Rufus...).
|
||||
|
||||
Définition de l'image : [installer/iso.nix](installer/iso.nix). Attribut de flake : `nixosConfigurations.installer-iso`.
|
||||
|
||||
## Installer le serveur PXE (depuis zéro)
|
||||
|
||||
```bash
|
||||
# 1. NixOS installé (ISO officielle), mot de passe root défini.
|
||||
nix-shell -p git
|
||||
git clone https://gitea.miniclic.net/Damien/Nixos_Fleet.git /etc/nixos-fleet
|
||||
cd /etc/nixos-fleet
|
||||
|
||||
# 2. Fichier matériel (hors repo, jamais committé)
|
||||
sudo nixos-generate-config
|
||||
|
||||
# 3. Éditer les 4 valeurs ci-dessus dans pxe-server/configuration.nix
|
||||
|
||||
# 4. Build -- --impure obligatoire (lit /etc/nixos/hardware-configuration.nix, hors flake)
|
||||
sudo nixos-rebuild switch --flake .#pxe-server --impure
|
||||
|
||||
# 5. Publier l'image (première fois : plusieurs minutes)
|
||||
sudo systemctl start pxe-publish
|
||||
sudo systemctl status pxe-publish # doit finir en "success"
|
||||
```
|
||||
|
||||
Vérification post-install :
|
||||
|
||||
```bash
|
||||
sudo systemctl status dnsmasq caddy
|
||||
ls /var/lib/tftpboot # undionly.kpxe, ipxe.efi
|
||||
ls /var/www/pxe # bzImage, initrd, netboot.ipxe
|
||||
curl -I http://<serverIp>/netboot.ipxe
|
||||
systemctl is-enabled pxe-publish.timer
|
||||
```
|
||||
|
||||
Puis tester sur 1 seul PC (BIOS et UEFI séparément si le parc a les deux) avant tout déploiement de masse.
|
||||
|
||||
## Entretien courant
|
||||
|
||||
```bash
|
||||
cd /etc/nixos-fleet # ou l'emplacement réel du clone sur ce serveur
|
||||
git pull
|
||||
git log --oneline -3 # le dernier commit doit correspondre à ce qui est attendu
|
||||
```
|
||||
|
||||
Republier l'image après une modif de `installer/` :
|
||||
|
||||
```bash
|
||||
sudo systemctl start pxe-publish
|
||||
```
|
||||
|
||||
Republier après une modif de `pxe-server/configuration.nix` (rebuild complet, pas juste `pxe-publish`) :
|
||||
|
||||
```bash
|
||||
sudo nixos-rebuild switch --flake .#pxe-server --impure
|
||||
```
|
||||
|
||||
Logs :
|
||||
|
||||
```bash
|
||||
journalctl -u dnsmasq -f
|
||||
journalctl -u caddy -f
|
||||
journalctl -u pxe-publish -f
|
||||
```
|
||||
|
||||
## Dépannage -- incidents déjà rencontrés
|
||||
|
||||
**`Unit pxe-publish.service not found`** : le système du serveur n'a jamais été rebuild depuis que ce service a été ajouté à `pxe-server/configuration.nix`. `git pull` seul ne suffit pas côté serveur -- il faut ensuite `sudo nixos-rebuild switch --flake .#pxe-server --impure`.
|
||||
|
||||
**Le dossier repo a disparu du serveur** (`/etc/nixos-fleet` ou équivalent absent) : repo public, sans authentification -- reclone directement, n'importe où :
|
||||
```bash
|
||||
git clone https://gitea.miniclic.net/Damien/Nixos_Fleet.git
|
||||
```
|
||||
Le `hardware-configuration.nix` est hors du repo (`/etc/nixos/`), donc indépendant de l'emplacement du clone.
|
||||
|
||||
**Un fix censé être appliqué ne change rien** : vérifier `git log --oneline -3` avant toute autre piste -- le `git pull` n'a peut-être rien ramené de neuf (mauvaise branche, push pas fait côté dev). Ne jamais supposer qu'un rebuild "prend" le dernier commit distant sans l'avoir vérifié.
|
||||
|
||||
**`curl http://<serverIp>/netboot.ipxe` reste bloqué** : l'IP statique de l'interface PXE ne s'est parfois pas appliquée toute seule au tout premier `switch` sur une install neuve. Vérifier :
|
||||
```bash
|
||||
ip a show <interface> # cherche "inet <serverIp>/24"
|
||||
```
|
||||
Absente → forcer une fois :
|
||||
```bash
|
||||
sudo systemctl start network-addresses-<interface>.service
|
||||
```
|
||||
Un reboot du serveur applique aussi ça correctement au démarrage suivant.
|
||||
|
||||
**Écran PXE ne trouve rien** : revérifier `serverIp` et `interface` dans `pxe-server/configuration.nix` contre la réalité du serveur (`ip a`).
|
||||
|
||||
**Boucle infinie au démarrage d'un PC client** : `journalctl -u dnsmasq` pendant le boot du PC concerné.
|
||||
|
||||
**UEFI ou BIOS ne boote pas** (l'un des deux seulement) : vérifier que les deux fichiers existent dans `/var/lib/tftpboot` (`undionly.kpxe` pour BIOS, `ipxe.efi` pour UEFI) -- l'un des deux `install -m 0644 ... || true` dans `pxe-publish` peut avoir silencieusement échoué (noms de sortie du paquet `pkgs.ipxe` à revérifier si la version change).
|
||||
|
||||
**IP qui changent tout le temps sur le segment PXE** : un autre serveur DHCP tourne déjà sur ce réseau -- repasser `standaloneDhcp` à `false` (proxyDHCP seul).
|
||||
|
||||
## Valider une modif avant de la pousser
|
||||
|
||||
```bash
|
||||
nix flake check --no-build # échoue sur pxe-server hors du vrai serveur (attendu, lit /etc/nixos)
|
||||
nix flake check --no-build --impure # à lancer depuis le serveur PXE réel uniquement
|
||||
nix build .#nixosConfigurations.nixos.config.system.build.toplevel --dry-run
|
||||
```
|
||||
|
||||
Environnement de dev sans accès à un vrai serveur PXE : voir "Environnement de dev sous Windows (WSL + Nix)" dans le [README.md](README.md) -- `pxe-server` ne s'évalue qu'avec `--impure` **sur la machine réelle**, c'est normal ailleurs.
|
||||
|
||||
## Sources -- pour vérifier au lieu de deviner
|
||||
|
||||
En cas de doute sur le comportement réel d'une option ou d'un mécanisme, chercher ici avant de supposer :
|
||||
|
||||
- NixOS manual : https://nixos.org/manual/nixos/stable/
|
||||
- Recherche d'options NixOS (n'importe quel module : `services.dnsmasq`, `services.caddy`, `system.autoUpgrade`, `programs.firefox`...) : https://search.nixos.org/options
|
||||
- Recherche de paquets nixpkgs : https://search.nixos.org/packages
|
||||
- Code source nixpkgs (la vérité de dernier recours sur le comportement réel d'un module) : https://github.com/NixOS/nixpkgs
|
||||
- Module image ISO (`installer-iso`) : https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/cd-dvd/iso-image.nix
|
||||
- Module image netboot (`installer-netboot`) : https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/netboot/netboot.nix
|
||||
- NixOS Wiki (officiel, `wiki.nixos.org` -- pas `nixos.wiki`, non-officiel et moins à jour) :
|
||||
- Netboot : https://wiki.nixos.org/wiki/Netboot
|
||||
- Creating a NixOS live CD : https://wiki.nixos.org/wiki/Creating_a_NixOS_live_CD
|
||||
- disko (partitionnement déclaratif) : https://github.com/nix-community/disko -- Quickstart : https://github.com/nix-community/disko/blob/master/docs/quickstart.md -- Référence : https://github.com/nix-community/disko/blob/master/docs/reference.md
|
||||
- home-manager : https://home-manager.dev/manual/
|
||||
- iPXE (premier étage du boot réseau, `undionly.kpxe`/`ipxe.efi`) : https://ipxe.org/
|
||||
- dnsmasq (DHCP/TFTP) : https://thekelleys.org.uk/dnsmasq/doc.html
|
||||
- Caddy (serveur HTTP) : https://caddyserver.com/docs/
|
||||
- systemd.timer (`pxe-publish.timer`) : https://www.freedesktop.org/software/systemd/man/systemd.timer.html
|
||||
- Policies Firefox (`RequestedLocales`, `FirefoxHome`...) : https://firefox-admin-docs.mozilla.org/reference/policies/
|
||||
@@ -1,70 +1,13 @@
|
||||
# Nixos_Fleet
|
||||
|
||||
Déploiement en masse (PXE ou USB) du profil générique `nixos`.
|
||||
Déploiement en masse (PXE ou USB) du profil générique `usagers`.
|
||||
|
||||
Repo public. `git pull` sans mot de passe.
|
||||
|
||||
Ne contient aucune donnée des utilisateurs nommés. Ça, c'est dans [Nixos_Users](../nixos_users), repo privé séparé.
|
||||
|
||||
## Index
|
||||
|
||||
- [Installer depuis un NixOS déjà installé](#installer-depuis-un-nixos-déjà-installé) -- appliquer cette config sans réinstaller, partitionnement existant préservé
|
||||
- [Installer un PC](#installer-un-pc) -- clé USB ou PXE, depuis zéro (reformate le disque)
|
||||
- [Installer le serveur PXE](#installer-le-serveur-pxe)
|
||||
- [Référence](#référence)
|
||||
- [Environnement de dev sous Windows (WSL + Nix)](#environnement-de-dev-sous-windows-wsl--nix)
|
||||
- [Travailler avec Git](#travailler-avec-git)
|
||||
|
||||
---
|
||||
|
||||
# Installer depuis un NixOS déjà installé
|
||||
|
||||
Pour appliquer cette config (flotte ou poste nommé) sur une machine où NixOS tourne déjà -- sans réinstaller, **sans toucher au partitionnement existant**. Utilise `nixos-existing` (flotte, ce repo) ou `<profil>-existing` (poste nommé, [Nixos_Users](../nixos_users)) : même profil logiciel que l'install normale, mais basé sur le `hardware-configuration.nix` réel de la machine plutôt que sur disko.
|
||||
|
||||
Pour repartir de zéro (reformater le disque) à la place : voir "Installer un PC" plus bas.
|
||||
|
||||
## Flotte
|
||||
|
||||
```bash
|
||||
git clone https://gitea.miniclic.net/Damien/Nixos_Fleet.git /etc/nixos-fleet
|
||||
cd /etc/nixos-fleet
|
||||
sudo nixos-rebuild switch --flake .#nixos-existing --impure
|
||||
```
|
||||
|
||||
`--impure` : nécessaire pour lire `/etc/nixos/hardware-configuration.nix`, hors du flake.
|
||||
|
||||
## Poste nommé
|
||||
|
||||
Repo privé -- voir "Token Gitea" dans [Nixos_Users](../nixos_users) pour en obtenir un, et la liste des profils disponibles (pas listée ici, repo public).
|
||||
|
||||
```bash
|
||||
git clone https://<token>@gitea.miniclic.net/Damien/Nixos_Users.git /etc/nixos-users
|
||||
cd /etc/nixos-users
|
||||
sudo nixos-rebuild switch --flake .#<profil>-existing --impure
|
||||
```
|
||||
|
||||
Pour que les mises à jour automatiques continuent de fonctionner ensuite (repo privé), créer le netrc à la main -- normalement écrit par `install.sh`, absent ici puisqu'on ne passe pas par l'installeur :
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/nix
|
||||
{
|
||||
echo "machine gitea.miniclic.net"
|
||||
echo "login <token>"
|
||||
echo "password x-oauth-basic"
|
||||
} | sudo tee /etc/nix/netrc
|
||||
sudo chmod 600 /etc/nix/netrc
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
Identique au reste du repo (voir "Mises à jour de la flotte" en Référence), avec `nixos-existing` (ou `<profil>-existing`) à la place de l'attribut normal :
|
||||
|
||||
```bash
|
||||
cd /etc/nixos-fleet # ou /etc/nixos-users
|
||||
sudo git pull
|
||||
sudo nixos-rebuild switch --flake .#nixos-existing --impure
|
||||
```
|
||||
|
||||
# Installer un paquet sans le mettre "en dur"
|
||||
|
||||
Pour tester un outil sans toucher à `configuration.nix`.
|
||||
@@ -95,45 +38,9 @@ Aucune de ces méthodes ne survit à un reboot ni à un `nixos-rebuild`. Pour qu
|
||||
|
||||
Une étape à la fois. Ne saute pas d'étape.
|
||||
|
||||
## Étape 0 — Support d'installation
|
||||
|
||||
Deux supports possibles, choisis-en un.
|
||||
|
||||
**PXE (serveur déjà monté)** : rien à préparer ici. Va à l'étape 1.
|
||||
|
||||
**Ce n'est pas l'ISO de nixos.org.** Celle-ci est construite depuis ce repo (`installer/iso.nix`) : paquets propres au parc (git, outils de partitionnement...), clavier/locale FR par défaut, et surtout `install.sh` qui se lance seul à l'ouverture de session -- l'ISO officielle démarre sur un shell nu, sans rien de tout ça, il faudrait tout faire à la main (partitionnement, `nixos-install`...).
|
||||
|
||||
Le `nix build` ci-dessous est **long** (10 à 30+ minutes) et n'affiche presque rien pendant la majeure partie du temps -- l'étape la plus lente (`mksquashfs`, compression de tout le store Nix pour l'ISO) ne montre aucune progression. Normal, pas figé : vérifier avec `top`/`htop` qu'un processus consomme du CPU avant de penser à interrompre.
|
||||
|
||||
**Clé USB, sur Linux :**
|
||||
|
||||
```bash
|
||||
git clone https://gitea.miniclic.net/Damien/Nixos_Fleet.git
|
||||
cd Nixos_Fleet
|
||||
nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage
|
||||
lsblk # repérer la clé -- pas ton disque système
|
||||
sudo dd if=result/iso/*.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
||||
```
|
||||
|
||||
Vérifier `/dev/sdX` avant de lancer `dd`.
|
||||
|
||||
**Clé USB, sur Windows** (dans un terminal WSL2 -- voir "Installer WSL2" plus bas si pas encore fait) :
|
||||
|
||||
```bash
|
||||
cd "/mnt/c/Users/<toi>/Desktop" # ou un autre dossier Windows de ton choix
|
||||
git clone https://gitea.miniclic.net/Damien/Nixos_Fleet.git
|
||||
cd Nixos_Fleet
|
||||
nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage
|
||||
cp result/iso/*.iso ./nixos-installer.iso
|
||||
```
|
||||
|
||||
`dd` ne fonctionne pas depuis WSL2 (pas d'accès à la clé USB par défaut, [source](https://learn.microsoft.com/en-us/windows/wsl/connect-usb)). `nixos-installer.iso` apparaît dans l'explorateur Windows, dans le dossier du repo -- le graver avec [Rufus](https://rufus.ie/) (ou balenaEtcher, Win32DiskImager).
|
||||
|
||||
Une fois la clé prête (Linux ou Windows) : va à l'étape 1.
|
||||
|
||||
## Étape 1 — Brancher
|
||||
|
||||
Branche le câble réseau (PXE) ou la clé USB gravée à l'étape 0 (USB).
|
||||
Branche le câble réseau.
|
||||
|
||||
Branche le PC au secteur.
|
||||
|
||||
@@ -149,7 +56,7 @@ HP → F9
|
||||
|
||||
Lenovo → F12
|
||||
|
||||
Choisis : Network Boot (PXE) ou Boot from USB (clé) -- le menu qui suit est identique dans les deux cas.
|
||||
Choisis : Network Boot.
|
||||
|
||||
## Étape 3 — Attendre
|
||||
|
||||
@@ -163,7 +70,7 @@ Il y a deux cas. Choisis-en un seul.
|
||||
|
||||
PC normal → va à l'étape 4A.
|
||||
|
||||
PC pour une personne (profil nommé) → va à l'étape 4B.
|
||||
PC pour thomas, clara, jade ou oreo → va à l'étape 4B.
|
||||
|
||||
## Étape 4A — PC normal
|
||||
|
||||
@@ -183,7 +90,7 @@ Tape : `2`
|
||||
|
||||
Appuie sur Entrée.
|
||||
|
||||
Tape le prénom du profil (liste donnée à part, pas dans ce guide public).
|
||||
Tape le prénom (thomas, clara, jade ou oreo).
|
||||
|
||||
Appuie sur Entrée.
|
||||
|
||||
@@ -215,19 +122,17 @@ N'y touche pas.
|
||||
|
||||
PC normal :
|
||||
|
||||
Utilisateur : `nixos`
|
||||
Utilisateur : `usagers`
|
||||
|
||||
Mot de passe : `nixos`
|
||||
|
||||
Utilisateur et mot de passe identiques, volontairement (un seul mot à retenir sur un poste partagé). Stable, ne change jamais tout seul et n'est jamais redemandé (voir "Réactiver un changement de mot de passe forcé" en référence si ça doit changer un jour).
|
||||
|
||||
PC pour une personne :
|
||||
|
||||
Utilisateur : le prénom tapé à l'étape 4B
|
||||
|
||||
Mot de passe : `nixos`
|
||||
|
||||
Un nouveau mot de passe est demandé tout de suite (changement forcé au premier login, seulement sur les postes nominatifs -- voir [Nixos_Users](../nixos_users)). Le choisir maintenant.
|
||||
Un nouveau mot de passe est demandé tout de suite. Le choisir maintenant.
|
||||
|
||||
## Étape 7 — Vérifier
|
||||
|
||||
@@ -333,7 +238,13 @@ Un reboot du serveur applique aussi ça correctement au démarrage suivant.
|
||||
|
||||
## Étape 8 — Tester sur 1 PC
|
||||
|
||||
Suivre "Installer un PC" (Network Boot au lieu de Boot from USB) sur une seule machine avant d'en lancer 20. Tester BIOS et UEFI séparément si le parc a les deux.
|
||||
Démarrer un seul PC en PXE.
|
||||
|
||||
Vérifier qu'il arrive sur l'écran de choix.
|
||||
|
||||
Si le parc a du BIOS et de l'UEFI, tester les deux.
|
||||
|
||||
Ne pas lancer 20 PC avant que cette étape fonctionne.
|
||||
|
||||
## Après l'installation
|
||||
|
||||
@@ -400,7 +311,7 @@ IP qui changent tout le temps → un autre DHCP tourne déjà, repasser `standal
|
||||
|
||||
## Contenu du repo
|
||||
|
||||
`hosts/nixos/` → le seul profil du repo. Jamais chiffré.
|
||||
`hosts/usagers/` → le seul profil du repo. Jamais chiffré.
|
||||
|
||||
`diskos/plain.nix` → layout disko (disque `main`, GPT+ESP+btrfs).
|
||||
|
||||
@@ -408,115 +319,45 @@ IP qui changent tout le temps → un autre DHCP tourne déjà, repasser `standal
|
||||
|
||||
`pxe-server/` → le serveur PXE (dnsmasq + Caddy).
|
||||
|
||||
## Personnaliser le bureau (dconf)
|
||||
|
||||
`hosts/nixos/dconf.ini` contient les réglages Cinnamon (thème, fond d'écran, panneau...), chargés par home-manager à chaque rebuild.
|
||||
|
||||
Pour changer un réglage : le faire en direct dans l'interface Cinnamon sur une machine, puis exporter :
|
||||
|
||||
```bash
|
||||
dconf dump /org/cinnamon/ > dconf.ini
|
||||
```
|
||||
|
||||
Remplacer `hosts/nixos/dconf.ini` par ce fichier, commit, push. Rien à changer dans le `.nix`.
|
||||
|
||||
## Changer le nom d'utilisateur ou le mot de passe
|
||||
|
||||
Deux fichiers à toucher, dans `hosts/nixos/configuration.nix` :
|
||||
|
||||
```nix
|
||||
users.users.nixos = {
|
||||
...
|
||||
initialPassword = "nixos";
|
||||
};
|
||||
```
|
||||
|
||||
Renommer `nixos` (l'attribut `users.users.nixos`) change le compte système. Changer la valeur d'`initialPassword` change le mot de passe.
|
||||
|
||||
Si le nom d'utilisateur change, mettre à jour aussi la référence dans `hosts/common/default.nix` (`home-manager.users.nixos = ...` doit cibler le même nom, sinon home-manager configure un compte qui n'existe plus).
|
||||
|
||||
**`initialPassword` ne s'applique qu'à la création du compte.** Sur une machine déjà installée, changer cette valeur dans le repo + laisser l'auto-upgrade tourner ne change RIEN au mot de passe réel -- NixOS ne réécrit jamais un mot de passe déjà défini. Ça ne s'applique qu'aux **nouvelles installations** à partir de ce moment-là.
|
||||
|
||||
Pour changer le mot de passe sur une machine déjà déployée : à la main, sur place ou à distance (SSH/console) :
|
||||
|
||||
```bash
|
||||
sudo passwd nixos
|
||||
```
|
||||
|
||||
Pareil pour renommer le compte sur une machine déjà installée : `usermod -l` ne suffit pas à tout migrer proprement (home directory, permissions) -- plus simple de considérer que le renommage ne s'applique qu'aux futures installations, pas de le faire à chaud sur le parc existant.
|
||||
|
||||
## Réactiver un changement de mot de passe forcé
|
||||
|
||||
Par défaut, le compte `nixos` garde `nixos` comme mot de passe stable, sans expiration (`hosts/nixos/configuration.nix`). Voulu : poste partagé, pas nominatif -- forcer un changement casserait le mot de passe commun affiché sur l'étiquette/le guide remis aux gens.
|
||||
|
||||
`nixos_users` force ce changement au premier login pour les postes nominatifs, via un script d'activation one-shot (`hosts/common/accounts.nix` de ce repo-là) :
|
||||
|
||||
```nix
|
||||
system.activationScripts.forcePasswordChangeOnce.text = ''
|
||||
marker=/var/lib/nixos-force-pwchange-done
|
||||
if [ ! -e "$marker" ]; then
|
||||
${pkgs.shadow}/bin/chage -d 0 nixos
|
||||
mkdir -p /var/lib
|
||||
touch "$marker"
|
||||
fi
|
||||
'';
|
||||
```
|
||||
|
||||
Pour réactiver la même chose sur `nixos`, reprendre ce bloc tel quel dans `hosts/nixos/configuration.nix` (ou `hosts/common/default.nix` si ça doit s'appliquer à un futur deuxième profil de flotte). Le marqueur est nécessaire : sans lui, `chage -d 0` s'appliquerait à nouveau à chaque `nixos-rebuild switch`, redemandant un mot de passe à chaque redémarrage.
|
||||
|
||||
## Pourquoi deux repos
|
||||
|
||||
Une machine de la flotte pull uniquement ce repo.
|
||||
|
||||
Les comptes nominatifs vivent dans un autre repo, privé.
|
||||
Les comptes thomas/clara/jade/oreo vivent dans un autre repo, privé.
|
||||
|
||||
La flotte ne voit jamais ce second repo.
|
||||
|
||||
## Graver la clé USB
|
||||
|
||||
Marche sans le serveur PXE. Utile pour tester avant de monter le PXE, ou en secours. Commandes : voir "Étape 0" au tout début de ce README.
|
||||
Marche sans le serveur PXE. Utile pour tester avant de monter le PXE, ou en secours.
|
||||
|
||||
NixOS permet nativement de fabriquer une image d'installation sur mesure : un ISO avec nos propres paquets et notre propre script (`install.sh`) qui se lance seul au démarrage. C'est une fonctionnalité standard de NixOS, pas un bricolage — `installer/iso.nix` dans ce repo est la définition de cette image.
|
||||
|
||||
C'est le même ISO pour la flotte et les postes nommés — pas deux images différentes, **pas de commande de build spécifique par profil nommé**. Le choix se fait à l'écran au démarrage, une fois la clé branchée sur le PC cible (voir étapes 4/4B).
|
||||
C'est le même ISO pour la flotte et les postes nommés — pas deux images différentes. Le choix se fait à l'écran au démarrage (`1) flotte` / `2) poste nommé`), pas au moment de la gravure.
|
||||
|
||||
Depuis Windows : mêmes commandes, dans WSL -- voir "Étape 0", section Windows.
|
||||
Le fichier `.iso` n'existe pas tant qu'on ne l'a pas construit :
|
||||
|
||||
```bash
|
||||
nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage
|
||||
```
|
||||
|
||||
Repérer la clé USB avant de graver — bien vérifier le chemin, pas ton disque système :
|
||||
|
||||
```bash
|
||||
lsblk
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo dd if=result/iso/*.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
||||
```
|
||||
|
||||
Vérifier `/dev/sdX` avant de lancer `dd`.
|
||||
|
||||
## Mises à jour de la flotte
|
||||
|
||||
Chaque PC tire ses mises à jour directement depuis ce repo, chaque semaine (`system.autoUpgrade`, `persistent = true` : se rattrape au boot suivant même si le PC était éteint au moment prévu).
|
||||
Chaque PC tire ses mises à jour directement depuis ce repo, tous les jours.
|
||||
|
||||
Pas de `git pull` manuel sur les PC en fonctionnement normal. Juste `git push` ici.
|
||||
|
||||
### Appliquer un changement immédiatement (sans attendre la semaine)
|
||||
|
||||
1. Pousser le changement depuis le poste de dev :
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "description"
|
||||
git push
|
||||
```
|
||||
|
||||
2. Sur le PC cible, trois options, du plus lent au plus immédiat :
|
||||
|
||||
**Rien faire** : le timer hebdomadaire s'en charge tout seul.
|
||||
|
||||
**Forcer maintenant, sans y toucher physiquement** (ex. à distance via SSH) : lancer le même service que le timer, il fetch tout seul depuis l'URL du repo, pas besoin que `/etc/nixos` soit à jour :
|
||||
|
||||
```bash
|
||||
sudo systemctl start nixos-upgrade.service
|
||||
```
|
||||
|
||||
**Rebuild manuel sur place** (typique en test, devant la machine) :
|
||||
|
||||
```bash
|
||||
cd /etc/nixos
|
||||
sudo git pull
|
||||
sudo nixos-rebuild switch --flake .#nixos
|
||||
```
|
||||
|
||||
Le `git pull` est obligatoire ici : contrairement à `nixos-upgrade.service`, ce rebuild lit les fichiers locaux de `/etc/nixos`, pas l'URL distante.
|
||||
Pas de `git pull` manuel sur les PC. Juste `git push` ici.
|
||||
|
||||
## Avant la première vague de 100 PC
|
||||
|
||||
@@ -532,26 +373,21 @@ Boot BIOS et boot UEFI, sur le parc réel.
|
||||
|
||||
Test sur 1-2 PC réels d'abord.
|
||||
|
||||
PC à seulement 4 Go de RAM : le zram (`installer-base.nix`) doit suffire pour installer `nixos` (Cinnamon + Firefox + LibreOffice + VLC), mais pas testé en dessous de 12 Go pour l'instant. Si ça échoue encore sur du 4 Go réel : alléger la liste de paquets de `nixos` plutôt que d'insister sur le zram seul.
|
||||
PC à seulement 4 Go de RAM : le zram (`installer-base.nix`) doit suffire pour installer `usagers` (Cinnamon + Firefox + LibreOffice + VLC), mais pas testé en dessous de 12 Go pour l'instant. Si ça échoue encore sur du 4 Go réel : alléger la liste de paquets de `usagers` plutôt que d'insister sur le zram seul.
|
||||
|
||||
```bash
|
||||
nix flake check --no-build # évalue nixos/installer-netboot/installer-iso
|
||||
nix flake check --no-build --impure # inclut pxe-server (lit /etc/nixos, réel sur le serveur seulement)
|
||||
nix build .#nixosConfigurations.nixos.config.system.build.toplevel --dry-run
|
||||
nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage --dry-run
|
||||
nix flake check
|
||||
nix build .#nixosConfigurations.usagers.config.system.build.toplevel
|
||||
nix build .#nixosConfigurations.installer-iso
|
||||
```
|
||||
|
||||
Vérifié (WSL, testé le 2026-09-12) : `nixos` s'évalue et résout entièrement sa fermeture de paquets sans erreur. `pxe-server` échoue sans `--impure` en dehors du vrai serveur PXE (attendu, lit `/etc/nixos/hardware-configuration.nix`).
|
||||
|
||||
**Ce que ces commandes ne couvrent pas** : elles vérifient que la config s'évalue et que les paquets existent, pas que le poste installé fonctionne réellement (Wi-Fi, wallpaper, LightDM, avatar...). Seul un test sur machine réelle (étape 8 plus haut) le confirme.
|
||||
|
||||
## Connexion Wi-Fi
|
||||
|
||||
Pour un PC sans câble réseau (après l'installation, ou en dépannage).
|
||||
|
||||
### Option 1 — NetworkManager
|
||||
|
||||
Déjà actif par défaut dans ce repo (`hosts/common/default.nix`). Rien à ajouter dans `configuration.nix`.
|
||||
Déjà actif par défaut dans ce repo (`hosts/common/base.nix`). Rien à ajouter dans `configuration.nix`.
|
||||
|
||||
```nix
|
||||
networking.networkmanager.enable = true;
|
||||
@@ -590,86 +426,7 @@ Aucun paquet à installer manuellement : `enable = true` suffit dans les deux ca
|
||||
Après édition :
|
||||
|
||||
```bash
|
||||
sudo nixos-rebuild switch --flake .#nixos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Environnement de dev sous Windows (WSL + Nix)
|
||||
|
||||
Pour tester `nix flake check` / `nix build` depuis un poste Windows. Nix ne tourne pas nativement sur Windows : il faut un vrai noyau Linux, donc WSL2.
|
||||
|
||||
## Installer WSL2
|
||||
|
||||
PowerShell **en administrateur** :
|
||||
|
||||
```powershell
|
||||
wsl --install
|
||||
```
|
||||
|
||||
Installe WSL2 + Ubuntu par défaut. Redémarrer si demandé, puis laisser Ubuntu créer l'utilisateur Linux (indépendant du compte Windows).
|
||||
|
||||
## Installer Nix
|
||||
|
||||
Dans le terminal Ubuntu, commande officielle (nixos.org) :
|
||||
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install | sh -s -- --daemon
|
||||
```
|
||||
|
||||
Fermer/rouvrir le terminal ensuite. Vérifier :
|
||||
|
||||
```bash
|
||||
nix --version
|
||||
```
|
||||
|
||||
Les flakes sont activées par défaut sur les versions récentes de Nix (2.35+) — rien à configurer en plus. Si `nix flake check` répond une erreur `experimental-features`, ajouter dans `/etc/nix/nix.conf` :
|
||||
|
||||
```
|
||||
experimental-features = nix-command flakes
|
||||
```
|
||||
|
||||
## Utiliser les repos depuis WSL
|
||||
|
||||
Les fichiers Windows sont accessibles depuis WSL sous `/mnt/c/...` — pas besoin de cloner une deuxième fois :
|
||||
|
||||
```bash
|
||||
cd "/mnt/c/Users/<toi>/Desktop/Claude/Nixos/nixos_fleet"
|
||||
nix flake check --no-build
|
||||
```
|
||||
|
||||
`pxe-server` échouera à `nix flake check` (sans `--impure`) : il lit `/etc/nixos/hardware-configuration.nix`, qui n'existe que sur le vrai serveur PXE. Normal, pas une régression.
|
||||
|
||||
## Si "pas de réseau" dans WSL
|
||||
|
||||
Symptôme typique : `ping 8.8.8.8` répond, mais `curl https://nixos.org` (ou tout domaine) time out à la résolution — le tunnel DNS de WSL2 est cassé (fréquent avec VPN/antivirus). Vérifier :
|
||||
|
||||
```bash
|
||||
curl -m 5 https://1.1.1.1 # IP brute : doit marcher
|
||||
curl -m 5 https://nixos.org # domaine : si ça time out, c'est le DNS
|
||||
```
|
||||
|
||||
Correctif (côté Windows, PowerShell, pas besoin d'être admin) : créer `%USERPROFILE%\.wslconfig` avec :
|
||||
|
||||
```
|
||||
[wsl2]
|
||||
networkingMode=mirrored
|
||||
```
|
||||
|
||||
Puis `wsl --shutdown` et rouvrir Ubuntu. Ce mode partage directement la pile réseau de Windows à WSL, au lieu de passer par le NAT + proxy DNS interne.
|
||||
|
||||
## Construire l'ISO et la graver, depuis Windows
|
||||
|
||||
Commandes complètes : voir "Étape 0 — Support d'installation" tout en haut de ce README.
|
||||
|
||||
Alternative avancée à Rufus (rattacher la clé USB à WSL et faire `dd` comme sur Linux) : [usbipd-win](https://github.com/dorssel/usbipd-win).
|
||||
|
||||
## Appeler `nix` depuis un outil qui pilote WSL sans shell interactif
|
||||
|
||||
`wsl.exe nix --version` échoue avec `nix: command not found` : une commande non-interactive ne source pas le profil qui ajoute `nix` au PATH. Forcer un shell de login :
|
||||
|
||||
```bash
|
||||
wsl.exe -e bash -lc "nix --version"
|
||||
sudo nixos-rebuild switch --flake .#usagers
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Generated
-22
@@ -20,27 +20,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1789182507,
|
||||
"narHash": "sha256-9nt4W0HNNP84B+b37yRs9Zxat1ZR5guJxEZDEjAgex4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "b1d1b60084970f9d1e2b72662639dab6d039be71",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "release-26.05",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1788807765,
|
||||
@@ -60,7 +39,6 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"disko": "disko",
|
||||
"home-manager": "home-manager",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,47 +7,17 @@
|
||||
url = "github:nix-community/disko";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
# Même branche release-26.05 que nixpkgs (confirmé : la branche existe
|
||||
# bien sur nix-community/home-manager). Gère les réglages Cinnamon par
|
||||
# utilisateur (dconf, fichiers dans $HOME) à la place des scripts
|
||||
# d'activation système à la main qu'on avait avant.
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager/release-26.05";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs, disko, home-manager, ... }:
|
||||
let
|
||||
repoUrl = "git+https://gitea.miniclic.net/Damien/Nixos_Fleet.git";
|
||||
in
|
||||
{ self, nixpkgs, disko, ... }:
|
||||
{
|
||||
nixosConfigurations = {
|
||||
nixos = nixpkgs.lib.nixosSystem {
|
||||
usagers = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
disko.nixosModules.disko
|
||||
home-manager.nixosModules.home-manager
|
||||
./hosts/nixos/configuration.nix
|
||||
./diskos/plain.nix
|
||||
{ system.autoUpgrade.flake = "${repoUrl}#nixos"; }
|
||||
];
|
||||
};
|
||||
|
||||
# Même profil logiciel que "nixos", mais sans disko : pour une
|
||||
# machine où NixOS est déjà installé (partitionnement existant,
|
||||
# quel qu'il soit, à ne pas toucher) -- utilise le
|
||||
# hardware-configuration.nix réel de cette machine à la place de
|
||||
# diskos/plain.nix, comme pxe-server. --impure obligatoire au
|
||||
# nixos-rebuild (lit un chemin hors du flake).
|
||||
nixos-existing = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
home-manager.nixosModules.home-manager
|
||||
./hosts/nixos/configuration.nix
|
||||
/etc/nixos/hardware-configuration.nix
|
||||
{ system.autoUpgrade.flake = "${repoUrl}#nixos-existing"; }
|
||||
./hosts/usagers/configuration.nix
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Compte admin des machines de la flotte : root, mot de passe "nixos"
|
||||
# STABLE (pas d'expiration forcée ici, contrairement à nixos_users) —
|
||||
# ces machines sont génériques/partagées, un mot de passe connu et
|
||||
# constant sur tout le parc est voulu pour l'administration courante.
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
users.mutableUsers = true;
|
||||
|
||||
users.users.root = {
|
||||
initialPassword = "nixos";
|
||||
};
|
||||
|
||||
# Login root direct autorisé nativement dès qu'un mot de passe est
|
||||
# défini (console/tty). SSH reste sur le défaut NixOS (clé uniquement) :
|
||||
# le mot de passe "nixos" n'ouvre pas la porte à distance, seulement en
|
||||
# local sur la machine.
|
||||
|
||||
# `initialPassword` n'est appliqué par NixOS qu'à la création du compte,
|
||||
# donc un rebuild/auto-upgrade ultérieur ne le réécrit jamais — mais ici
|
||||
# on ne force pas non plus de changement : "nixos" doit rester valide
|
||||
# indéfiniment sur la flotte.
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# Socle commun à toutes les machines de la flotte (repo public).
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
# Toutes les machines de la flotte portent le même nom : pas
|
||||
# d'identification individuelle pour l'instant (voir README du repo
|
||||
# racine). À revoir plus tard via le numéro de série DMI si un vrai
|
||||
# besoin d'identification apparaît.
|
||||
networking.hostName = "Nixos_Fleet";
|
||||
networking.networkmanager.enable = true;
|
||||
|
||||
time.timeZone = "Europe/Paris";
|
||||
i18n.defaultLocale = "fr_FR.UTF-8";
|
||||
i18n.extraLocaleSettings = {
|
||||
LC_ADDRESS = "fr_FR.UTF-8";
|
||||
LC_IDENTIFICATION = "fr_FR.UTF-8";
|
||||
LC_MEASUREMENT = "fr_FR.UTF-8";
|
||||
LC_MONETARY = "fr_FR.UTF-8";
|
||||
LC_NAME = "fr_FR.UTF-8";
|
||||
LC_NUMERIC = "fr_FR.UTF-8";
|
||||
LC_PAPER = "fr_FR.UTF-8";
|
||||
LC_TELEPHONE = "fr_FR.UTF-8";
|
||||
LC_TIME = "fr_FR.UTF-8";
|
||||
};
|
||||
services.xserver.xkb = {
|
||||
layout = "fr";
|
||||
variant = "";
|
||||
};
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
boot.loader.systemd-boot.configurationLimit = 10;
|
||||
|
||||
services.openssh.enable = true;
|
||||
networking.firewall.enable = true;
|
||||
|
||||
# Certains PC du parc n'ont que 4 Go de RAM (Cinnamon + Firefox +
|
||||
# LibreOffice + VLC peuvent solliciter la mémoire) — swap compressé en
|
||||
# RAM, marche identiquement quelle que soit la machine.
|
||||
zramSwap = {
|
||||
enable = true;
|
||||
memoryPercent = 50;
|
||||
};
|
||||
|
||||
nix.settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
options = "--delete-older-than 30d";
|
||||
};
|
||||
|
||||
# Convergence : chaque machine de la flotte tire ses mises à jour du
|
||||
# même repo public, sans intervention humaine (pas de netrc nécessaire
|
||||
# ici, contrairement au repo nixos_users). Voir README pour adapter
|
||||
# l'URL réelle du repo.
|
||||
system.autoUpgrade = {
|
||||
enable = true;
|
||||
dates = "daily";
|
||||
flake = "git+https://gitea.miniclic.net/Damien/Nixos_Fleet.git#usagers";
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
git
|
||||
curl
|
||||
wget
|
||||
htop
|
||||
nixfmt
|
||||
];
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -1,538 +0,0 @@
|
||||
# Socle commun à toutes les machines de la flotte (repo public) : compte
|
||||
# système, réseau/durcissement, bureau Cinnamon/Firefox. Un seul fichier
|
||||
# plutôt que plusieurs petits (base/desktop/accounts) -- chaque option
|
||||
# n'a qu'un seul endroit où la chercher.
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
# PREMIER ESSAI FAUX (corrigé ici) : Cinnamon ne suit PAS le mécanisme
|
||||
# GNOME standard (gnome-background-properties via XDG_DATA_DIRS).
|
||||
# Vérifié dans le vrai source du sélecteur, cs_backgrounds.py
|
||||
# (linuxmint/cinnamon, module cinnamon-settings) :
|
||||
# `properties_dir = "/usr/share/cinnamon-background-properties"`, un
|
||||
# chemin ABSOLU EN DUR, jamais résolu via XDG_DATA_DIRS -- même en
|
||||
# mettant le bon XML au bon endroit dans le store, Cinnamon ne
|
||||
# regarde jamais là. mint-artwork (déjà installé par défaut avec
|
||||
# Cinnamon) ne fournit de toute façon aucun XML de ce genre -- ses
|
||||
# images sont sur le disque mais invisibles dans le sélecteur.
|
||||
#
|
||||
# Solution : générer le XML nous-mêmes (à partir de ce que
|
||||
# mint-artwork contient réellement, pas une liste en dur -- chemin
|
||||
# confirmé dans nixpkgs/pkgs/by-name/mi/mint-artwork/package.nix qui
|
||||
# sed-remplace littéralement usr/share/backgrounds/linuxmint/*.jpg),
|
||||
# PUIS forcer /usr/share/cinnamon-background-properties à pointer
|
||||
# dessus via systemd-tmpfiles (seul moyen d'atteindre un chemin en
|
||||
# dur sur un système qui n'a pas de /usr/share FHS classique).
|
||||
cinnamonDefaultWallpapers = pkgs.runCommand "cinnamon-default-wallpapers-properties" { } ''
|
||||
mkdir -p $out/share/cinnamon-background-properties
|
||||
{
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
echo '<!DOCTYPE wallpapers SYSTEM "gnome-wp-list.dtd">'
|
||||
echo '<wallpapers>'
|
||||
for f in ${pkgs.mint-artwork}/share/backgrounds/linuxmint/*.jpg \
|
||||
${pkgs.mint-artwork}/share/backgrounds/linuxmint/*.png \
|
||||
${./wallpaper/Nix/13977413.png}; do
|
||||
[ -e "$f" ] || continue
|
||||
name=$(basename "$f")
|
||||
echo " <wallpaper deleted=\"false\">"
|
||||
echo " <name>$name</name>"
|
||||
echo " <filename>$f</filename>"
|
||||
echo " <options>zoom</options>"
|
||||
echo " </wallpaper>"
|
||||
done
|
||||
echo '</wallpapers>'
|
||||
} > $out/share/cinnamon-background-properties/nixos-fleet-defaults.xml
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [ ../../modules/anssi ];
|
||||
|
||||
# L+ crée aussi les dossiers parents manquants (/usr, /usr/share,
|
||||
# confirmé dans la doc systemd-tmpfiles.d) -- pas besoin de les créer
|
||||
# à la main. Réappliqué à chaque nixos-rebuild switch (pas seulement
|
||||
# au boot), donc pas besoin de reboot pour que ça prenne effet.
|
||||
#
|
||||
# Symlink un FICHIER précis dans le dossier, pas le dossier entier :
|
||||
# /usr/share/cinnamon-background-properties reste alors un vrai
|
||||
# dossier (auto-créé par L+) où d'autres XML peuvent être ajoutés à
|
||||
# côté (ex: hosts/oreo/configuration.nix côté nixos_users) sans
|
||||
# conflit -- symlinker le dossier entier depuis deux endroits
|
||||
# différents écraserait l'un des deux.
|
||||
systemd.tmpfiles.rules = [
|
||||
"L+ /usr/share/cinnamon-background-properties/common-defaults.xml - - - - ${cinnamonDefaultWallpapers}/share/cinnamon-background-properties/nixos-fleet-defaults.xml"
|
||||
];
|
||||
|
||||
# Nom uniforme, pas d'identification individuelle pour l'instant (voir
|
||||
# README). À revoir via le numéro de série DMI si le besoin apparaît.
|
||||
networking.hostName = "Nixos_Fleet";
|
||||
networking.networkmanager.enable = true;
|
||||
|
||||
# Couverture matérielle maximale (wifi/Bluetooth/PCI) sur un parc
|
||||
# hétérogène inconnu à l'avance. Inclut enableRedistributableFirmware.
|
||||
hardware.enableAllFirmware = lib.mkDefault true;
|
||||
|
||||
# Durcissement ANSSI (modules/anssi/, vendored depuis SécurixOS).
|
||||
# level "intermediary" : couvre déjà tout ce qui était réglé à la main
|
||||
# sur le profil oreo (R9/R11/R12/R14). category "client" : postes de
|
||||
# travail, pas des serveurs.
|
||||
#
|
||||
# R12 force net.ipv4.ip_forward = 0 : sans impact sur des postes
|
||||
# clients. Ne pas importer ce module sur pxe-server/configuration.nix
|
||||
# (a besoin du forwarding inverse).
|
||||
security.anssi = {
|
||||
enable = true;
|
||||
level = "intermediary";
|
||||
category = "client";
|
||||
|
||||
# R8 (mitigations CPU) coupe l'hyperthreading via "mds=full,nosmt" :
|
||||
# trop coûteux sur le matériel bas de gamme du parc. Désactivé via
|
||||
# exception plutôt que contourné en silence ; réappliqué ci-dessous
|
||||
# sans le "nosmt".
|
||||
exceptions.R8.rationale = "SMT conservé : impact perf trop lourd sur le parc (matériel bas de gamme, parfois 4 Go de RAM). Mitigations réappliquées ci-dessous sans 'nosmt'.";
|
||||
};
|
||||
|
||||
# R8 (modules/anssi/kernel-options.nix), sauf mds=full,nosmt -> mds=full.
|
||||
boot.kernelParams = [
|
||||
"l1tf=full,force"
|
||||
"page_poison=on"
|
||||
"pti=on"
|
||||
"slab_nomerge=yes"
|
||||
"slub_debug=FZP"
|
||||
"spec_store_bypass_disable=seccomp"
|
||||
"spectre_v2=on"
|
||||
"mds=full" # sans ",nosmt" : hyperthreading conservé
|
||||
"mce=0"
|
||||
"page_alloc.shuffle=1"
|
||||
"rng_core.default_quality=500"
|
||||
];
|
||||
|
||||
# Pas couvert par le module ANSSI (R45 en exception : AppArmor
|
||||
# partiellement supporté sur NixOS).
|
||||
systemd.coredump.enable = false;
|
||||
security.apparmor.packages = [ pkgs.apparmor-profiles ];
|
||||
security.apparmor.enableCache = true;
|
||||
|
||||
# Bluetooth (même config que le profil oreo) : pas d'allumage auto au
|
||||
# boot, blueman pour la gestion depuis Cinnamon.
|
||||
hardware.bluetooth = {
|
||||
enable = true;
|
||||
powerOnBoot = false;
|
||||
};
|
||||
services.blueman.enable = true;
|
||||
|
||||
time.timeZone = "Europe/Paris";
|
||||
i18n.defaultLocale = "fr_FR.UTF-8";
|
||||
i18n.extraLocaleSettings = {
|
||||
LC_ADDRESS = "fr_FR.UTF-8";
|
||||
LC_IDENTIFICATION = "fr_FR.UTF-8";
|
||||
LC_MEASUREMENT = "fr_FR.UTF-8";
|
||||
LC_MONETARY = "fr_FR.UTF-8";
|
||||
LC_NAME = "fr_FR.UTF-8";
|
||||
LC_NUMERIC = "fr_FR.UTF-8";
|
||||
LC_PAPER = "fr_FR.UTF-8";
|
||||
LC_TELEPHONE = "fr_FR.UTF-8";
|
||||
LC_TIME = "fr_FR.UTF-8";
|
||||
};
|
||||
services.xserver.xkb = {
|
||||
layout = "fr";
|
||||
variant = "";
|
||||
};
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
boot.loader.systemd-boot.configurationLimit = 10;
|
||||
|
||||
services.openssh.enable = true;
|
||||
networking.firewall.enable = true;
|
||||
|
||||
# Swap compressé en RAM : couvre le bas du parc (4 Go, desktop complet).
|
||||
zramSwap = {
|
||||
enable = true;
|
||||
memoryPercent = 50;
|
||||
};
|
||||
|
||||
nix.settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
options = "--delete-older-than 30d";
|
||||
};
|
||||
|
||||
# Convergence automatique depuis le repo public, sans netrc. Cadence
|
||||
# weekly (même format que oreo, plutôt que daily). persistent = true
|
||||
# (déjà la valeur par défaut de ce module -- explicité ici) : si la
|
||||
# machine est éteinte au moment prévu, la maj se lance dès le
|
||||
# prochain démarrage plutôt que d'attendre la semaine suivante.
|
||||
# `flake` n'est PAS fixé ici : il dépend de l'attribut réellement
|
||||
# utilisé pour cette machine (nixos vs nixos-existing, disko ou pas)
|
||||
# -- injecté par flake.nix, comme dans nixos_users. Le fixer ici
|
||||
# ferait basculer une machine "nixos-existing" vers l'attribut
|
||||
# "nixos" (disko) à son prochain auto-upgrade, avec un layout disque
|
||||
# potentiellement incompatible avec l'existant.
|
||||
system.autoUpgrade = {
|
||||
enable = true;
|
||||
persistent = true;
|
||||
dates = "weekly";
|
||||
};
|
||||
|
||||
# Compte admin de la flotte : root, mot de passe "nixos" STABLE (pas
|
||||
# d'expiration forcée) -- machines génériques/partagées, un mot de
|
||||
# passe connu et constant sur tout le parc est voulu pour
|
||||
# l'administration courante. `initialPassword` n'est appliqué qu'à la
|
||||
# création du compte : un rebuild/auto-upgrade ultérieur ne le réécrit
|
||||
# jamais, mais on ne force pas non plus de changement -- "nixos" doit
|
||||
# rester valide indéfiniment sur la flotte. Login root direct autorisé
|
||||
# en local (console/tty) ; SSH reste sur le défaut NixOS (clé
|
||||
# uniquement), ce mot de passe n'ouvre pas la porte à distance.
|
||||
users.mutableUsers = true;
|
||||
users.users.root.initialPassword = "nixos";
|
||||
|
||||
# --- Bureau Cinnamon + Firefox --------------------------------------
|
||||
services.xserver.enable = true;
|
||||
services.xserver.displayManager.lightdm.enable = true;
|
||||
services.xserver.displayManager.lightdm.background = "/etc/nixos/hosts/common/wallpaper/Nix/13977413.png";
|
||||
services.xserver.desktopManager.cinnamon.enable = true;
|
||||
services.accounts-daemon.enable = true;
|
||||
|
||||
environment.cinnamon.excludePackages = with pkgs; [
|
||||
celluloid
|
||||
warpinator
|
||||
];
|
||||
|
||||
# dconf par utilisateur via home-manager, chargé depuis un fichier ini
|
||||
# brut (hosts/nixos/dconf.ini) plutôt que du dconf.settings typé --
|
||||
# même mécanisme que oreo (nixos_users), pour la même raison : ça se
|
||||
# met à jour par un simple `dconf dump /org/cinnamon/ > dconf.ini` +
|
||||
# commit depuis une machine réelle, pas en retranscrivant chaque clé à
|
||||
# la main en Nix. Repris tel quel du dconf d'oreo (panneau, thèmes,
|
||||
# réglages GTK/Nemo...), sauf le chemin du fond d'écran (commun,
|
||||
# pas par-profil) et l'ID Warpinator (retiré : une valeur fixe
|
||||
# partagée par tout le parc casserait l'appairage).
|
||||
home-manager.useGlobalPkgs = true; # partage le pkgs du système, pas de deuxième évaluation (RAM)
|
||||
home-manager.backupFileExtension = "hm-backup"; # évite un échec si un fichier existe déjà (migration)
|
||||
home-manager.users.nixos =
|
||||
{ pkgs, lib, ... }:
|
||||
{
|
||||
home.stateVersion = "26.05";
|
||||
|
||||
# DBUS_SESSION_BUS_ADDRESS / dbus-run-session : même logique que le
|
||||
# module home-manager dconf.nix officiel (dconf a besoin d'un bus
|
||||
# D-Bus pour écrire, on en lance un temporaire s'il n'y en a pas
|
||||
# déjà un actif).
|
||||
home.activation.nixosDconf = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then
|
||||
DCONF_DBUS_RUN_SESSION=""
|
||||
else
|
||||
DCONF_DBUS_RUN_SESSION="${pkgs.dbus}/bin/dbus-run-session --dbus-daemon=${pkgs.dbus}/bin/dbus-daemon"
|
||||
fi
|
||||
run $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / < ${../nixos/dconf.ini}
|
||||
'';
|
||||
};
|
||||
|
||||
# Reprend la config Firefox d'oreo (nixos_users/hosts/oreo/configuration.nix)
|
||||
# -- c'est elle qui tue la page d'accueil/configuration au premier
|
||||
# lancement (aboutwelcome, newtabpage...) et bloque les pubs/contenu
|
||||
# sponsorisé bien plus largement que l'ancienne version ici (qui ne
|
||||
# coupait que le strict showSponsored). Deux choses changées par
|
||||
# rapport à oreo, volontairement : SearXNG (perso, privé) remplacé par
|
||||
# DuckDuckGo (public, intégré à Firefox) ; ExtensionSettings réduit à
|
||||
# uBlock Origin + Privacy Badger (oreo en a une quinzaine, trop pour
|
||||
# un poste générique de flotte).
|
||||
programs.firefox = {
|
||||
enable = true;
|
||||
languagePacks = [ "fr" ];
|
||||
|
||||
preferences = {
|
||||
"browser.startup.homepage" = "https://duckduckgo.com";
|
||||
"privacy.resistFingerprinting" = true;
|
||||
|
||||
"network.trr.mode" = 3;
|
||||
"network.trr.uri" = "https://dns.quad9.net/dns-query";
|
||||
"network.trr.fallback-resolver" = "https://mozilla.cloudflare-dns.com/dns-query";
|
||||
|
||||
"browser.newtabpage.enabled" = false;
|
||||
"browser.newtabpage.activity-stream.enabled" = false;
|
||||
"browser.startup.page" = 1;
|
||||
|
||||
# Page de configuration/bienvenue au premier lancement -- désactivée.
|
||||
"browser.aboutwelcome.enabled" = false;
|
||||
"browser.newtabpage.pinned" = "[]";
|
||||
"browser.newtabpage.section.discovery" = false;
|
||||
|
||||
"places.history.enabled" = false;
|
||||
"privacy.clearOnShutdown.history" = true;
|
||||
"privacy.clearOnShutdown.cache" = true;
|
||||
"privacy.clearOnShutdown.downloads" = true;
|
||||
"privacy.clearOnShutdown.cookies" = false;
|
||||
"privacy.clearOnShutdown.sessions" = false;
|
||||
"privacy.clearOnShutdown.formdata" = false;
|
||||
"privacy.clearOnShutdown.offlineApps" = true;
|
||||
|
||||
# Onglets verticaux à gauche par défaut
|
||||
"sidebar.revamp" = true;
|
||||
"sidebar.verticalTabs" = true;
|
||||
|
||||
# Virer "Recent browsing" (Firefox View)
|
||||
"browser.tabs.firefox-view" = false;
|
||||
"browser.tabs.firefox-view-next" = false;
|
||||
|
||||
# Virer "List all tabs" (bouton chevron dropdown)
|
||||
"browser.tabs.tabmanager.enabled" = true;
|
||||
|
||||
# Virer suggestions de recherche (y compris tendances)
|
||||
"browser.search.suggest.enabled" = false;
|
||||
"browser.urlbar.suggest.searches" = false;
|
||||
"browser.urlbar.suggest.trending" = false;
|
||||
"browser.newtabpage.activity-stream.showSponsored" = false;
|
||||
|
||||
# HTTPS obligatoire en permanence
|
||||
"dom.security.https_only_mode" = true;
|
||||
"dom.security.https_only_mode_ever_enabled" = true;
|
||||
|
||||
# Désactiver WebRTC (fuite d'IP réelle même via VPN)
|
||||
"media.peerconnection.enabled" = false;
|
||||
|
||||
# Désactiver la géolocalisation
|
||||
"geo.enabled" = false;
|
||||
|
||||
# Désactiver DRM (Widevine)
|
||||
"media.eme.enabled" = false;
|
||||
|
||||
# Désactiver le prefetching (DNS/liens) — fuite d'intention de navigation
|
||||
"network.dns.disablePrefetch" = true;
|
||||
"network.prefetch-next" = false;
|
||||
"network.predictor.enabled" = false;
|
||||
"network.http.speculative-parallel-limit" = 0;
|
||||
|
||||
# Désactiver Safe Browsing distant (envoie des hachages d'URL à Google)
|
||||
"browser.safebrowsing.malware.enabled" = false;
|
||||
"browser.safebrowsing.phishing.enabled" = false;
|
||||
|
||||
# Désactiver la détection de portail captif (requête réseau vers Mozilla)
|
||||
"network.captive-portal-service.enabled" = false;
|
||||
"network.connectivity-service.enabled" = false;
|
||||
|
||||
# Isolation par premier-parti (empêche le cross-site tracking via cache/cookies)
|
||||
"privacy.firstparty.isolate" = true;
|
||||
|
||||
# Désactiver WebGL (vecteur de fingerprinting majeur)
|
||||
"webgl.disabled" = true;
|
||||
|
||||
# Désactiver l'API batterie (fingerprinting)
|
||||
"dom.battery.enabled" = false;
|
||||
|
||||
# Bloquer l'autoplay média
|
||||
"media.autoplay.default" = 5;
|
||||
|
||||
# Référent minimal envoyé aux sites
|
||||
"network.http.referer.XOriginPolicy" = 2;
|
||||
|
||||
# Do Not Track : plus d'UI depuis les versions récentes de Firefox,
|
||||
# mais le pref existe encore et envoie bien l'en-tête DNT=1.
|
||||
"privacy.donottrackheader.enabled" = true;
|
||||
};
|
||||
|
||||
policies = {
|
||||
# Langpack fr téléchargé via languagePacks ci-dessus ; sans
|
||||
# RequestedLocales, Firefox reste quand même en anglais (le
|
||||
# langpack est juste installé, pas activé).
|
||||
RequestedLocales = [
|
||||
"fr"
|
||||
"en-US"
|
||||
];
|
||||
|
||||
DisableFirefoxStudies = true;
|
||||
DisableFirefoxAccounts = false;
|
||||
DisableFirefoxScreenshots = true;
|
||||
DisableForgetButton = true;
|
||||
DisableMasterPasswordCreation = true;
|
||||
DisableProfileImport = true;
|
||||
DisableProfileRefresh = true;
|
||||
DisableSetDesktopBackground = true;
|
||||
DisablePocket = true;
|
||||
DisableTelemetry = true;
|
||||
DisableFormHistory = true;
|
||||
DisablePasswordReveal = true;
|
||||
SanitizeOnShutdown = false;
|
||||
GenerativeAI.Enable = false;
|
||||
|
||||
FirefoxSuggest = {
|
||||
WebSuggestions = false;
|
||||
SponsoredSuggestions = false;
|
||||
ImproveSuggest = false;
|
||||
};
|
||||
|
||||
FirefoxHome = {
|
||||
SponsoredTopSites = false;
|
||||
SponsoredPocket = false;
|
||||
TopSites = false;
|
||||
Highlights = false;
|
||||
Pocket = false;
|
||||
Snippets = false;
|
||||
# Widget météo (demande la géolocalisation) -- ajouté à
|
||||
# FirefoxHome en Firefox 152 / ESR 140.12.
|
||||
Weather = false;
|
||||
Locked = true;
|
||||
};
|
||||
|
||||
UserMessaging = {
|
||||
WhatsNew = false;
|
||||
MoreFromMozilla = false;
|
||||
};
|
||||
|
||||
EnableTrackingProtection = {
|
||||
Value = true;
|
||||
Locked = true;
|
||||
Cryptomining = true;
|
||||
Fingerprinting = true;
|
||||
};
|
||||
|
||||
BlockAboutConfig = false;
|
||||
BlockAboutProfiles = false;
|
||||
BlockAboutSupport = false;
|
||||
|
||||
DontCheckDefaultBrowser = true;
|
||||
HardwareAcceleration = true;
|
||||
OfferToSaveLogins = false;
|
||||
DefaultDownloadDirectory = "$HOME/Downloads";
|
||||
PromptForDownloadLocation = true;
|
||||
DisplayBookmarksToolbar = "never";
|
||||
|
||||
Preferences = {
|
||||
"privacy.globalprivacycontrol.enabled" = {
|
||||
Value = true;
|
||||
Status = "locked";
|
||||
};
|
||||
"privacy.globalprivacycontrol.functionality.enabled" = {
|
||||
Value = true;
|
||||
Status = "locked";
|
||||
};
|
||||
};
|
||||
|
||||
# DuckDuckGo est un moteur intégré à Firefox, pas besoin de l'Add.
|
||||
Homepage = {
|
||||
URL = "https://duckduckgo.com";
|
||||
Locked = true;
|
||||
StartPage = "homepage";
|
||||
};
|
||||
SearchEngines = {
|
||||
Default = "DuckDuckGo";
|
||||
PreventInstalls = true;
|
||||
};
|
||||
|
||||
"browser.aboutConfig.showWarning" = {
|
||||
Value = false;
|
||||
Status = "locked";
|
||||
};
|
||||
|
||||
NewTabPage = false;
|
||||
|
||||
ExtensionSettings = {
|
||||
"uBlock0@raymondhill.net" = {
|
||||
install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
|
||||
installation_mode = "force_installed";
|
||||
};
|
||||
"jid1-MnnxcxisBPnSXQ@jetpack" = {
|
||||
install_url = "https://addons.mozilla.org/firefox/downloads/latest/privacy-badger17/latest.xpi";
|
||||
installation_mode = "force_installed";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
cinnamonDefaultWallpapers
|
||||
git
|
||||
curl
|
||||
wget
|
||||
htop
|
||||
nixfmt
|
||||
|
||||
# === Office/Notes ===
|
||||
libreoffice
|
||||
pdfarranger
|
||||
simple-scan
|
||||
|
||||
# === System tools ===
|
||||
btop
|
||||
unzip
|
||||
screenfetch
|
||||
tmux
|
||||
fwupd
|
||||
fwupd-efi
|
||||
ncdu
|
||||
gdu
|
||||
hplip
|
||||
gutenprint
|
||||
cups
|
||||
inxi
|
||||
ntfs3g
|
||||
|
||||
# === Communication/Media ===
|
||||
thunderbird
|
||||
ffmpeg
|
||||
vlc
|
||||
mpv
|
||||
strawberry
|
||||
];
|
||||
|
||||
# Flatpak
|
||||
services.flatpak.enable = true;
|
||||
xdg.portal.enable = true;
|
||||
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
|
||||
|
||||
systemd.services.flatpak-repo = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
path = [ pkgs.flatpak ];
|
||||
script = ''
|
||||
flatpak remote-add --if-not-exists flathub \
|
||||
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.flatpak-install = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [
|
||||
"flatpak-repo.service"
|
||||
"network-online.target"
|
||||
];
|
||||
wants = [ "network-online.target" ];
|
||||
requires = [ "flatpak-repo.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
path = [ pkgs.flatpak ];
|
||||
script = ''
|
||||
flatpak install -y flathub \
|
||||
net.waterfox.waterfox \
|
||||
eu.betterbird.Betterbird
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.flatpak-update = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.flatpak ];
|
||||
script = ''
|
||||
flatpak update -y
|
||||
'';
|
||||
};
|
||||
|
||||
programs.appimage = {
|
||||
enable = true;
|
||||
binfmt = true;
|
||||
};
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
# Bureau Cinnamon générique pour la flotte. Adapté (pas importé) depuis
|
||||
# nixos_cinnamon/hosts/cinnamon/desktop-cinnamon.nix : les deux repos
|
||||
# restent indépendants au `git pull`.
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
services.xserver.enable = true;
|
||||
services.xserver.displayManager.lightdm.enable = true;
|
||||
services.xserver.desktopManager.cinnamon.enable = true;
|
||||
services.accounts-daemon.enable = true;
|
||||
|
||||
environment.cinnamon.excludePackages = with pkgs; [
|
||||
celluloid
|
||||
warpinator
|
||||
];
|
||||
|
||||
# Réglages de confidentialité génériques (pas de SearXNG/DNS personnels
|
||||
# comme sur le poste perso — cette config est destinée à être publique).
|
||||
programs.firefox = {
|
||||
enable = true;
|
||||
languagePacks = [ "fr" ];
|
||||
|
||||
preferences = {
|
||||
"privacy.resistFingerprinting" = true;
|
||||
"browser.newtabpage.activity-stream.showSponsored" = false;
|
||||
"browser.search.suggest.enabled" = false;
|
||||
"dom.security.https_only_mode" = true;
|
||||
"media.peerconnection.enabled" = false;
|
||||
"geo.enabled" = false;
|
||||
"network.captive-portal-service.enabled" = false;
|
||||
};
|
||||
|
||||
policies = {
|
||||
DisableTelemetry = true;
|
||||
DisablePocket = true;
|
||||
DisableFirefoxStudies = true;
|
||||
OfferToSaveLogins = false;
|
||||
DontCheckDefaultBrowser = true;
|
||||
EnableTrackingProtection = {
|
||||
Value = true;
|
||||
Locked = true;
|
||||
};
|
||||
ExtensionSettings = {
|
||||
"uBlock0@raymondhill.net" = {
|
||||
install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
|
||||
installation_mode = "force_installed";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
libreoffice
|
||||
thunderbird
|
||||
vlc
|
||||
];
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 146 KiB |
@@ -1,22 +0,0 @@
|
||||
# Profil générique "nixos" : poste non-nominatif de la flotte.
|
||||
# Le module disque (diskos/plain.nix pour une install neuve, ou le
|
||||
# hardware-configuration.nix réel d'une machine déjà installée) est
|
||||
# injecté par flake.nix, pas ici -- ce fichier ne connaît pas le
|
||||
# layout disque, comme dans nixos_users.
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
../common
|
||||
];
|
||||
|
||||
# Compte de session non-nominatif, mot de passe "nixos" stable, sans
|
||||
# expiration forcée (voir hosts/common). Nom d'utilisateur "nixos"
|
||||
# aussi, pour unifier avec le mot de passe -- un seul mot à retenir.
|
||||
users.users.nixos = {
|
||||
isNormalUser = true;
|
||||
description = "Nixos";
|
||||
extraGroups = [ "networkmanager" ];
|
||||
initialPassword = "nixos";
|
||||
};
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
[org/cinnamon]
|
||||
enabled-applets=['panel1:left:0:menu@cinnamon.org:0', 'panel1:left:1:separator@cinnamon.org:1', 'panel1:left:2:grouped-window-list@cinnamon.org:2', 'panel1:right:0:systray@cinnamon.org:3', 'panel1:right:1:xapp-status@cinnamon.org:4', 'panel1:right:2:notifications@cinnamon.org:5', 'panel1:right:3:printers@cinnamon.org:6', 'panel1:right:4:removable-drives@cinnamon.org:7', 'panel1:right:5:keyboard@cinnamon.org:8', 'panel1:right:6:favorites@cinnamon.org:9', 'panel1:right:7:network@cinnamon.org:10', 'panel1:right:8:sound@cinnamon.org:11', 'panel1:right:9:power@cinnamon.org:12', 'panel1:right:10:calendar@cinnamon.org:13', 'panel1:right:11:cornerbar@cinnamon.org:14']
|
||||
next-applet-id=15
|
||||
|
||||
[org/cinnamon/desktop/background]
|
||||
picture-uri='file:///etc/nixos/hosts/common/wallpaper/Nix/13977413.png'
|
||||
|
||||
[org/cinnamon/desktop/background/slideshow]
|
||||
delay=15
|
||||
image-source='directory:///etc/nixos/hosts/common/wallpaper/Nix'
|
||||
|
||||
[org/cinnamon/desktop/input-sources]
|
||||
sources=[('xkb', 'fr')]
|
||||
|
||||
[org/cinnamon/desktop/interface]
|
||||
cursor-size=24
|
||||
cursor-theme='Bibata-Modern-Classic'
|
||||
gtk-theme='Mint-Y-Dark-Blue'
|
||||
icon-theme='Mint-Y-Blue'
|
||||
toolkit-accessibility=false
|
||||
|
||||
[org/cinnamon/desktop/peripherals/mouse]
|
||||
double-click=400
|
||||
drag-threshold=8
|
||||
speed=0.0
|
||||
|
||||
[org/cinnamon/desktop/peripherals/touchpad]
|
||||
natural-scroll=false
|
||||
speed=0.0
|
||||
|
||||
[org/cinnamon/desktop/screensaver]
|
||||
layout-group=0
|
||||
|
||||
[org/cinnamon/desktop/sound]
|
||||
event-sounds=false
|
||||
|
||||
[org/cinnamon/gestures]
|
||||
swipe-down-2='PUSH_TILE_DOWN::end'
|
||||
swipe-down-3='TOGGLE_OVERVIEW::end'
|
||||
swipe-down-4='VOLUME_DOWN::end'
|
||||
swipe-left-2='PUSH_TILE_LEFT::end'
|
||||
swipe-left-3='WORKSPACE_NEXT::end'
|
||||
swipe-left-4='WINDOW_WORKSPACE_PREVIOUS::end'
|
||||
swipe-right-2='PUSH_TILE_RIGHT::end'
|
||||
swipe-right-3='WORKSPACE_PREVIOUS::end'
|
||||
swipe-right-4='WINDOW_WORKSPACE_NEXT::end'
|
||||
swipe-up-2='PUSH_TILE_UP::end'
|
||||
swipe-up-3='TOGGLE_EXPO::end'
|
||||
swipe-up-4='VOLUME_UP::end'
|
||||
tap-3='MEDIA_PLAY_PAUSE::end'
|
||||
|
||||
[org/cinnamon/settings-daemon/plugins/color]
|
||||
night-light-enabled=true
|
||||
night-light-last-coordinates=(48.866667, 2.3333330000000001)
|
||||
night-light-schedule-mode='always'
|
||||
night-light-temperature=uint32 5400
|
||||
|
||||
[org/cinnamon/theme]
|
||||
name='Mint-Y-Dark-Blue'
|
||||
|
||||
[org/gnome/desktop/a11y/applications]
|
||||
screen-reader-enabled=false
|
||||
|
||||
[org/gnome/desktop/a11y/mouse]
|
||||
dwell-click-enabled=false
|
||||
dwell-threshold=10
|
||||
dwell-time=1.2
|
||||
secondary-click-enabled=false
|
||||
secondary-click-time=1.2
|
||||
|
||||
[org/gnome/desktop/input-sources]
|
||||
sources=[('xkb', 'fr')]
|
||||
xkb-options=['terminate:ctrl_alt_bksp']
|
||||
|
||||
[org/gnome/desktop/interface]
|
||||
can-change-accels=false
|
||||
clock-show-date=false
|
||||
clock-show-seconds=false
|
||||
cursor-blink=true
|
||||
cursor-blink-time=1200
|
||||
cursor-blink-timeout=10
|
||||
cursor-size=24
|
||||
cursor-theme='Bibata-Modern-Classic'
|
||||
enable-animations=true
|
||||
font-name='Ubuntu 10'
|
||||
gtk-color-palette='black:white:gray50:red:purple:blue:light blue:green:yellow:orange:lavender:brown:goldenrod4:dodger blue:pink:light green:gray10:gray30:gray75:gray90'
|
||||
gtk-color-scheme=''
|
||||
gtk-enable-primary-paste=true
|
||||
gtk-im-module=''
|
||||
gtk-im-preedit-style='callback'
|
||||
gtk-im-status-style='callback'
|
||||
gtk-key-theme='Default'
|
||||
gtk-theme='Mint-Y-Dark-Blue'
|
||||
gtk-timeout-initial=200
|
||||
gtk-timeout-repeat=20
|
||||
icon-theme='Mint-Y-Blue'
|
||||
menubar-accel='F10'
|
||||
menubar-detachable=false
|
||||
menus-have-tearoff=false
|
||||
scaling-factor=uint32 0
|
||||
text-scaling-factor=1.0
|
||||
toolbar-detachable=false
|
||||
toolbar-icons-size='large'
|
||||
toolbar-style='both-horiz'
|
||||
toolkit-accessibility=false
|
||||
|
||||
[org/gnome/desktop/peripherals/mouse]
|
||||
accel-profile='default'
|
||||
double-click=400
|
||||
drag-threshold=8
|
||||
left-handed=false
|
||||
middle-click-emulation=false
|
||||
natural-scroll=false
|
||||
speed=0.0
|
||||
|
||||
[org/gnome/desktop/privacy]
|
||||
disable-camera=false
|
||||
disable-microphone=false
|
||||
disable-sound-output=false
|
||||
old-files-age=uint32 30
|
||||
recent-files-max-age=7
|
||||
remember-recent-files=true
|
||||
remove-old-temp-files=false
|
||||
remove-old-trash-files=false
|
||||
|
||||
[org/gnome/desktop/sound]
|
||||
event-sounds=false
|
||||
input-feedback-sounds=false
|
||||
theme-name='LinuxMint'
|
||||
|
||||
[org/gnome/desktop/wm/preferences]
|
||||
action-double-click-titlebar='toggle-maximize'
|
||||
action-middle-click-titlebar='lower'
|
||||
action-right-click-titlebar='menu'
|
||||
audible-bell=false
|
||||
auto-raise=false
|
||||
auto-raise-delay=500
|
||||
button-layout=':minimize,maximize,close'
|
||||
disable-workarounds=false
|
||||
focus-mode='click'
|
||||
focus-new-windows='smart'
|
||||
mouse-button-modifier='<Alt>'
|
||||
num-workspaces=4
|
||||
raise-on-click=true
|
||||
resize-with-right-button=true
|
||||
theme='Mint-Y'
|
||||
titlebar-font='Ubuntu Medium 10'
|
||||
titlebar-uses-system-font=false
|
||||
visual-bell=false
|
||||
visual-bell-type='fullscreen-flash'
|
||||
workspace-names=@as []
|
||||
|
||||
[org/gnome/file-roller/dialogs/extract]
|
||||
height=800
|
||||
recreate-folders=true
|
||||
skip-newer=false
|
||||
width=1000
|
||||
|
||||
[org/gnome/file-roller/file-selector]
|
||||
show-hidden=false
|
||||
sidebar-size=300
|
||||
sort-method='name'
|
||||
sort-type='ascending'
|
||||
window-size=(-1, -1)
|
||||
|
||||
[org/gnome/file-roller/listing]
|
||||
list-mode='as-folder'
|
||||
name-column-width=57
|
||||
show-path=false
|
||||
sort-method='name'
|
||||
sort-type='ascending'
|
||||
|
||||
[org/gnome/file-roller/ui]
|
||||
sidebar-width=200
|
||||
window-height=480
|
||||
window-width=600
|
||||
|
||||
[org/gtk/settings/file-chooser]
|
||||
date-format='regular'
|
||||
location-mode='path-bar'
|
||||
show-hidden=false
|
||||
show-size-column=true
|
||||
show-type-column=true
|
||||
sidebar-width=176
|
||||
sort-column='name'
|
||||
sort-directories-first=true
|
||||
sort-order='ascending'
|
||||
type-format='category'
|
||||
window-position=(202, 73)
|
||||
window-size=(1096, 822)
|
||||
|
||||
[org/nemo/window-state]
|
||||
geometry='800x550+349+125'
|
||||
maximized=false
|
||||
sidebar-bookmark-breakpoint=3
|
||||
start-with-sidebar=true
|
||||
|
||||
[org/x/apps/portal]
|
||||
color-scheme='prefer-dark'
|
||||
@@ -0,0 +1,20 @@
|
||||
# Profil générique "usagers" : poste non-nominatif de la flotte.
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
../common/base.nix
|
||||
../common/accounts.nix
|
||||
../common/desktop.nix
|
||||
../../diskos/plain.nix
|
||||
];
|
||||
|
||||
# Compte de session non-nominatif, mot de passe "nixos" stable, sans
|
||||
# expiration forcée (voir hosts/common/accounts.nix).
|
||||
users.users.usagers = {
|
||||
isNormalUser = true;
|
||||
description = "Usagers";
|
||||
extraGroups = [ "networkmanager" ];
|
||||
initialPassword = "nixos";
|
||||
};
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Claude Code Windows CMD Bootstrap Script
|
||||
REM Installs Claude Code for environments where PowerShell is not available
|
||||
|
||||
REM Parse command line argument
|
||||
set "TARGET=%~1"
|
||||
if "!TARGET!"=="" set "TARGET=latest"
|
||||
|
||||
REM Validate target parameter
|
||||
if /i "!TARGET!"=="stable" goto :target_valid
|
||||
if /i "!TARGET!"=="latest" goto :target_valid
|
||||
echo !TARGET! | findstr /r "^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" >nul
|
||||
if !ERRORLEVEL! equ 0 goto :target_valid
|
||||
|
||||
echo Usage: %0 [stable^|latest^|VERSION] >&2
|
||||
echo Example: %0 1.0.58 >&2
|
||||
exit /b 1
|
||||
|
||||
:target_valid
|
||||
|
||||
REM Check for 64-bit Windows
|
||||
if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64" goto :arch_valid
|
||||
if /i "%PROCESSOR_ARCHITECTURE%"=="ARM64" goto :arch_valid
|
||||
if /i "%PROCESSOR_ARCHITEW6432%"=="AMD64" goto :arch_valid
|
||||
if /i "%PROCESSOR_ARCHITEW6432%"=="ARM64" goto :arch_valid
|
||||
|
||||
echo Claude Code does not support 32-bit Windows. Please use a 64-bit version of Windows. >&2
|
||||
exit /b 1
|
||||
|
||||
:arch_valid
|
||||
|
||||
REM Set constants
|
||||
set "DOWNLOAD_BASE_URL=https://downloads.claude.ai/claude-code-releases"
|
||||
set "DOWNLOAD_DIR=%USERPROFILE%\.claude\downloads"
|
||||
REM Use native ARM64 binary on ARM64 Windows, x64 otherwise
|
||||
if /i "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
|
||||
set "PLATFORM=win32-arm64"
|
||||
) else (
|
||||
set "PLATFORM=win32-x64"
|
||||
)
|
||||
|
||||
REM Create download directory
|
||||
if not exist "!DOWNLOAD_DIR!" mkdir "!DOWNLOAD_DIR!"
|
||||
|
||||
REM Check for curl availability
|
||||
curl --version >nul 2>&1
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo curl is required but not available. Please install curl or use PowerShell installer. >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Always download latest version (which has the most up-to-date installer)
|
||||
call :download_file "!DOWNLOAD_BASE_URL!/latest" "!DOWNLOAD_DIR!\latest"
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo Failed to get latest version >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Reject non-version content (e.g. an HTML error page) before it reaches the manifest URL
|
||||
findstr /r "^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" "!DOWNLOAD_DIR!\latest" >nul
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
del "!DOWNLOAD_DIR!\latest"
|
||||
echo Failed to get a valid version from downloads.claude.ai - got unexpected content. >&2
|
||||
echo This can happen if the download service is unreachable or not available in your region - see https://www.anthropic.com/supported-countries >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Read version from file
|
||||
set /p VERSION=<"!DOWNLOAD_DIR!\latest"
|
||||
del "!DOWNLOAD_DIR!\latest"
|
||||
|
||||
REM Download manifest
|
||||
call :download_file "!DOWNLOAD_BASE_URL!/!VERSION!/manifest.json" "!DOWNLOAD_DIR!\manifest.json"
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo Failed to get manifest >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Extract checksum from manifest
|
||||
call :parse_manifest "!DOWNLOAD_DIR!\manifest.json" "!PLATFORM!"
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo Platform !PLATFORM! not found in manifest >&2
|
||||
del "!DOWNLOAD_DIR!\manifest.json" 2>nul
|
||||
exit /b 1
|
||||
)
|
||||
del "!DOWNLOAD_DIR!\manifest.json"
|
||||
|
||||
REM Download binary
|
||||
set "BINARY_PATH=!DOWNLOAD_DIR!\claude-!VERSION!-!PLATFORM!.exe"
|
||||
call :download_file "!DOWNLOAD_BASE_URL!/!VERSION!/!PLATFORM!/claude.exe" "!BINARY_PATH!"
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo Failed to download binary >&2
|
||||
if exist "!BINARY_PATH!" del "!BINARY_PATH!"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Verify checksum
|
||||
call :verify_checksum "!BINARY_PATH!" "!EXPECTED_CHECKSUM!"
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo Checksum verification failed >&2
|
||||
del "!BINARY_PATH!"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Run claude install to set up launcher and shell integration
|
||||
echo Setting up Claude Code...
|
||||
"!BINARY_PATH!" install "!TARGET!"
|
||||
set "INSTALL_RESULT=!ERRORLEVEL!"
|
||||
|
||||
REM Clean up downloaded file
|
||||
REM Wait a moment for any file handles to be released
|
||||
timeout /t 1 /nobreak >nul 2>&1
|
||||
del /f "!BINARY_PATH!" >nul 2>&1
|
||||
if exist "!BINARY_PATH!" (
|
||||
echo Warning: Could not remove temporary file: !BINARY_PATH!
|
||||
)
|
||||
|
||||
if !INSTALL_RESULT! neq 0 (
|
||||
echo Installation failed >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Installation complete^^!
|
||||
echo.
|
||||
exit /b 0
|
||||
|
||||
REM ============================================================================
|
||||
REM SUBROUTINES
|
||||
REM ============================================================================
|
||||
|
||||
:download_file
|
||||
REM Downloads a file using curl
|
||||
REM Args: %1=URL, %2=OutputPath
|
||||
set "URL=%~1"
|
||||
set "OUTPUT=%~2"
|
||||
|
||||
REM Capture stderr so a certificate-revocation failure can be detected below
|
||||
curl -fsSL "!URL!" -o "!OUTPUT!" 2>"!OUTPUT!.stderr"
|
||||
set "CURL_RESULT=!ERRORLEVEL!"
|
||||
if !CURL_RESULT! equ 0 goto :download_file_done
|
||||
|
||||
REM Some networks (commonly corporate ones) block the endpoints Windows uses
|
||||
REM to check whether a TLS certificate has been revoked. curl's schannel
|
||||
REM backend then fails with CRYPT_E_NO_REVOCATION_CHECK (0x80092012) or
|
||||
REM CRYPT_E_REVOCATION_OFFLINE (0x80092013). Retry once with best-effort
|
||||
REM revocation checking - the soft-fail behavior browsers use: an unreachable
|
||||
REM revocation server is tolerated, but a certificate that is known to be
|
||||
REM revoked is still rejected.
|
||||
findstr /i /c:"0x80092012" /c:"0x80092013" "!OUTPUT!.stderr" >nul
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
type "!OUTPUT!.stderr" >&2
|
||||
goto :download_file_done
|
||||
)
|
||||
|
||||
echo This network appears to block certificate revocation checks. Retrying the download with best-effort revocation checking... >&2
|
||||
curl -fsSL --ssl-revoke-best-effort "!URL!" -o "!OUTPUT!"
|
||||
set "CURL_RESULT=!ERRORLEVEL!"
|
||||
REM If the retry also failed, re-emit the original error so the actionable
|
||||
REM schannel diagnostic is not lost
|
||||
if !CURL_RESULT! neq 0 type "!OUTPUT!.stderr" >&2
|
||||
|
||||
:download_file_done
|
||||
del "!OUTPUT!.stderr" >nul 2>&1
|
||||
exit /b !CURL_RESULT!
|
||||
|
||||
:parse_manifest
|
||||
REM Parse JSON manifest to extract checksum for platform
|
||||
REM Args: %1=ManifestPath, %2=Platform
|
||||
set "MANIFEST_PATH=%~1"
|
||||
set "PLATFORM_NAME=%~2"
|
||||
set "EXPECTED_CHECKSUM="
|
||||
|
||||
REM Use findstr to find platform section, then look for checksum
|
||||
set "FOUND_PLATFORM="
|
||||
set "IN_PLATFORM_SECTION="
|
||||
|
||||
REM Read the manifest line by line
|
||||
for /f "usebackq tokens=*" %%i in ("!MANIFEST_PATH!") do (
|
||||
set "LINE=%%i"
|
||||
|
||||
REM Check if this line contains our platform
|
||||
echo !LINE! | findstr /c:"\"%PLATFORM_NAME%\":" >nul
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
set "IN_PLATFORM_SECTION=1"
|
||||
)
|
||||
|
||||
REM If we're in the platform section, look for checksum
|
||||
if defined IN_PLATFORM_SECTION (
|
||||
echo !LINE! | findstr /c:"\"checksum\":" >nul
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
REM Extract checksum value
|
||||
for /f "tokens=2 delims=:" %%j in ("!LINE!") do (
|
||||
set "CHECKSUM_PART=%%j"
|
||||
REM Remove quotes, whitespace, and comma
|
||||
set "CHECKSUM_PART=!CHECKSUM_PART: =!"
|
||||
set "CHECKSUM_PART=!CHECKSUM_PART:"=!"
|
||||
set "CHECKSUM_PART=!CHECKSUM_PART:,=!"
|
||||
|
||||
REM Check if it looks like a SHA256 (64 hex chars)
|
||||
if not "!CHECKSUM_PART!"=="" (
|
||||
call :check_length "!CHECKSUM_PART!" 64
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
set "EXPECTED_CHECKSUM=!CHECKSUM_PART!"
|
||||
exit /b 0
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM Check if we've left the platform section (closing brace)
|
||||
echo !LINE! | findstr /c:"}" >nul
|
||||
if !ERRORLEVEL! equ 0 set "IN_PLATFORM_SECTION="
|
||||
)
|
||||
)
|
||||
|
||||
if "!EXPECTED_CHECKSUM!"=="" exit /b 1
|
||||
exit /b 0
|
||||
|
||||
:check_length
|
||||
REM Check if string length equals expected length
|
||||
REM Args: %1=String, %2=ExpectedLength
|
||||
set "STR=%~1"
|
||||
set "EXPECTED_LEN=%~2"
|
||||
set "LEN=0"
|
||||
:count_loop
|
||||
if "!STR:~%LEN%,1!"=="" goto :count_done
|
||||
set /a LEN+=1
|
||||
goto :count_loop
|
||||
:count_done
|
||||
if %LEN%==%EXPECTED_LEN% exit /b 0
|
||||
exit /b 1
|
||||
|
||||
:verify_checksum
|
||||
REM Verify file checksum using certutil
|
||||
REM Args: %1=FilePath, %2=ExpectedChecksum
|
||||
set "FILE_PATH=%~1"
|
||||
set "EXPECTED=%~2"
|
||||
|
||||
for /f "skip=1 tokens=*" %%i in ('certutil -hashfile "!FILE_PATH!" SHA256') do (
|
||||
set "ACTUAL=%%i"
|
||||
set "ACTUAL=!ACTUAL: =!"
|
||||
if "!ACTUAL!"=="CertUtil:Thecommandcompletedsuccessfully." goto :verify_done
|
||||
if "!ACTUAL!" neq "" (
|
||||
if /i "!ACTUAL!"=="!EXPECTED!" (
|
||||
exit /b 0
|
||||
) else (
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
:verify_done
|
||||
exit /b 1
|
||||
+31
-107
@@ -1,113 +1,38 @@
|
||||
# Injecté comme corps de script par pkgs.writeShellScriptBin
|
||||
# (installer-base.nix) : pas de mode strict par défaut, d'où le `set`
|
||||
# explicite ci-dessous.
|
||||
# Pas de shebang ici : ce fichier est injecté comme corps de script par
|
||||
# pkgs.writeShellScriptBin dans installer-base.nix (qui ajoute juste
|
||||
# `#!...`, PAS de mode strict — d'où le `set` explicite ci-dessous : sans
|
||||
# lui, une erreur (ex: DNS indisponible) n'arrête pas le script, qui
|
||||
# continuait jusqu'au reboot final en masquant l'échec.
|
||||
set -euo pipefail
|
||||
|
||||
# Installeur unique pour les deux repos :
|
||||
# - branche "flotte" -> nixos_fleet (public, jamais chiffré)
|
||||
# - branche "utilisateur nommé" -> nixos_users (privé, chiffrement au choix)
|
||||
# Le token du repo privé est saisi en direct, jamais stocké dans ce
|
||||
# fichier ni dans un repo git.
|
||||
# Ce script ne contient aucun secret : le token du repo privé est saisi en
|
||||
# direct, jamais stocké dans ce fichier ni dans un repo git.
|
||||
|
||||
FLEET_REPO="git+https://gitea.miniclic.net/Damien/Nixos_Fleet.git"
|
||||
USERS_REPO_HOST="gitea.miniclic.net"
|
||||
USERS_REPO_PATH="Damien/Nixos_Users.git"
|
||||
|
||||
# Le script se lance dès le login (autologin), potentiellement avant que
|
||||
# NetworkManager ait fini de négocier le DHCP.
|
||||
# NetworkManager ait fini de négocier le DHCP. On attend une route par
|
||||
# défaut avant de continuer (jusqu'à 30s), plutôt que d'échouer trop tôt.
|
||||
echo "Attente du réseau..."
|
||||
for _ in $(seq 1 30); do
|
||||
ip route | grep -q '^default' && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 4 étapes séparées (clone -> partition -> swap sur le disque cible ->
|
||||
# build) plutôt qu'un seul `disko-install` : ce dernier construit tout
|
||||
# dans le tmpfs RAM de l'environnement live, qui sature sur les PC à
|
||||
# 4 Go avant même de toucher le disque cible. Le swapfile posé sur /mnt
|
||||
# donne accès au disque comme mémoire virtuelle pendant le build.
|
||||
#
|
||||
# Le clone local est nécessaire : `disko` seul (contrairement à
|
||||
# `disko-install`) n'a aucun mécanisme de substitution de disque nommé.
|
||||
# On substitue donc le vrai disque dans une copie locale du fichier
|
||||
# disko (sed), puis `disko` et `nixos-install` pointent tous les deux
|
||||
# sur ce même clone.
|
||||
do_install() {
|
||||
local remote_url="$1" # URL flake (git+https://...), pour cloner
|
||||
local attr="$2"
|
||||
local disko_rel_path="$3" # diskos/plain.nix ou diskos/luks.nix
|
||||
|
||||
local repo_dir="/tmp/install-repo"
|
||||
local clone_url="${remote_url#git+}" # git clone ne comprend pas le préfixe git+
|
||||
|
||||
echo "Récupération de la config ($attr)..."
|
||||
rm -rf "$repo_dir"
|
||||
git clone --depth 1 "$clone_url" "$repo_dir"
|
||||
|
||||
echo "Substitution du disque cible dans la config disko..."
|
||||
sed -i "s#/dev/placeholder#$DEVICE#" "$repo_dir/$disko_rel_path"
|
||||
|
||||
echo "Partitionnement de $DEVICE..."
|
||||
# --yes-wipe-all-disks : évite la confirmation interactive, un install
|
||||
# de flotte ne doit dépendre de personne au clavier.
|
||||
nix run --no-write-lock-file --refresh 'github:nix-community/disko' -- \
|
||||
--mode destroy,format,mount \
|
||||
--yes-wipe-all-disks \
|
||||
"$repo_dir/$disko_rel_path"
|
||||
|
||||
echo "Swap temporaire sur le disque cible (RAM limitée sur certains PC du parc)..."
|
||||
# btrfs : chattr +C (NOCOW) doit précéder toute écriture dans le
|
||||
# fichier, sinon swapon échoue avec "Invalid argument".
|
||||
truncate -s 0 /mnt/.install-swapfile
|
||||
chattr +C /mnt/.install-swapfile
|
||||
fallocate -l 4G /mnt/.install-swapfile
|
||||
chmod 600 /mnt/.install-swapfile
|
||||
mkswap /mnt/.install-swapfile
|
||||
swapon /mnt/.install-swapfile
|
||||
|
||||
echo "Construction et installation du système (peut prendre plusieurs minutes)..."
|
||||
# --no-root-passwd : le mot de passe root est déjà déclaré
|
||||
# (initialPassword) côté cible, on évite juste un prompt interactif.
|
||||
# --flake pointe sur le clone local (disque déjà substitué).
|
||||
nixos-install --root /mnt --flake "${repo_dir}#${attr}" --no-channel-copy --no-root-passwd
|
||||
|
||||
swapoff /mnt/.install-swapfile
|
||||
rm -f /mnt/.install-swapfile
|
||||
|
||||
# Persiste la config sur le disque cible : sans ça, le clone ne vit
|
||||
# que dans /tmp du live (RAM), effacé au reboot -- plus rien à éditer
|
||||
# ni à `git pull` ensuite. system.autoUpgrade continue de fonctionner
|
||||
# sans ça (il pointe sur l'URL distante), mais un technicien n'a alors
|
||||
# aucun accès local.
|
||||
echo "Installation de la configuration dans /mnt/etc/nixos..."
|
||||
rm -rf /mnt/etc/nixos
|
||||
mkdir -p /mnt/etc
|
||||
cp -a "$repo_dir" /mnt/etc/nixos
|
||||
|
||||
# installer/ (script d'install, obsolète une fois fait) et
|
||||
# pxe-server/ (config d'un autre host) ne servent à rien sur un poste
|
||||
# client -- ne concerne que nixos_fleet, ces rm sont des no-op côté
|
||||
# nixos_users.
|
||||
rm -rf /mnt/etc/nixos/installer /mnt/etc/nixos/pxe-server
|
||||
|
||||
# Committé en local (jamais poussé) pour que `git status` reste propre
|
||||
# sur la machine installée.
|
||||
git -C /mnt/etc/nixos \
|
||||
-c user.name="Installeur Nixos_Fleet" \
|
||||
-c user.email="installeur@nixos-fleet.local" \
|
||||
commit -aqm "Config locale : disque substitué ($DEVICE), installer/pxe-server retirés (inutiles sur ce poste)"
|
||||
|
||||
INSTALLED_ATTR="$attr"
|
||||
}
|
||||
|
||||
echo "=== Installeur Nixos_Fleet ==="
|
||||
echo "1) PC de la flotte (générique)"
|
||||
echo "2) Poste utilisateur nommé"
|
||||
read -rp "Choix [1/2] : " CHOICE
|
||||
|
||||
# --- Détection automatique du disque cible -------------------------------
|
||||
# Exclut les disques amovibles (la clé USB de boot) et les loop/rom.
|
||||
# S'il en reste plusieurs, on prend le plus gros et on le signale.
|
||||
# On exclut les disques amovibles (la clé USB de boot elle-même) et les
|
||||
# loop/rom. S'il en reste plusieurs (rare), on prend le plus gros et on le
|
||||
# signale clairement dans les logs plutôt que de bloquer sur un menu.
|
||||
echo
|
||||
echo "Détection du disque cible..."
|
||||
mapfile -t CANDIDATES < <(
|
||||
@@ -131,16 +56,20 @@ fi
|
||||
|
||||
case "$CHOICE" in
|
||||
1)
|
||||
# --- Branche flotte : rien d'autre à saisir -----------------------
|
||||
echo
|
||||
echo "Installation du profil générique 'nixos' sur $DEVICE..."
|
||||
do_install "$FLEET_REPO" "nixos" "diskos/plain.nix"
|
||||
echo "Installation du profil générique 'usagers' sur $DEVICE..."
|
||||
nix run --no-write-lock-file --refresh 'github:nix-community/disko#disko-install' -- --write-efi-boot-entries \
|
||||
--flake "${FLEET_REPO}#usagers" \
|
||||
--disk main "$DEVICE"
|
||||
;;
|
||||
|
||||
2)
|
||||
read -rp "Utilisateur (thomas/clara/jade/oreo/oreo-i3) : " USER_PROFILE
|
||||
# --- Branche utilisateur nommé -------------------------------------
|
||||
read -rp "Utilisateur (thomas/clara/jade/oreo) : " USER_PROFILE
|
||||
USER_PROFILE=$(echo "$USER_PROFILE" | tr '[:upper:]' '[:lower:]' | xargs)
|
||||
case "$USER_PROFILE" in
|
||||
thomas|clara|jade|oreo|oreo-i3) ;;
|
||||
thomas|clara|jade|oreo) ;;
|
||||
*)
|
||||
echo "Utilisateur inconnu : $USER_PROFILE" >&2
|
||||
exec bash
|
||||
@@ -156,26 +85,26 @@ case "$CHOICE" in
|
||||
read -rsp "Token Gitea (repo privé Nixos_Users) : " TOKEN
|
||||
echo
|
||||
|
||||
# Utilisé seulement en mémoire/ligne de commande pour ce fetch
|
||||
# ponctuel, jamais écrit dans un fichier suivi par git.
|
||||
# Le token n'est utilisé qu'en mémoire/ligne de commande pour ce fetch
|
||||
# ponctuel : il n'est jamais écrit dans un fichier suivi par git.
|
||||
AUTHED_URL="git+https://${TOKEN}@${USERS_REPO_HOST}/${USERS_REPO_PATH}"
|
||||
|
||||
echo
|
||||
echo "Installation du profil '$ATTR' sur $DEVICE..."
|
||||
DISKO_REL_PATH="diskos/plain.nix"
|
||||
if [[ "$ATTR" == *-luks ]]; then
|
||||
echo "Disque chiffré : disko va demander une passphrase LUKS pendant le partitionnement."
|
||||
DISKO_REL_PATH="diskos/luks.nix"
|
||||
fi
|
||||
do_install "$AUTHED_URL" "$ATTR" "$DISKO_REL_PATH"
|
||||
nix run --no-write-lock-file --refresh 'github:nix-community/disko#disko-install' -- --write-efi-boot-entries \
|
||||
--flake "${AUTHED_URL}#${ATTR}" \
|
||||
--disk main "$DEVICE"
|
||||
|
||||
# Netrc laissé pour que le système installé continue de tirer ses
|
||||
# mises à jour du repo privé tout seul (nix.settings.netrc-file
|
||||
# pointe vers ce même chemin côté nixos_users). Jamais commité,
|
||||
# jamais dans le Nix store.
|
||||
# Le système installé doit pouvoir continuer à tirer ses mises à jour
|
||||
# du repo privé tout seul : on lui laisse un netrc, jamais commité,
|
||||
# jamais dans le Nix store (nix.settings.netrc-file dans le repo
|
||||
# nixos_users pointe vers ce même chemin).
|
||||
#
|
||||
# ATTENTION : le format exact attendu par Gitea (login=token vs
|
||||
# login+password séparés) dépend de la version/config du serveur --
|
||||
# login+password séparés) dépend de la version/config du serveur —
|
||||
# à vérifier sur l'instance réelle avant le déploiement pilote.
|
||||
mkdir -p /mnt/etc/nix
|
||||
{
|
||||
@@ -193,11 +122,6 @@ case "$CHOICE" in
|
||||
esac
|
||||
|
||||
echo
|
||||
echo "Installation terminée."
|
||||
echo "Config installée dans /etc/nixos (attribut : ${INSTALLED_ATTR})."
|
||||
echo "Pour ajouter/enlever un paquet après le premier démarrage :"
|
||||
echo " 1. éditer les fichiers dans /etc/nixos"
|
||||
echo " 2. nixos-rebuild switch --flake /etc/nixos#${INSTALLED_ATTR}"
|
||||
echo "Redémarrage dans 10s (Ctrl-C pour annuler et inspecter /mnt)..."
|
||||
echo "Installation terminée. Redémarrage dans 10s (Ctrl-C pour annuler et inspecter /mnt)..."
|
||||
sleep 10
|
||||
reboot
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Environnement live commun aux deux formes d'image (PXE netboot / ISO
|
||||
# USB). Contient juste de quoi partitionner et installer ; la config
|
||||
# cible est toujours récupérée depuis git au moment de l'install (repo
|
||||
# public nixos_fleet ou repo privé nixos_users).
|
||||
# USB). Contient juste de quoi partitionner et lancer disko-install ; la
|
||||
# vraie configuration cible est toujours récupérée depuis git au moment
|
||||
# de l'install (repo public nixos_fleet ou repo privé nixos_users).
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
@@ -13,13 +13,22 @@ in
|
||||
"flakes"
|
||||
];
|
||||
|
||||
# netboot.nix définit "/" et "/nix/.rw-store" (overlay où atterrissent
|
||||
# les paquets téléchargés/construits pendant l'install) sans taille
|
||||
# explicite -> 50% de la RAM chacun par défaut. .rw-store sature en
|
||||
# premier sur un système complet (Cinnamon + Firefox + LibreOffice +
|
||||
# VLC), pas "/". mkForce nécessaire : netboot.nix utilise
|
||||
# mkImageMediaOverride (priorité 60), une assignation simple (100) est
|
||||
# ignorée ; mkForce remplace toute la liste, d'où mode=0755 remis ici.
|
||||
# netboot.nix définit DEUX tmpfs distincts, ni l'un ni l'autre avec une
|
||||
# taille explicite -> plafonnés à 50% de la RAM chacun par défaut :
|
||||
# - "/" : la racine du système live lui-même (petite)
|
||||
# - "/nix/.rw-store" : la vraie zone où atterrissent tous les nouveaux
|
||||
# paquets téléchargés/construits pendant l'install
|
||||
# (overlay au-dessus du squashfs .ro-store en
|
||||
# lecture seule). C'est CELLE-LÀ qui sature en
|
||||
# premier sur un système complet (Cinnamon +
|
||||
# Firefox + LibreOffice + VLC...), pas "/".
|
||||
#
|
||||
# netboot.nix définit ces deux options via mkImageMediaOverride, qui vaut
|
||||
# mkOverride 60 — une simple assignation ici (priorité 100 par défaut)
|
||||
# est donc entièrement ignorée, pas fusionnée (constaté : le premier
|
||||
# essai n'a eu aucun effet). Il faut lib.mkForce (priorité 50, plus
|
||||
# fort que 60) pour vraiment gagner — et comme mkForce REMPLACE toute la
|
||||
# liste plutôt que de la compléter, on remet mode=0755 dedans aussi.
|
||||
fileSystems."/".options = lib.mkForce [
|
||||
"mode=0755"
|
||||
"size=20%"
|
||||
@@ -29,9 +38,11 @@ in
|
||||
"size=90%"
|
||||
];
|
||||
|
||||
# Certains PC du parc n'ont que 4 Go de RAM. Le disque cible ne peut
|
||||
# pas servir de secours ici (pas encore partitionné à ce stade) : swap
|
||||
# compressé en RAM (zram), identique en PXE et en USB.
|
||||
# Certains PC du parc n'ont que 4 Go de RAM. Le disque cible ne peut pas
|
||||
# servir de secours (disko-install l'efface entièrement en une seule
|
||||
# opération, un swap posé dessus avant serait détruit en cours de
|
||||
# route) : on utilise du swap compressé en RAM (zram) à la place, qui
|
||||
# marche pareil en PXE et en USB sans dépendre d'un périphérique externe.
|
||||
zramSwap = {
|
||||
enable = true;
|
||||
memoryPercent = 100; # jusqu'à 1x la RAM en swap compressé (~2-3x effectif)
|
||||
@@ -41,19 +52,26 @@ in
|
||||
git
|
||||
parted
|
||||
gptfdisk
|
||||
util-linux # lsblk, fallocate
|
||||
e2fsprogs # chattr (NOCOW pour le swapfile sur btrfs)
|
||||
util-linux # lsblk
|
||||
installScript
|
||||
];
|
||||
|
||||
# disko-install n'est pas préinstallé : install.sh l'invoque via
|
||||
# `nix run github:nix-community/disko#disko-install`, ce qui évite de
|
||||
# dépendre d'un nom de paquet nixpkgs précis (pas de package `disko`
|
||||
# garanti tel quel côté nixpkgs) et reste conforme à l'usage documenté
|
||||
# du projet.
|
||||
|
||||
networking.networkmanager.enable = true;
|
||||
services.xserver.xkb.layout = "fr";
|
||||
console.keyMap = "fr";
|
||||
|
||||
# Root autologin sur tty1, l'installeur se lance automatiquement au
|
||||
# login. Échec ou sortie -> shell root normal pour dépanner.
|
||||
# login. S'il échoue ou qu'on le quitte, on retombe sur un shell root
|
||||
# normal pour dépanner à la main — pas de piège sans issue.
|
||||
# mkForce : le profil installeur officiel (installation-device.nix)
|
||||
# définit aussi cette option, même priorité -> conflit sinon.
|
||||
# définit aussi cette option (autologin "nixos"), même priorité que la
|
||||
# nôtre sans mkForce -> conflit sinon.
|
||||
services.getty.autologinUser = lib.mkForce "root";
|
||||
users.users.root.password = lib.mkForce "";
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
# ANSSI compliance module
|
||||
|
||||
This is a self-contained NixOS implementation of <https://messervices.cyber.gouv.fr/guides/recommandations-de-securite-relatives-un-systeme-gnulinux>.
|
||||
It can be copy-pasted outside of Sécurix for your own needs.
|
||||
|
||||
> Vendored depuis [SécurixOS](https://github.com/cloud-gouv/securix)
|
||||
> (`modules/anssi/`, alpha), 2026-09-09. Ne pas éditer directement --
|
||||
> reprendre depuis Sécurix en cas de correctif.
|
||||
@@ -1,15 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
{
|
||||
# Default exceptions for MACs.
|
||||
security.anssi.exceptions = {
|
||||
R37.rationale = "No MAC implementation is available on NixOS at the moment.";
|
||||
R45.rationale = "AppArmor is partially supported on NixOS.";
|
||||
R46.rationale = "SELinux is unsupported on NixOS.";
|
||||
R47.rationale = "SELinux is unsupported on NixOS.";
|
||||
R48.rationale = "SELinux is unsupported on NixOS.";
|
||||
R49.rationale = "SELinux is unsupported on NixOS.";
|
||||
};
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
{
|
||||
imports = [
|
||||
./options.nix
|
||||
./base.nix
|
||||
];
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
{
|
||||
# rulesets: a set of rules, e.g., { R1 = {...}; R2 = {...}; ... }
|
||||
generateOptionHierarchy =
|
||||
rulesets:
|
||||
let
|
||||
levelMapping = {
|
||||
"minimal" = 0;
|
||||
"intermediary" = 1;
|
||||
"reinforced" = 2;
|
||||
"high" = 3;
|
||||
};
|
||||
# helper to generate one rule's option set
|
||||
makeRuleOption =
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
ruleId: rule:
|
||||
{
|
||||
anssiRef = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = rule.anssiRef;
|
||||
readOnly = true;
|
||||
description = "ANSSI reference for rule ${rule.name}";
|
||||
};
|
||||
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default =
|
||||
config.security.anssi.enable
|
||||
&& levelMapping.${config.security.anssi.level} >= levelMapping.${rule.severity}
|
||||
&& (rule.category == "base" || rule.category == config.security.anssi.category)
|
||||
&& lib.all (t: !(lib.elem t config.security.anssi.excludes)) (rule.tags or [ ])
|
||||
&& (
|
||||
((rule.architectures or [ ]) != [ ])
|
||||
-> lib.elem pkgs.stdenv.hostPlatform.linuxArch rule.architectures
|
||||
)
|
||||
&& !config.security.anssi.exceptions ? ${ruleId};
|
||||
|
||||
description = ''
|
||||
Enable rule ${rule.name}.
|
||||
${rule.description}
|
||||
ANSSI reference: ${rule.anssiRef}.
|
||||
'';
|
||||
};
|
||||
|
||||
exceptionRationale = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default =
|
||||
if config.security.anssi.exceptions ? ${ruleId} then
|
||||
config.security.anssi.exceptions.${ruleId}.rationale
|
||||
else
|
||||
null;
|
||||
readOnly = true;
|
||||
description = "Optional rationale for disabling or modifying rule ${rule.name}";
|
||||
};
|
||||
|
||||
checkScript = lib.mkOption {
|
||||
type = lib.types.path; # TODO: or string if we store the script inline
|
||||
default = rule.checkScript pkgs;
|
||||
description = "Check script for rule ${rule.name}";
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (rule ? implementations) {
|
||||
implementation = lib.mkOption {
|
||||
type = lib.types.enum (lib.attrNames rule.implementations);
|
||||
description = "Chosen implementation for this rule.";
|
||||
};
|
||||
};
|
||||
|
||||
# map each rule in rulesets to the generated option hierarchy
|
||||
ruleOptions =
|
||||
moduleArgs: moduleArgs.lib.mapAttrs (ruleId: rule: makeRuleOption moduleArgs ruleId rule) rulesets;
|
||||
ruleEnabled = config: ruleId: config.security.anssi.rules.${ruleId}.enable;
|
||||
enabledRuleConfigs =
|
||||
moduleArgs:
|
||||
moduleArgs.lib.mapAttrsToList (
|
||||
ruleId: rule:
|
||||
let
|
||||
complexConfigTerm =
|
||||
cfg:
|
||||
moduleArgs.lib.mkMerge (
|
||||
moduleArgs.lib.mapAttrsToList (
|
||||
implName: _:
|
||||
moduleArgs.lib.mkIf (moduleArgs.config.security.anssi.rules.${ruleId}.implementation == implName) (
|
||||
rule.implementations.${implName}.config cfg
|
||||
)
|
||||
) rule.implementations
|
||||
);
|
||||
simpleConfigFn = rule.config;
|
||||
configFn = if rule ? implementations then complexConfigTerm else simpleConfigFn;
|
||||
in
|
||||
moduleArgs.lib.mkIf (ruleEnabled moduleArgs.config ruleId) (configFn moduleArgs)
|
||||
) rulesets;
|
||||
in
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}@moduleArgs:
|
||||
{
|
||||
options.security.anssi.rules = ruleOptions moduleArgs;
|
||||
config = lib.mkMerge (enabledRuleConfigs moduleArgs);
|
||||
};
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
let
|
||||
mkSysctlChecker =
|
||||
lib: attrs:
|
||||
let
|
||||
# Normalize whitespace (tabs to spaces, collapse runs, trim edges)
|
||||
# so that sysctl output like "32768\t65535" matches Nix value "32768 65535".
|
||||
normalizeWhitespace = "tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//' ";
|
||||
mkCheckSingularSysctl = attr: expectedValue: ''
|
||||
# Check for sysctl '${attr}'
|
||||
actual_value=$(sysctl -n "${attr}" | ${normalizeWhitespace}) || {
|
||||
echo "Check failed for ${attr}: unable to read sysctl value"
|
||||
exit 1
|
||||
}
|
||||
expected_value=$(echo "${toString expectedValue}" | ${normalizeWhitespace})
|
||||
if [[ "$actual_value" != "$expected_value" ]]; then
|
||||
echo "Check failed for ${attr}: expected $expected_value, got $actual_value"
|
||||
exit 1
|
||||
else
|
||||
echo "Check passed for ${attr}"
|
||||
fi
|
||||
'';
|
||||
in
|
||||
''
|
||||
${lib.concatStringsSep "\n" (
|
||||
map (name: mkCheckSingularSysctl name attrs.${name}) (lib.attrNames attrs)
|
||||
)}
|
||||
'';
|
||||
in
|
||||
{
|
||||
R8 = {
|
||||
name = "R8_MemoryBootOptions";
|
||||
anssiRef = "R8 – Paramétrer les options de configuration de la mémoire";
|
||||
description = "Set memory options at boot";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
config = _: {
|
||||
boot.kernelParams = [
|
||||
"l1tf=full,force" # Enable full mitigation for L1 Terminal Fault (L1TF), including disabling SMT
|
||||
"page_poison=on" # Enable poisoning of freed pages to detect leaks
|
||||
"pti=on" # Force use of Page Table Isolation (PTI) for Meltdown vulnerability
|
||||
"slab_nomerge=yes" # Disable slab cache merging to complicate heap overflow attacks
|
||||
"slub_debug=FZP" # Enable slab cache debugging with specific tests
|
||||
"spec_store_bypass_disable=seccomp" # Use seccomp mitigation for Spectre v4
|
||||
"spectre_v2=on" # Force Spectre v2 mitigation
|
||||
"mds=full,nosmt" # Enable full mitigation for MDS and disable SMT (requires microcode update)
|
||||
"mce=0" # Force kernel panic on uncorrected Machine Check exceptions
|
||||
"page_alloc.shuffle=1" # Enable page allocator randomization
|
||||
"rng_core.default_quality=500" # Set HWRNG quality for TPM-based CSPRNG initialization
|
||||
];
|
||||
};
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R8" ''
|
||||
# The kernel boot parameters we expect to check
|
||||
EXPECTED_PARAMS=(
|
||||
"l1tf=full,force"
|
||||
"page_poison=on"
|
||||
"pti=on"
|
||||
"slab_nomerge=yes"
|
||||
"slub_debug=FZP"
|
||||
"spec_store_bypass_disable=seccomp"
|
||||
"spectre_v2=on"
|
||||
"mds=full,nosmt"
|
||||
"mce=0"
|
||||
"page_alloc.shuffle=1"
|
||||
"rng_core.default_quality=500"
|
||||
)
|
||||
|
||||
# Get the current kernel parameters from /proc/cmdline
|
||||
ACTUAL_PARAMS=$(cat /proc/cmdline)
|
||||
|
||||
# Check if each expected parameter is in the actual kernel parameters
|
||||
for param in "''${EXPECTED_PARAMS[@]}"; do
|
||||
if echo "$ACTUAL_PARAMS" | grep -q "$param"; then
|
||||
echo "Kernel parameter '$param' is correctly set."
|
||||
else
|
||||
echo "WARNING: Kernel parameter '$param' is NOT set."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Additionally, check specific parameters from /proc/cpuinfo (for cpu-related parameters)
|
||||
echo "Checking CPU-specific parameters..."
|
||||
|
||||
# Check if SMT (Hyper-Threading) is disabled, as required by "mds=full,nosmt"
|
||||
if grep -q "nosmt" <<< "$ACTUAL_PARAMS"; then
|
||||
if grep -q "siblings" /proc/cpuinfo && ! grep -q "HT" /proc/cpuinfo; then
|
||||
echo "SMT is disabled (as expected for mds=full,nosmt)."
|
||||
else
|
||||
echo "WARNING: SMT is still enabled, but 'mds=full,nosmt' was passed."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if Page Table Isolation (PTI) is enabled (from /proc/cpuinfo)
|
||||
if grep -q "pti" <<< "$ACTUAL_PARAMS"; then
|
||||
if grep -q "pti" /proc/cpuinfo; then
|
||||
echo "PTI is enabled (as expected for pti=on)."
|
||||
else
|
||||
echo "WARNING: PTI is not enabled, despite pti=on being passed."
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
|
||||
R9 =
|
||||
let
|
||||
sysctls = {
|
||||
# Restrict access to dmesg buffer
|
||||
"kernel.dmesg_restrict" = "1";
|
||||
|
||||
# Hide kernel addresses in /proc and other interfaces, even for privileged users
|
||||
"kernel.kptr_restrict" = "2";
|
||||
|
||||
# Set process ID maximum
|
||||
"kernel.pid_max" = "1048576"; # 2^20
|
||||
|
||||
# Restrict perf subsystem usage
|
||||
"kernel.perf_cpu_time_max_percent" = "1";
|
||||
"kernel.perf_event_max_sample_rate" = "1";
|
||||
|
||||
# Restrict access to perf_event_open system call
|
||||
"kernel.perf_event_paranoid" = "2";
|
||||
|
||||
# Enable Address Space Layout Randomization (ASLR)
|
||||
"kernel.randomize_va_space" = "2";
|
||||
|
||||
# Disable Magic SysRq key (prevents magical system requests)
|
||||
"kernel.sysrq" = "0";
|
||||
|
||||
# Restrict use of BPF (Berkeley Packet Filter) to privileged users
|
||||
"kernel.unprivileged_bpf_disabled" = "1";
|
||||
|
||||
# Kernel panic on unexpected kernel behavior (e.g., oops)
|
||||
"kernel.panic_on_oops" = "1";
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "R9_KernelOptions";
|
||||
anssiRef = "R9 – Paramétrer les options de configuration du noyau";
|
||||
description = "Tune kernel configuration options";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
config = _: { boot.kernel.sysctl = sysctls; };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R9" ''
|
||||
${mkSysctlChecker pkgs.lib sysctls}
|
||||
'';
|
||||
};
|
||||
|
||||
R10 = {
|
||||
name = "R10_DisableKernelModuleLoading";
|
||||
anssiRef = "R10 – Désactiver le chargement des modules noyau";
|
||||
description = "Disable loading of kernel modules";
|
||||
severity = "reinforced";
|
||||
category = "base";
|
||||
tags = [ "disable-kernel-module-loading" ];
|
||||
|
||||
config = _: { boot.kernel.sysctl."kernel.modules_disabled" = "1"; };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R10" ''
|
||||
${mkSysctlChecker pkgs.lib { "kernel.modules_disabled" = 1; }}
|
||||
'';
|
||||
};
|
||||
|
||||
R11 = {
|
||||
name = "R11_ConfigureYamaLSM";
|
||||
anssiRef = "R11 – Activer et configurer le LSM Yama";
|
||||
description = "Enable and configure the Yama Linux Security Module";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
config = _: { boot.kernel.sysctl."kernel.yama.ptrace_scope" = "1"; };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R11" ''
|
||||
${mkSysctlChecker pkgs.lib { "kernel.yama.ptrace_scope" = 1; }}
|
||||
'';
|
||||
};
|
||||
|
||||
R12 =
|
||||
let
|
||||
ipv4_sysctls = {
|
||||
# Harden the JIT compiler for BPF
|
||||
"net.core.bpf_jit_harden" = "2";
|
||||
|
||||
# Disable IP forwarding (no routing between interfaces)
|
||||
"net.ipv4.ip_forward" = "0";
|
||||
|
||||
# Reject packets with source address 127/8 (loopback network)
|
||||
"net.ipv4.conf.all.accept_local" = "0";
|
||||
|
||||
# Disable ICMP redirects (prevents potential traffic redirection)
|
||||
"net.ipv4.conf.all.accept_redirects" = "0";
|
||||
"net.ipv4.conf.default.accept_redirects" = "0";
|
||||
"net.ipv4.conf.all.secure_redirects" = "0";
|
||||
"net.ipv4.conf.default.secure_redirects" = "0";
|
||||
|
||||
# Disable shared media detection (useful for preventing misconfiguration)
|
||||
"net.ipv4.conf.all.shared_media" = "0";
|
||||
"net.ipv4.conf.default.shared_media" = "0";
|
||||
|
||||
# Disable kernel ARP management across interfaces (security for high-availability setups)
|
||||
"net.ipv4.conf.all.arp_filter" = "1";
|
||||
|
||||
# Respond to ARP requests only if source and destination are on the same network
|
||||
"net.ipv4.conf.all.arp_ignore" = "2";
|
||||
|
||||
# Refuse routing of packets with loopback addresses as source or destination
|
||||
"net.ipv4.conf.all.route_localnet" = "0";
|
||||
|
||||
# Drop Gratuitous ARP (prevents ARP poisoning attacks)
|
||||
"net.ipv4.conf.all.drop_gratuitous_arp" = "1";
|
||||
|
||||
# Perform Reverse Path Filtering (checks if the source is reachable via the interface it came from)
|
||||
"net.ipv4.conf.default.rp_filter" = "1";
|
||||
"net.ipv4.conf.all.rp_filter" = "1";
|
||||
|
||||
# Disable sending ICMP redirects (normal for routers but unnecessary for end hosts)
|
||||
"net.ipv4.conf.default.send_redirects" = "0";
|
||||
"net.ipv4.conf.all.send_redirects" = "0";
|
||||
|
||||
# Ignore bogus ICMP error responses (RFC 1122)
|
||||
"net.ipv4.icmp_ignore_bogus_error_responses" = "1";
|
||||
|
||||
# Increase the range for ephemeral ports (for better security)
|
||||
"net.ipv4.ip_local_port_range" = "32768 65535";
|
||||
|
||||
# Enable RFC 1337 compliance (prevents TCP timestamp attacks)
|
||||
"net.ipv4.tcp_rfc1337" = "1";
|
||||
|
||||
# Enable SYN cookies (prevents SYN flood attacks)
|
||||
"net.ipv4.tcp_syncookies" = "1";
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "R12_IPv4Networking";
|
||||
anssiRef = "R12 – Paramétrer les options de configuration du réseau IPv4";
|
||||
description = "Configure IPv4 network options";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
config = _: { boot.kernel.sysctl = ipv4_sysctls; };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R12" ''
|
||||
${mkSysctlChecker pkgs.lib ipv4_sysctls}
|
||||
'';
|
||||
};
|
||||
|
||||
R13 =
|
||||
let
|
||||
sysctls = {
|
||||
# Disable IPv6 on all interfaces by default
|
||||
"net.ipv6.conf.default.disable_ipv6" = "1";
|
||||
|
||||
# Disable IPv6 on all interfaces
|
||||
"net.ipv6.conf.all.disable_ipv6" = "1";
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "R13_DisableIPv6";
|
||||
anssiRef = "R13 – Désactiver le plan IPv6";
|
||||
description = "Disable IPv6";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
tags = [ "no-ipv6" ];
|
||||
|
||||
config = _: { boot.kernel.sysctl = sysctls; };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R13" ''
|
||||
${mkSysctlChecker pkgs.lib sysctls}
|
||||
'';
|
||||
};
|
||||
|
||||
R14 =
|
||||
let
|
||||
sysctls = {
|
||||
# Disable coredumps for setuid executables
|
||||
"fs.suid_dumpable" = "0";
|
||||
|
||||
# Prevent opening FIFOs and regular files not owned by the user in sticky directories
|
||||
"fs.protected_fifos" = "2";
|
||||
"fs.protected_regular" = "2";
|
||||
|
||||
# Restrict the creation of symbolic links to files owned by the user
|
||||
"fs.protected_symlinks" = "1";
|
||||
|
||||
# Restrict the creation of hard links to files owned by the user
|
||||
"fs.protected_hardlinks" = "1";
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "R14_FilesystemConfig";
|
||||
anssiRef = "R14 – Paramétrer les options de configuration des systèmes de fichiers";
|
||||
description = "Tune filesystem configuration options";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
config = _: { boot.kernel.sysctl = sysctls; };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R14" ''
|
||||
${mkSysctlChecker pkgs.lib sysctls}
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkEnableOption mkOption types;
|
||||
|
||||
cfg = config.security.anssi;
|
||||
rulesets = import ./ruleset.nix { inherit lib; };
|
||||
levelMapping = {
|
||||
"minimal" = 0;
|
||||
"intermediary" = 1;
|
||||
"reinforced" = 2;
|
||||
"high" = 3;
|
||||
};
|
||||
|
||||
# rules.* must follow the format used earlier:
|
||||
# {
|
||||
# name = "...";
|
||||
# category = "client" / "server" / "base";
|
||||
# severity = "minimal" / "intermediary" / "reinforced" / "high";
|
||||
# tags = [ "kernel" ... ];
|
||||
# config = f: { ... };
|
||||
# checkScript = pkgs: pkgs.writeShellScript ...
|
||||
# }
|
||||
|
||||
# Look at all rules that are not enabled.
|
||||
# Determine why they are not enabled:
|
||||
# - if there's an exception for it, use it.
|
||||
# - if it's excluded via a tag, use it.
|
||||
# - if it's excluded via architecture, use it.
|
||||
# - if it's out of the scope for the category, use it.
|
||||
# - if it's out of the scope for the level, use it.
|
||||
# - otherwise, mark it unknown.
|
||||
# Determine why a rule is not enabled.
|
||||
determineExclusionReason =
|
||||
ruleId: rule:
|
||||
if !cfg.rules.${ruleId}.enable then
|
||||
# Check if it's excluded via a clear mechanism.
|
||||
let
|
||||
tagExclusions = lib.filter (tag: lib.elem tag (rule.tags or [ ])) cfg.excludes;
|
||||
categoryExclusion = rule.category != "base" && rule.category != cfg.category;
|
||||
levelExclusion = levelMapping.${rule.severity} > levelMapping.${cfg.level};
|
||||
architectureExclusion =
|
||||
if (rule.architectures or [ ]) != [ ] then
|
||||
!lib.elem pkgs.stdenv.hostPlatform.linuxArch rule.architectures
|
||||
else
|
||||
false;
|
||||
in
|
||||
if tagExclusions != [ ] then
|
||||
{
|
||||
reason = "excluded by tag ${lib.concatStringsSep ", " tagExclusions}";
|
||||
via = "tag";
|
||||
exclusionTags = tagExclusions;
|
||||
}
|
||||
else if categoryExclusion then
|
||||
{
|
||||
reason = "out of scope for category '${cfg.category}' (${rule.category})";
|
||||
via = "category";
|
||||
requiredCategory = rule.category;
|
||||
}
|
||||
else if levelExclusion then
|
||||
{
|
||||
reason = "out of scope for level ${cfg.level} < ${rule.severity}";
|
||||
via = "level";
|
||||
requiredLevel = rule.severity;
|
||||
}
|
||||
else if architectureExclusion then
|
||||
{
|
||||
reason = "out of scope for architecture '${pkgs.stdenv.hostPlatform.linuxArch}'";
|
||||
via = "architecture";
|
||||
requiredArchitectures = rule.architectures or [ ];
|
||||
}
|
||||
else if lib.hasAttr ruleId cfg.exceptions then
|
||||
{
|
||||
reason = "excluded by explicit exception: ${cfg.exceptions.${ruleId}.rationale}";
|
||||
via = "exception";
|
||||
rationale = cfg.exceptions.${ruleId}.rationale;
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = "could not understand why this rule was excluded, the user must have set the `enable = false;` forcibly via the module system bypassing the governance system. This is a non-compliant.";
|
||||
via = "unknown";
|
||||
}
|
||||
else
|
||||
null;
|
||||
|
||||
generateRuleEntry =
|
||||
ruleId: rule:
|
||||
{
|
||||
enabled = cfg.rules.${ruleId}.enable;
|
||||
anssiRef = cfg.rules.${ruleId}.anssiRef;
|
||||
knownCheckScript = rule.checkScript pkgs;
|
||||
}
|
||||
// lib.optionalAttrs (cfg.exceptions ? ruleId) {
|
||||
declaredExceptionRationale = cfg.exceptions.${ruleId}.rationale;
|
||||
}
|
||||
// lib.optionalAttrs (rule ? implementations) {
|
||||
implementation = cfg.rules.${ruleId}.implementation;
|
||||
};
|
||||
|
||||
complianceReport = {
|
||||
"org.securix.anssi-compliance.v1" = {
|
||||
guideVersion = "2.0";
|
||||
rules = lib.mapAttrsToList (ruleId: rule: generateRuleEntry ruleId rule) rulesets;
|
||||
targetLevel = cfg.level;
|
||||
hostname = config.networking.hostName;
|
||||
domain = config.networking.domain or null;
|
||||
targetSystemCategory = cfg.category;
|
||||
exclusions = lib.mapAttrs determineExclusionReason (
|
||||
lib.filterAttrs (ruleId: _: !cfg.rules.${ruleId}.enable) rulesets
|
||||
);
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
# This will generate `security.anssi.rules.RXX.{anssiRef,enable,checkScript}` options and so on.
|
||||
((import ./generator.nix).generateOptionHierarchy rulesets)
|
||||
];
|
||||
|
||||
options.security.anssi = {
|
||||
enable = mkEnableOption "ANSSI compliance for GNU/Linux systems (v2.0)";
|
||||
|
||||
level = mkOption {
|
||||
type = types.enum [
|
||||
"minimal"
|
||||
"intermediary"
|
||||
"reinforced"
|
||||
"high"
|
||||
];
|
||||
default = "minimal";
|
||||
description = "Select desired ANSSI compliance level.";
|
||||
};
|
||||
|
||||
category = mkOption {
|
||||
type = types.enum [
|
||||
"base"
|
||||
"client"
|
||||
"server"
|
||||
];
|
||||
default = "base";
|
||||
description = "Rule category to activate.";
|
||||
};
|
||||
|
||||
excludes = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "Tags to exclude (e.g. [ \"kernel\" \"no-ipv6\" ])";
|
||||
};
|
||||
|
||||
exceptions = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.submodule (_: {
|
||||
options.rationale = mkOption { type = types.lines; };
|
||||
})
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
system.build.complianceReport = complianceReport;
|
||||
system.build.complianceReportDocument = pkgs.writers.writeJSON "anssi-compliance.json" complianceReport;
|
||||
system.build.complianceCheckScript =
|
||||
let
|
||||
mkCheckSingularRule = ruleId: rule: ''
|
||||
# Check for rule ${ruleId}
|
||||
if [ -n "${rule.checkScript}" ]; then
|
||||
result=$(bash -c "${rule.checkScript}")
|
||||
results["${ruleId}"]=$result
|
||||
else
|
||||
results["${ruleId}"]="Check script not defined: cannot be checked."
|
||||
fi
|
||||
'';
|
||||
in
|
||||
pkgs.writeShellScriptBin "anssi-nixos-compliance-check" ''
|
||||
declare -A results
|
||||
|
||||
${lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList mkCheckSingularRule (
|
||||
lib.filterAttrs (_: rule: rule.enable) config.security.anssi.rules
|
||||
)
|
||||
)}
|
||||
|
||||
# Output the results in a matrix format
|
||||
echo "ANSSI Compliance Check Results:"
|
||||
for rule in "''${!results[@]}"; do
|
||||
result="''${results[$rule]}"
|
||||
if [[ "$result" =~ .*TODO.* ]]; then
|
||||
printf "\033[33m%-40s : %s\033[0m\n" "$rule" "$result"
|
||||
elif [[ "$result" =~ (fail|WARNING|DIVERGENCE|UNSET|error|not\ completed|incomplete) ]]; then
|
||||
printf "\033[31m%-40s : %s\033[0m\n" "$rule" "$result"
|
||||
else
|
||||
printf "\033[32m%-40s : %s\033[0m\n" "$rule" "$result"
|
||||
fi
|
||||
done
|
||||
'';
|
||||
|
||||
environment.systemPackages = [ config.system.build.complianceCheckScript ];
|
||||
};
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
{
|
||||
# TODO: these two next rules requires special system integration that depends on the hardware manufacturer.
|
||||
# We implement no specific check or implementation here.
|
||||
R1 = {
|
||||
name = "R1_ChooseHardware";
|
||||
anssiRef = "R1 – Choisir et configurer son matériel";
|
||||
description = "Choosing and configuring hardware";
|
||||
severity = "high";
|
||||
category = "base";
|
||||
|
||||
config = _: {
|
||||
# TODO: hardware-level config (maybe BIOS settings, firmware)
|
||||
};
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R1" ''
|
||||
echo "TODO: check hardware configuration for R1"
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
|
||||
R2 = {
|
||||
name = "R2_BIOS_UEFI";
|
||||
anssiRef = "R2 – Configurer le BIOS/UEFI";
|
||||
description = "Configure BIOS / UEFI";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
config = _: {
|
||||
# TODO: set NixOS options for UEFI / BIOS security
|
||||
};
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R2" ''
|
||||
echo "TODO: check BIOS / UEFI secure configuration"
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
|
||||
R3 = {
|
||||
name = "R3_UEFI_SecureBoot";
|
||||
anssiRef = "R3 – Activer le démarrage sécurisé UEFI";
|
||||
description = "Enable UEFI Secure Boot";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
config = _: {
|
||||
# TODO: NixOS bootloader secure-boot config
|
||||
};
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R3" ''
|
||||
# Check if the system is running in UEFI mode
|
||||
if [ ! -d "/sys/firmware/efi" ]; then
|
||||
echo "Secure Boot cannot be checked. System is not in UEFI mode."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check the Secure Boot status
|
||||
secure_boot_file="/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c"
|
||||
if [ -f "$secure_boot_file" ]; then
|
||||
# Extract the value of SecureBoot
|
||||
secure_boot_status=$(xxd -p -c 4 "$secure_boot_file")
|
||||
|
||||
# Check if Secure Boot is enabled
|
||||
if [[ "$secure_boot_status" =~ 01 ]]; then
|
||||
echo "Secure Boot is enabled."
|
||||
exit 0
|
||||
else
|
||||
echo "fail: Secure Boot is not enabled."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "fail: Secure Boot status file not found."
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
R4 = {
|
||||
name = "R4_ReplacePreloadedKeys";
|
||||
anssiRef = "R4 – Remplacer les clés préchargées";
|
||||
description = "Replace preloaded UEFI keys";
|
||||
severity = "high";
|
||||
category = "base";
|
||||
|
||||
config = _: { };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R4" ''
|
||||
echo "TODO: check all loaded certificates"
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
|
||||
R5 = {
|
||||
name = "R5_BootloaderPassword";
|
||||
anssiRef = "R5 – Configurer un mot de passe pour le chargeur de démarrage";
|
||||
description = "Set a password on the bootloader";
|
||||
severity = "intermediary";
|
||||
category = "base";
|
||||
|
||||
implementations = {
|
||||
secureboot = {
|
||||
checkScript = { };
|
||||
depends = [ "R3" ];
|
||||
config = _: { };
|
||||
};
|
||||
grub = {
|
||||
checkScript = "";
|
||||
config = _: { };
|
||||
};
|
||||
};
|
||||
|
||||
config = _: {
|
||||
# TODO: grub or systemd-boot password
|
||||
};
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R5" ''
|
||||
echo "R5 depends on R3."
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
|
||||
R6 = {
|
||||
name = "R6_ProtectKernelCmdline";
|
||||
anssiRef = "R6 – Protéger les paramètres de ligne de commande du noyau et l’initramfs";
|
||||
description = "Protect kernel cmdline and initramfs";
|
||||
severity = "high";
|
||||
category = "base";
|
||||
|
||||
# implementations.secureboot = { checkScript = {}; depends = [ "R3" ]; };
|
||||
|
||||
config = _: {
|
||||
# TODO
|
||||
};
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R6" ''
|
||||
echo "R6 depends on R3."
|
||||
'';
|
||||
};
|
||||
|
||||
R7 = {
|
||||
name = "R7_EnableIOMMU";
|
||||
anssiRef = "R7 – Activer l’IOMMU";
|
||||
description = "Enable IOMMU";
|
||||
severity = "reinforced";
|
||||
category = "base";
|
||||
|
||||
config = _: { boot.kernelParams = [ "iommu=force" ]; };
|
||||
|
||||
checkScript =
|
||||
pkgs:
|
||||
pkgs.writeShellScript "check-R7" ''
|
||||
#!/bin/sh
|
||||
|
||||
IOMMU_DIR="/sys/class/iommu"
|
||||
|
||||
if [ -d "$IOMMU_DIR" ] && [ "$(ls -A $IOMMU_DIR)" ]; then
|
||||
echo "IOMMU is enabled. Files found: $(ls $IOMMU_DIR | xargs)"
|
||||
else
|
||||
echo "No IOMMU detected or not enabled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2026 Ryan Lahfa <ryan.lahfa@numerique.gouv.fr>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
{ lib, ... }:
|
||||
let
|
||||
loadRules = files: lib.mergeAttrsList (map import files);
|
||||
in
|
||||
loadRules [
|
||||
./preboot.nix
|
||||
./kernel-options.nix
|
||||
# ./kernel.nix
|
||||
# ./vfs.nix
|
||||
# ./users.nix
|
||||
# ./apparmor.nix
|
||||
# ./selinux.nix
|
||||
# ./journaling.nix
|
||||
# ./runtime-minimization.nix
|
||||
# ./hardening.nix
|
||||
# ./nss-hardening.nix
|
||||
# ./integrity.nix
|
||||
# ./mta.nix
|
||||
]
|
||||
@@ -41,9 +41,6 @@ in
|
||||
];
|
||||
|
||||
i18n.defaultLocale = "fr_FR.UTF-8";
|
||||
# i18n.defaultLocale ne règle pas le clavier de la console — option
|
||||
# séparée, absente jusqu'ici pour ce host (vérifié : wiki.nixos.org/wiki/Keyboard_Layout_Customization).
|
||||
console.keyMap = "fr";
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
Reference in New Issue
Block a user