[FIX] install nixos : l'amorçage, les en-têtes, les manuels écartés

L'amorçage d'ERPLibre n'atteignait pas NixOS : le Makefile force
« SHELL := /bin/bash », que l'image ne porte pas, et le fournisseur de
Python ne cherche l'interpréteur du système qu'en /usr/bin/pythonX.Y.
Les deux répondent une fois envfs déclaré.

Ce qui n'a pas de roue amont se compile et réclame ses en-têtes : ils sont
liés au profil du système, leurs bibliothèques vont au chargeur, et
pg_config — dérivation à part dans nixpkgs — est nommé. La sortie « doc »
de CPython est écartée : sa construction est un Sphinx de 3 000 pages qui
domine le temps d'installation d'une VM de 4 Go.

--- EN ---

ERPLibre's bootstrap did not reach NixOS: the Makefile forces "SHELL :=
/bin/bash", which the image lacks, and the Python provider only looks for
the system interpreter at /usr/bin/pythonX.Y. Both answer once envfs is
declared.

What has no upstream wheel compiles and demands its headers: they are
linked into the system profile, their libraries reach the loader, and
pg_config — a separate derivation in nixpkgs — is named. CPython's "doc"
output is left out: building it is a 3000-page Sphinx run that dominates
install time on a 4 GB VM.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-09-16 00:42:13 -04:00
parent fa5892d006
commit a9f91dda87
9 changed files with 245 additions and 7 deletions

View file

@ -73,7 +73,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `long_test/qemu_cache.py --distro tous` (or a comma list) chains one campaign per catalogue system, destroys each system's VMs before the next, and ends on a table of verdict, durations and upstream bytes. A failure does not stop the series
- The QEMU download cache can clean itself up every day: by age (`EL_PURGE_AGE`, e.g. `90j`) and by size ceiling (`EL_MAX_SIZE`, e.g. `50G`, the least recently served going first, objects and git mirrors alike). Both are off by default and set from **Deployment › QEMU cache › Automatic cleanup**, which previews what would go; `--purge-to-size` runs the ceiling by hand. A reinstall keeps the values chosen
- NixOS 25.11 as a deployable system, alongside the eight others. It is the only one no distribution publishes a cloud image for: the image is rebuilt by a third party, so its release is pinned, its sha256 verified at every download, and its origin printed before anything is created. cloud-init receives no network configuration there — its networkd renderer takes the key of a block as an interface NAME, where netplan honours a `match:` — and the account is created with `/bin/sh`, sshd refusing an account whose shell does not exist
- ERPLibre installed on NixOS, its dependencies DECLARED in `conf/nixos/erplibre.nix` and applied by `nixos-rebuild`, where the four other families install them one command at a time. `services.envfs` answers `/usr/bin/env` and `programs.nix-ld` gives manylinux wheels the dynamic loader they ask for; the headers of what has no wheel are linked into the system profile, and its libraries reach the loader too, a module compiled on the machine carrying no RPATH. Checked on a VM: the 362-package lock installs, and Odoo initialises a database and answers over HTTP
- ERPLibre installed on NixOS, its dependencies DECLARED in `conf/nixos/erplibre.nix` and applied by `nixos-rebuild`, where the four other families install them one command at a time. `services.envfs` answers `/usr/bin/env` and `programs.nix-ld` gives manylinux wheels the dynamic loader they ask for; the headers of what has no wheel are linked into the system profile, and its libraries reach the loader too, a module compiled on the machine carrying no RPATH. The install reaches it from the menu like any other distribution: the bootstrap poses git, make and python3 — absent from that image — in the user profile for the clone, and « make install_os » redeclares them for the system. Checked on a VM: the 362-package lock installs and Odoo answers over HTTP
- A `nix + nixos-anywhere` option on the OTHER distributions, the reverse of picking NixOS: it leaves an ordinary VM able to install NixOS onto any machine reachable over SSH. Nix is called by absolute path, the remote shell's PATH being frozen before the installer drops the binary, and the flake features are repeated on the command line, writing `nix.conf` needing a sudo that may fail
<!-- [fr] -->
@ -120,7 +120,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `long_test/qemu_cache.py --distro tous` (ou une liste séparée par des virgules) enchaîne une campagne par système du catalogue, défait les VM de chaque système avant le suivant, et finit sur un tableau des verdicts, durées et octets d'amont. Un échec n'arrête pas la série
- Le cache de téléchargement QEMU peut se nettoyer chaque jour : par âge (`EL_PURGE_AGE`, ex. `90j`) et par plafond de taille (`EL_MAX_SIZE`, ex. `50G`, le moins récemment servi partant d'abord, objets et miroirs git confondus). Les deux sont désactivés par défaut et se règlent depuis **Déploiement › Cache QEMU › Nettoyage automatique**, qui montre ce qui partirait ; `--purge-to-size` applique le plafond à la main. Une réinstallation garde les valeurs choisies
- NixOS 25.11 parmi les systèmes déployables, à côté des huit autres. C'est le seul dont aucune distribution ne publie d'image cloud : l'image est rebâtie par un tiers, donc sa version est épinglée, sa somme sha256 vérifiée à chaque téléchargement, et son origine dite avant que rien ne soit créé. cloud-init n'y reçoit aucune configuration réseau — son moteur networkd prend la clé d'un bloc pour un NOM d'interface, là où netplan honore un `match:` — et le compte y est créé avec `/bin/sh`, sshd refusant un compte dont le shell n'existe pas
- ERPLibre installé sur NixOS, ses dépendances DÉCLARÉES dans `conf/nixos/erplibre.nix` puis appliquées par `nixos-rebuild`, là où les quatre autres familles les installent commande par commande. `services.envfs` répond à `/usr/bin/env` et `programs.nix-ld` donne aux roues manylinux le chargeur dynamique qu'elles réclament ; les en-têtes de ce qui n'a pas de roue sont liés au profil du système, et leurs bibliothèques vont aussi au chargeur, un module compilé sur la machine ne portant pas de RPATH. Vérifié sur une VM : le verrou de 362 paquets s'installe, et Odoo initialise une base et répond en HTTP
- ERPLibre installé sur NixOS, ses dépendances DÉCLARÉES dans `conf/nixos/erplibre.nix` puis appliquées par `nixos-rebuild`, là où les quatre autres familles les installent commande par commande. `services.envfs` répond à `/usr/bin/env` et `programs.nix-ld` donne aux roues manylinux le chargeur dynamique qu'elles réclament ; les en-têtes de ce qui n'a pas de roue sont liés au profil du système, et leurs bibliothèques vont aussi au chargeur, un module compilé sur la machine ne portant pas de RPATH. L'installation l'atteint depuis le menu comme toute autre distribution : l'amorçage pose git, make et python3 — absents de cette image — dans le profil de l'utilisateur pour la durée du clone, et « make install_os » les redéclare pour le système. Vérifié sur une VM : le verrou de 362 paquets s'installe et Odoo répond en HTTP
- Une option `nix + nixos-anywhere` sur les AUTRES distributions, l'inverse de choisir NixOS : elle laisse une VM ordinaire capable d'installer NixOS sur toute machine joignable en SSH. Nix est appelé par son chemin absolu, le PATH du shell distant étant figé avant que l'installateur ne pose le binaire, et les fonctions des flakes sont redonnées sur la ligne de commande, l'écriture de `nix.conf` réclamant un sudo qui peut manquer
<!-- [en] -->
@ -200,6 +200,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **Deployment › QEMU cache › Git mirrors** fills the base of the active Odoo version, or its extra modules, on their own — beside the full fill of every manifest, which takes hours. Each list shows how many repositories it declares and how many still lack a mirror. What a deployment clones is now read with the manifest merge's own rule and lists, so the extra modules, installed only on request, and the mobile project no longer count: the offline warning announced four missing mirrors that a default Odoo 18 install never clones
- pip's PEP 658 metadata — the `.whl.metadata` file fetched before each wheel — is served from disk. The name ends in `.metadata`, which no rule knew, so each file was taken again on every install: 178 of them on a full ERPLibre install. Copies stored before this change are not reached again; the next online install refills them
- The timezone a deployed VM inherits from its host is translated to its canonical name. Ubuntu 24.04 cloud images no longer carry the legacy aliases — `Canada/*`, `US/*`, `Asia/Calcutta` — moved to a `tzdata-legacy` package they do not install: cloud-init refused the zone, the VM stayed on UTC, and the only sign was cloud-init reporting an error, the offset showing up in timestamps long afterwards. The alias table is the host's own `tzdata.zi`, not a copy kept in the code
- `make` looks for bash instead of assuming `/bin/bash`. That path does not exist on NixOS, where the shell lives in the store, and make stopped before running any recipe — including the one that installs what creates that path. Elsewhere the resolved shell is the same one as before
- The locale and the keyboard a deployed VM is given now apply on Debian, where both silently failed. A locale is generated from `/etc/locale.gen` and nowhere else, so `update-locale` refused one that was not there and the VM stayed on C.UTF-8; the keyboard module ends on a `console-setup` the genericcloud image does not carry, so `/etc/default/keyboard` — the file localed and X read — is written directly instead. Every Debian deployment used to print `cloud-init: status: error`, and a word that always shows warns of nothing
<!-- [fr] -->
@ -243,6 +244,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **Déploiement › Cache QEMU › Miroirs git** remplit à part la base de la version d'Odoo active, ou ses modules extra — à côté du remplissage de tous les manifestes, qui prend des heures. Chaque liste dit combien de dépôts elle déclare et combien n'ont pas encore de miroir. Ce qu'un déploiement clone se lit désormais avec la règle et les listes de la fusion des manifestes elle-même : les modules extra, installés seulement sur demande, et le projet mobile ne comptent plus, là où l'avertissement hors ligne annonçait quatre miroirs manquants qu'une installation Odoo 18 par défaut ne clone jamais
- Les métadonnées PEP 658 de pip — le fichier `.whl.metadata` récupéré avant chaque roue — sont servies du disque. Le nom finit par `.metadata`, qu'aucune règle ne connaissait : chaque fichier repartait à chaque installation, 178 sur une installation complète d'ERPLibre. Les copies gardées avant ce changement ne sont plus atteintes ; la prochaine installation en ligne les remplit
- Le fuseau horaire qu'une VM déployée hérite de son hôte est traduit en son nom canonique. Les images cloud d'Ubuntu 24.04 ne portent plus les alias historiques — `Canada/*`, `US/*`, `Asia/Calcutta` — déplacés dans un paquet `tzdata-legacy` qu'elles n'installent pas : cloud-init refusait le fuseau, la VM restait en UTC, et le seul signe était un cloud-init en erreur, le décalage n'apparaissant qu'aux horodatages longtemps après. La table des alias est le `tzdata.zi` de l'hôte, et non une copie figée dans le code
- `make` cherche bash au lieu de présumer `/bin/bash`. Ce chemin n'existe pas sur NixOS, où le shell vit dans le store, et make s'arrêtait avant d'exécuter la moindre recette — y compris celle qui installe de quoi créer ce chemin. Ailleurs, le shell résolu est celui d'avant
- Le locale et le clavier qu'une VM déployée reçoit s'appliquent désormais sur Debian, où les deux échouaient en silence. Un locale se génère à partir de `/etc/locale.gen` et de nulle part ailleurs : `update-locale` refusait celui qui n'y était pas et la VM restait en C.UTF-8 ; le module clavier finit par un `console-setup` que l'image genericcloud ne porte pas, alors `/etc/default/keyboard` — le fichier que localed et X relisent — est écrit directement. Chaque déploiement Debian imprimait `cloud-init: status: error`, et un mot qui s'affiche toujours n'avertit plus de rien
<!-- [en] -->

View file

@ -53,7 +53,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `long_test/qemu_cache.py --distro tous` (ou une liste séparée par des virgules) enchaîne une campagne par système du catalogue, défait les VM de chaque système avant le suivant, et finit sur un tableau des verdicts, durées et octets d'amont. Un échec n'arrête pas la série
- Le cache de téléchargement QEMU peut se nettoyer chaque jour : par âge (`EL_PURGE_AGE`, ex. `90j`) et par plafond de taille (`EL_MAX_SIZE`, ex. `50G`, le moins récemment servi partant d'abord, objets et miroirs git confondus). Les deux sont désactivés par défaut et se règlent depuis **Déploiement › Cache QEMU › Nettoyage automatique**, qui montre ce qui partirait ; `--purge-to-size` applique le plafond à la main. Une réinstallation garde les valeurs choisies
- NixOS 25.11 parmi les systèmes déployables, à côté des huit autres. C'est le seul dont aucune distribution ne publie d'image cloud : l'image est rebâtie par un tiers, donc sa version est épinglée, sa somme sha256 vérifiée à chaque téléchargement, et son origine dite avant que rien ne soit créé. cloud-init n'y reçoit aucune configuration réseau — son moteur networkd prend la clé d'un bloc pour un NOM d'interface, là où netplan honore un `match:` — et le compte y est créé avec `/bin/sh`, sshd refusant un compte dont le shell n'existe pas
- ERPLibre installé sur NixOS, ses dépendances DÉCLARÉES dans `conf/nixos/erplibre.nix` puis appliquées par `nixos-rebuild`, là où les quatre autres familles les installent commande par commande. `services.envfs` répond à `/usr/bin/env` et `programs.nix-ld` donne aux roues manylinux le chargeur dynamique qu'elles réclament ; les en-têtes de ce qui n'a pas de roue sont liés au profil du système, et leurs bibliothèques vont aussi au chargeur, un module compilé sur la machine ne portant pas de RPATH. Vérifié sur une VM : le verrou de 362 paquets s'installe, et Odoo initialise une base et répond en HTTP
- ERPLibre installé sur NixOS, ses dépendances DÉCLARÉES dans `conf/nixos/erplibre.nix` puis appliquées par `nixos-rebuild`, là où les quatre autres familles les installent commande par commande. `services.envfs` répond à `/usr/bin/env` et `programs.nix-ld` donne aux roues manylinux le chargeur dynamique qu'elles réclament ; les en-têtes de ce qui n'a pas de roue sont liés au profil du système, et leurs bibliothèques vont aussi au chargeur, un module compilé sur la machine ne portant pas de RPATH. L'installation l'atteint depuis le menu comme toute autre distribution : l'amorçage pose git, make et python3 — absents de cette image — dans le profil de l'utilisateur pour la durée du clone, et « make install_os » les redéclare pour le système. Vérifié sur une VM : le verrou de 362 paquets s'installe et Odoo répond en HTTP
- Une option `nix + nixos-anywhere` sur les AUTRES distributions, l'inverse de choisir NixOS : elle laisse une VM ordinaire capable d'installer NixOS sur toute machine joignable en SSH. Nix est appelé par son chemin absolu, le PATH du shell distant étant figé avant que l'installateur ne pose le binaire, et les fonctions des flakes sont redonnées sur la ligne de commande, l'écriture de `nix.conf` réclamant un sudo qui peut manquer
## Modifié
@ -111,6 +111,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **Déploiement › Cache QEMU › Miroirs git** remplit à part la base de la version d'Odoo active, ou ses modules extra — à côté du remplissage de tous les manifestes, qui prend des heures. Chaque liste dit combien de dépôts elle déclare et combien n'ont pas encore de miroir. Ce qu'un déploiement clone se lit désormais avec la règle et les listes de la fusion des manifestes elle-même : les modules extra, installés seulement sur demande, et le projet mobile ne comptent plus, là où l'avertissement hors ligne annonçait quatre miroirs manquants qu'une installation Odoo 18 par défaut ne clone jamais
- Les métadonnées PEP 658 de pip — le fichier `.whl.metadata` récupéré avant chaque roue — sont servies du disque. Le nom finit par `.metadata`, qu'aucune règle ne connaissait : chaque fichier repartait à chaque installation, 178 sur une installation complète d'ERPLibre. Les copies gardées avant ce changement ne sont plus atteintes ; la prochaine installation en ligne les remplit
- Le fuseau horaire qu'une VM déployée hérite de son hôte est traduit en son nom canonique. Les images cloud d'Ubuntu 24.04 ne portent plus les alias historiques — `Canada/*`, `US/*`, `Asia/Calcutta` — déplacés dans un paquet `tzdata-legacy` qu'elles n'installent pas : cloud-init refusait le fuseau, la VM restait en UTC, et le seul signe était un cloud-init en erreur, le décalage n'apparaissant qu'aux horodatages longtemps après. La table des alias est le `tzdata.zi` de l'hôte, et non une copie figée dans le code
- `make` cherche bash au lieu de présumer `/bin/bash`. Ce chemin n'existe pas sur NixOS, où le shell vit dans le store, et make s'arrêtait avant d'exécuter la moindre recette — y compris celle qui installe de quoi créer ce chemin. Ailleurs, le shell résolu est celui d'avant
- Le locale et le clavier qu'une VM déployée reçoit s'appliquent désormais sur Debian, où les deux échouaient en silence. Un locale se génère à partir de `/etc/locale.gen` et de nulle part ailleurs : `update-locale` refusait celui qui n'y était pas et la VM restait en C.UTF-8 ; le module clavier finit par un `console-setup` que l'image genericcloud ne porte pas, alors `/etc/default/keyboard` — le fichier que localed et X relisent — est écrit directement. Chaque déploiement Debian imprimait `cloud-init: status: error`, et un mot qui s'affiche toujours n'avertit plus de rien
## Retiré

View file

@ -53,7 +53,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `long_test/qemu_cache.py --distro tous` (or a comma list) chains one campaign per catalogue system, destroys each system's VMs before the next, and ends on a table of verdict, durations and upstream bytes. A failure does not stop the series
- The QEMU download cache can clean itself up every day: by age (`EL_PURGE_AGE`, e.g. `90j`) and by size ceiling (`EL_MAX_SIZE`, e.g. `50G`, the least recently served going first, objects and git mirrors alike). Both are off by default and set from **Deployment › QEMU cache › Automatic cleanup**, which previews what would go; `--purge-to-size` runs the ceiling by hand. A reinstall keeps the values chosen
- NixOS 25.11 as a deployable system, alongside the eight others. It is the only one no distribution publishes a cloud image for: the image is rebuilt by a third party, so its release is pinned, its sha256 verified at every download, and its origin printed before anything is created. cloud-init receives no network configuration there — its networkd renderer takes the key of a block as an interface NAME, where netplan honours a `match:` — and the account is created with `/bin/sh`, sshd refusing an account whose shell does not exist
- ERPLibre installed on NixOS, its dependencies DECLARED in `conf/nixos/erplibre.nix` and applied by `nixos-rebuild`, where the four other families install them one command at a time. `services.envfs` answers `/usr/bin/env` and `programs.nix-ld` gives manylinux wheels the dynamic loader they ask for; the headers of what has no wheel are linked into the system profile, and its libraries reach the loader too, a module compiled on the machine carrying no RPATH. Checked on a VM: the 362-package lock installs, and Odoo initialises a database and answers over HTTP
- ERPLibre installed on NixOS, its dependencies DECLARED in `conf/nixos/erplibre.nix` and applied by `nixos-rebuild`, where the four other families install them one command at a time. `services.envfs` answers `/usr/bin/env` and `programs.nix-ld` gives manylinux wheels the dynamic loader they ask for; the headers of what has no wheel are linked into the system profile, and its libraries reach the loader too, a module compiled on the machine carrying no RPATH. The install reaches it from the menu like any other distribution: the bootstrap poses git, make and python3 — absent from that image — in the user profile for the clone, and « make install_os » redeclares them for the system. Checked on a VM: the 362-package lock installs and Odoo answers over HTTP
- A `nix + nixos-anywhere` option on the OTHER distributions, the reverse of picking NixOS: it leaves an ordinary VM able to install NixOS onto any machine reachable over SSH. Nix is called by absolute path, the remote shell's PATH being frozen before the installer drops the binary, and the flake features are repeated on the command line, writing `nix.conf` needing a sudo that may fail
## Changed
@ -111,6 +111,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **Deployment › QEMU cache › Git mirrors** fills the base of the active Odoo version, or its extra modules, on their own — beside the full fill of every manifest, which takes hours. Each list shows how many repositories it declares and how many still lack a mirror. What a deployment clones is now read with the manifest merge's own rule and lists, so the extra modules, installed only on request, and the mobile project no longer count: the offline warning announced four missing mirrors that a default Odoo 18 install never clones
- pip's PEP 658 metadata — the `.whl.metadata` file fetched before each wheel — is served from disk. The name ends in `.metadata`, which no rule knew, so each file was taken again on every install: 178 of them on a full ERPLibre install. Copies stored before this change are not reached again; the next online install refills them
- The timezone a deployed VM inherits from its host is translated to its canonical name. Ubuntu 24.04 cloud images no longer carry the legacy aliases — `Canada/*`, `US/*`, `Asia/Calcutta` — moved to a `tzdata-legacy` package they do not install: cloud-init refused the zone, the VM stayed on UTC, and the only sign was cloud-init reporting an error, the offset showing up in timestamps long afterwards. The alias table is the host's own `tzdata.zi`, not a copy kept in the code
- `make` looks for bash instead of assuming `/bin/bash`. That path does not exist on NixOS, where the shell lives in the store, and make stopped before running any recipe — including the one that installs what creates that path. Elsewhere the resolved shell is the same one as before
- The locale and the keyboard a deployed VM is given now apply on Debian, where both silently failed. A locale is generated from `/etc/locale.gen` and nowhere else, so `update-locale` refused one that was not there and the VM stayed on C.UTF-8; the keyboard module ends on a `console-setup` the genericcloud image does not carry, so `/etc/default/keyboard` — the file localed and X read — is written directly instead. Every Debian deployment used to print `cloud-init: status: error`, and a word that always shows warns of nothing
## Removed

View file

@ -1,4 +1,9 @@
SHELL := /bin/bash
# bash, où qu'il soit. « /bin/bash » n'existe pas sur NixOS, où le shell
# vit dans le store : make s'y arrêtait sur « /bin/bash: No such file or
# directory » avant d'exécuter la moindre recette, y compris celle qui
# installe de quoi le créer. Le repli garde le chemin des plateformes qui
# l'ont, et l'affectation reste immédiate — SHELL est lu à chaque recette.
SHELL := $(shell command -v bash 2>/dev/null || echo /bin/bash)
ERPLIBRE_VERSION := $(strip \
$(if $(wildcard .erplibre-version),$(shell cat .erplibre-version),) \
)

View file

@ -154,6 +154,19 @@
# « fatal error: lber.h: No such file or directory » reste entier.
environment.pathsToLink = [ "/include" "/lib/pkgconfig" ];
# Les manuels HTML, et EUX SEULS, sont écartés.
#
# NixOS installe la sortie « doc » de CHAQUE paquet du système
# (environment.extraOutputsToInstall vaut « man info doc »). Celle de
# CPython n'est pas dans le cache binaire : le premier « nixos-rebuild »
# la BÂTIT — un Sphinx qui lit puis écrit 3 000 pages. Sur une VM de 4 Go
# et 4 cœurs, cela domine le temps d'installation ; sur une de 2 Go, la
# machine cesse de répondre pendant la construction.
#
# « documentation.doc » et non « documentation » : les pages de manuel et
# info restent, elles se lisent depuis un terminal et ne coûtent rien.
documentation.doc.enable = false;
# « sessionVariables » et NON « variables » : la seconde n'écrit que dans
# /etc/set-environment, que seul un shell de CONNEXION lit. Or le
# déploiement installe par « ssh hôte 'commande' », qui n'en est pas un —

View file

@ -63,3 +63,21 @@ EL_VERBOSE="${EL_VERBOSE:-0}"
if [ -n "${NIX_LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="${NIX_LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
fi
# NixOS : les chemins de compilation, relus du SYSTEME DE FICHIERS.
#
# Le module declare CPATH, LIBRARY_PATH et PKG_CONFIG_PATH, mais une session
# les recoit de pam_env a son OUVERTURE. Or l'installation applique le module
# (« make install_os ») puis compile (« make install_odoo_18 ») dans la MEME
# session : la sienne est plus vieille que ce qu'elle vient de declarer. Les
# trois paquets du verrou qui n'ont pas de roue amont s'arretaient alors sur
# « lber.h », « cups/http.h » et « mysql.h » -- au PREMIER passage seulement,
# ce qui est la pire des pannes : le second reussit et donne raison a tort.
#
# « /run/current-system/sw » est le profil courant du systeme, un lien que le
# rebuild vient de reposer : le lire ne depend d'aucune variable heritee.
if [ -d /run/current-system/sw ]; then
export CPATH="/run/current-system/sw/include${CPATH:+:${CPATH}}"
export LIBRARY_PATH="/run/current-system/sw/lib${LIBRARY_PATH:+:${LIBRARY_PATH}}"
export PKG_CONFIG_PATH="/run/current-system/sw/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
fi

View file

@ -250,8 +250,42 @@ class QemuDeployMixin:
"--auto-agree-with-licenses $PKGS; "
"elif command -v yum >/dev/null 2>&1; then "
"sudo yum makecache -q || true; sudo yum install -y $PKGS; "
"else echo 'Aucun gestionnaire de paquets "
"(apt/dnf/pacman/zypper/yum)'; exit 1; fi; "
# NixOS n'a aucun des cinq, et c'est là que l'installation
# s'arrêtait : « Aucun gestionnaire de paquets », avant même le
# clone. Le système est DÉCLARATIF, mais l'amorçage est le seul
# moment où on ne peut pas l'être — le module qui déclare git et
# make vit DANS le dépôt qu'il faut git pour cloner.
#
# Le profil de l'utilisateur tranche ce nœud : les outils y sont
# posés pour la durée du clone, et « make install_os » les
# redéclare ensuite pour le système entier. Rien n'est écrit dans
# /etc/nixos avant que le dépôt ne soit là pour le faire.
#
# « <nixpkgs> » et non un canal : l'utilisateur n'en a aucun sur
# cette image, là où NIX_PATH est posé pour tout le monde et
# pointe les canaux de root.
#
# Les noms sont ceux de nixpkgs, où make s'appelle gnumake — $PKGS
# porte les noms des quatre autres familles et ne vaut pas ici.
#
# python3 s'y ajoute, et seulement ici : les images des quatre
# autres familles l'embarquent — cloud-init est écrit en Python et
# le pose sur le PATH. Sur NixOS il vit dans le store, hors PATH,
# et « make install_os » s'arrêtait sur « env: python3: No such
# file or directory » à sa première recette. Sa version importe
# peu : l'installation choisit ensuite le Python d'Odoo, que le
# module déclare et que le profil du système porte, cherché avant
# celui de l'utilisateur.
+ "elif command -v nix-env >/dev/null 2>&1; then "
"nix-env -f '<nixpkgs>' -iA git gnumake curl python3"
f"{self._qemu_editor_suffix()}; "
# Le PATH de cette commande distante a été figé à l'ouverture du
# shell SSH, avant que nix-env ne pose quoi que ce soit : sans
# cette ligne, le contrôle juste en dessous déclare git manquant
# sur une machine où il vient d'être installé.
'export PATH="$HOME/.nix-profile/bin:$PATH"; '
+ "else echo 'Aucun gestionnaire de paquets "
"(apt/dnf/pacman/zypper/yum/nix)'; exit 1; fi; "
# Vérifie explicitement que tout est là : erreur nette plutôt
# qu'un « command not found » cryptique plus loin.
"for t in curl git make; do command -v $t >/dev/null 2>&1 || "

View file

@ -190,6 +190,43 @@ class LeModule(unittest.TestCase):
self.assertIn("environment.sessionVariables", self.src)
self.assertNotIn("environment.variables", self.src)
def test_the_html_manuals_are_left_out(self):
"""NixOS installe la sortie « doc » de CHAQUE paquet du système
(extraOutputsToInstall vaut « man info doc »). Celle de CPython n'est
pas dans le cache binaire : le premier rebuild la BÂTIT, un Sphinx de
trois mille pages qui domine le temps d'installation et fait cesser de
répondre une machine étroite.
« documentation.doc » et non « documentation » : les pages de manuel
et info restent, elles se lisent depuis un terminal."""
self.assertIn("documentation.doc.enable = false;", self.src)
self.assertNotIn("documentation.enable", self.src)
def test_the_install_reads_the_paths_off_the_filesystem(self):
"""Une session reçoit les variables du module à son OUVERTURE, par
pam_env. L'installation applique le module (« make install_os ») puis
compile (« make install_odoo_18 ») dans la MÊME session : la sienne
est plus vieille que ce qu'elle vient de déclarer.
Les trois paquets du verrou qui n'ont pas de roue amont s'arrêtaient
alors sur « lber.h », « cups/http.h » et « mysql.h » — au PREMIER
passage seulement, ce qui est la pire des pannes : le second réussit
et donne raison à tort. env_var.sh relit donc le profil courant du
système, un lien sur le disque, plutôt qu'une variable héritée."""
env = (RACINE / "env_var.sh").read_text(encoding="utf-8")
self.assertIn("if [ -d /run/current-system/sw ]", env)
for var in ("CPATH", "LIBRARY_PATH", "PKG_CONFIG_PATH"):
with self.subTest(var=var):
self.assertIn(f"export {var}=", env)
self.assertIn("/run/current-system/sw", env)
def test_nothing_is_exported_off_nixos(self):
"""Le garde est le répertoire lui-même : ailleurs, aucune de ces
variables n'est touchée."""
env = (RACINE / "env_var.sh").read_text(encoding="utf-8")
bloc = env.split("if [ -d /run/current-system/sw ]")[1].split("fi")[0]
self.assertNotIn("\nexport", bloc.replace("\n export", ""))
def test_the_headers_are_linked_into_the_profile(self):
"""Le profil ne porte pas « /include » par défaut : sans cette ligne,
déclarer une sortie « .dev » ne met les en-têtes nulle part, et

View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""L'amorçage d'une installation ERPLibre sur NixOS.
Le catalogue offre NixOS et le dépôt sait y déclarer ses dépendances ; entre
les deux manquait le geste qui va du système nu au dépôt cloné. L'amorçage
posait curl, git et make par apt, dnf, pacman, zypper ou yum, et s'arrêtait
sur « Aucun gestionnaire de paquets » — avant le clone, donc avant tout.
Deux nœuds, et chacun se dénoue au même endroit : on ne peut pas être
déclaratif AVANT d'avoir le dépôt qui porte la déclaration.
- git et make sont ABSENTS de l'image NixOS (curl et sudo, eux, y sont). Ils
sont donc posés dans le profil de l'utilisateur pour la durée du clone, et
« make install_os » les redéclare ensuite pour le système entier ;
- « /bin/bash » n'existe pas non plus — le shell vit dans le store. Le
Makefile l'exigeait dès sa première ligne, et make s'arrêtait avant
d'exécuter la moindre recette, y compris celle qui installe de quoi créer
ce chemin.
"""
import subprocess
import sys
import unittest
from pathlib import Path
sys.argv = ["todo.py"]
from script.todo.todo import TODO # noqa: E402
RACINE = Path(__file__).resolve().parents[1]
class LAmorcage(unittest.TestCase):
def setUp(self):
self.cmd = TODO.__new__(TODO)._qemu_erplibre_remote_cmd("develop")
def test_a_nixos_vm_is_no_longer_turned_away(self):
"""Le message d'arrêt nomme désormais nix : sans la branche, une VM
NixOS n'atteignait jamais son clone."""
self.assertIn("command -v nix-env", self.cmd)
self.assertIn("(apt/dnf/pacman/zypper/yum/nix)", self.cmd)
def test_the_tools_come_from_nixpkgs_not_from_a_channel(self):
"""L'utilisateur n'a aucun canal sur cette image, là où NIX_PATH est
posé pour tout le monde et pointe les canaux de root."""
self.assertIn("nix-env -f '<nixpkgs>' -iA", self.cmd)
def test_make_is_asked_for_by_its_nixpkgs_name(self):
"""Dans nixpkgs, make s'appelle gnumake : demander « make » ferait
échouer la pose sans que rien ne dise pourquoi."""
i = self.cmd.index("nix-env -f")
pose = self.cmd[i : self.cmd.index(";", i)]
self.assertIn("gnumake", pose)
self.assertNotIn(" make", pose)
def test_python3_is_posed_here_and_only_here(self):
"""Les images des quatre autres familles l'embarquent — cloud-init
est écrit en Python. Sur NixOS il vit dans le store, hors PATH, et la
première recette de « make install_os » s'arrêtait sur « env:
python3: No such file or directory »."""
i = self.cmd.index("nix-env -f")
pose = self.cmd[i : self.cmd.index(";", i)]
self.assertIn("python3", pose)
self.assertNotIn("python3", self.cmd[: self.cmd.index("PKGS=")])
def test_the_path_is_widened_after_the_pose(self):
"""Le PATH du shell distant est figé à son ouverture : sans cette
ligne, le contrôle qui suit déclare git manquant sur une machine où
il vient d'être installé."""
i = self.cmd.index("nix-env -f")
j = self.cmd.index("for t in curl git make")
self.assertIn('PATH="$HOME/.nix-profile/bin:$PATH"', self.cmd[i:j])
def test_the_check_that_follows_is_unchanged(self):
"""Le même contrôle vaut pour les six familles : une erreur nette
plutôt qu'un « command not found » cryptique plus loin."""
self.assertIn("for t in curl git make; do command -v $t", self.cmd)
def test_it_is_valid_shell(self):
fini = subprocess.run(
["bash", "-n"], input=self.cmd, text=True, capture_output=True
)
self.assertEqual(0, fini.returncode, fini.stderr)
class LeShellDuMakefile(unittest.TestCase):
PREMIERES = (RACINE / "Makefile").read_text(encoding="utf-8")[:600]
def test_bash_is_looked_for_rather_than_assumed(self):
"""« /bin/bash » n'existe pas sur NixOS."""
self.assertIn("command -v bash", self.PREMIERES)
self.assertNotIn("SHELL := /bin/bash", self.PREMIERES)
def test_a_host_without_bash_keeps_the_old_path(self):
"""Le repli ne rend pas la panne pire qu'avant sur une plateforme qui
n'a pas bash du tout."""
self.assertIn("|| echo /bin/bash", self.PREMIERES)
def test_the_assignment_stays_immediate(self):
"""« = » différé ferait relancer le sous-shell à chaque recette, et
SHELL est lu à chacune."""
self.assertIn("SHELL := ", self.PREMIERES)
def test_the_resolved_shell_exists_here(self):
"""L'épreuve sur la machine qui lit ce test : make doit pouvoir
exécuter une recette."""
ligne = next(
x
for x in (RACINE / "Makefile")
.read_text(encoding="utf-8")
.splitlines()
if x.startswith("SHELL")
)
fini = subprocess.run(
["make", "-s", "-f", "-", "essai"],
input=f"{ligne}\nessai:\n\techo ok\n",
text=True,
capture_output=True,
cwd=RACINE,
)
self.assertEqual(0, fini.returncode, fini.stderr)
self.assertIn("ok", fini.stdout)
if __name__ == "__main__":
unittest.main()