NixOS active un pare-feu par défaut ; aucune des quatre autres images cloud n'en active. Le service écoutait bien et répondait en local, mais l'hôte ne recevait RIEN — pas un refus, un silence jusqu'au délai — et le suivi déclarait Odoo absent sur une machine où il tournait. Mesuré : 000 après 12 s, puis 303 en 9 ms. Un système déclaratif n'a que ce qui est écrit : xmlsec1, sans lequel Odoo refuse d'installer auth_saml ; parallel et shfmt, appelés par leur nom nu ; growpart, dont l'absence faisait rendre 0 à un agrandissement qui n'agrandissait rien ; et l'agent invité, qui venait de l'image. --- EN --- NixOS enables a firewall by default; none of the four other cloud images does. The service did listen and answered locally, but the host received NOTHING — not a refusal, silence until the timeout — and the monitor declared Odoo absent on a machine where it was running. Measured: 000 after 12 s, then 303 in 9 ms. A declarative system has only what is written: xmlsec1, without which Odoo refuses to install auth_saml; parallel and shfmt, called by bare name; growpart, whose absence made a disk grow return 0 while growing nothing; and the guest agent, which came from the image. Assisted-by: Claude Opus 5
306 lines
14 KiB
Nix
306 lines
14 KiB
Nix
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
#
|
|
# Dépendances système d'ERPLibre, pour NixOS.
|
|
#
|
|
# Le pendant DÉCLARATIF des quatre install_<distro>_dependency.sh. Ceux-là
|
|
# posent des paquets par apt, dnf, pacman ou zypper ; ici rien ne s'installe
|
|
# par une commande — ce qui doit rester se déclare, et ce fichier est cette
|
|
# déclaration. Un « nixos-rebuild switch » l'applique ; ce qui aurait été posé
|
|
# à la main disparaîtrait à la reconstruction suivante.
|
|
#
|
|
# Déposé en /etc/nixos/erplibre.nix par script/install/install_nixos_dependency.sh,
|
|
# qui l'ajoute aussi aux « imports » de la configuration de la machine.
|
|
#
|
|
# DEUX options portent tout le reste, et sans elles rien de ce dépôt ne
|
|
# fonctionne sur NixOS :
|
|
#
|
|
# services.envfs.enable — NixOS ne peuple pas /bin ni /usr/bin. Or le
|
|
# Makefile force « SHELL := /bin/bash » (toute cible make échouerait avant
|
|
# sa première ligne) et lib_python_provider.sh ne cherche l'interpréteur du
|
|
# système qu'en /usr/bin/pythonX.Y, jamais dans le PATH. envfs fabrique ces
|
|
# deux répertoires à la volée depuis le PATH : les scripts du dépôt marchent
|
|
# alors sans être réécrits.
|
|
#
|
|
# programs.nix-ld.enable — les binaires téléchargés (roues manylinux de pip,
|
|
# CPython précompilé de mise, archives amont) sont liés à
|
|
# /lib64/ld-linux-x86-64.so.2, qui n'existe pas ici. nix-ld le fournit et
|
|
# leur donne les bibliothèques listées plus bas.
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
{
|
|
# ── Ce que /bin, /usr/bin et l'éditeur de liens doivent porter ──────────
|
|
services.envfs.enable = true;
|
|
|
|
programs.nix-ld.enable = true;
|
|
# Les bibliothèques que réclament les roues manylinux d'Odoo : psycopg2 veut
|
|
# libpq, lxml veut libxml2/libxslt, cryptography veut openssl, pillow veut
|
|
# zlib et freetype. Une roue qui n'en trouve pas une échoue à l'IMPORT, pas
|
|
# à l'installation — donc bien après, et sans rapport apparent.
|
|
programs.nix-ld.libraries = with pkgs; [
|
|
stdenv.cc.cc.lib
|
|
zlib
|
|
openssl
|
|
libxml2
|
|
libxslt
|
|
libffi
|
|
libpq
|
|
freetype
|
|
fontconfig
|
|
libjpeg
|
|
glib
|
|
expat
|
|
bzip2
|
|
xz
|
|
ncurses
|
|
readline
|
|
sqlite
|
|
util-linux
|
|
openldap
|
|
cyrus_sasl
|
|
cups
|
|
libmysqlclient
|
|
];
|
|
|
|
# ── La base de données ───────────────────────────────────────────────────
|
|
# Le compte de service est SUPERUSER comme sur les autres distributions :
|
|
# ERPLibre crée et détruit des bases (migrations, copies neutralisées), ce
|
|
# qu'un rôle ordinaire ne peut pas faire.
|
|
services.postgresql = {
|
|
enable = true;
|
|
# PostGIS comme les quatre autres scripts de distribution le posent. Ici
|
|
# c'est une extension DU SERVEUR, pas un paquet du système : déclarée
|
|
# ailleurs, elle ne serait pas chargeable par « CREATE EXTENSION ».
|
|
extensions = ps: with ps; [ postgis ];
|
|
ensureUsers = [
|
|
{
|
|
name = "@EL_USER@";
|
|
ensureClauses.superuser = true;
|
|
ensureClauses.createdb = true;
|
|
ensureClauses.login = true;
|
|
}
|
|
];
|
|
};
|
|
|
|
# ── Les outils ───────────────────────────────────────────────────────────
|
|
# python3.12 : la version qu'attend ERPLibre (.python-odoo-version). envfs
|
|
# la rend visible en /usr/bin/python3.12, où lib_python_provider.sh la
|
|
# cherche — ni mise ni pyenv n'ont alors à télécharger quoi que ce soit.
|
|
#
|
|
# Les sorties « .dev » portent les en-têtes : sans elles, une roue absente
|
|
# du dépôt amont devrait se compiler et ne trouverait ni libpq-fe.h ni
|
|
# openssl/ssl.h. Elles ne servent qu'à ce cas, et ne coûtent que du disque.
|
|
environment.systemPackages = with pkgs; [
|
|
python312
|
|
python312Packages.pip
|
|
python312Packages.virtualenv
|
|
uv
|
|
nodejs_22
|
|
postgresql
|
|
postgresql.dev
|
|
git
|
|
gnumake
|
|
gcc
|
|
pkg-config
|
|
openssl
|
|
openssl.dev
|
|
zlib
|
|
zlib.dev
|
|
libxml2
|
|
libxml2.dev
|
|
libxslt
|
|
libxslt.dev
|
|
libffi
|
|
libffi.dev
|
|
# python-ldap n'a PAS de roue amont : il compile, et réclame lber.h
|
|
# (openldap) plus sasl.h. Sans ces sorties « .dev », « poetry install »
|
|
# s'arrête sur « fatal error: lber.h: No such file or directory ».
|
|
openldap
|
|
openldap.dev
|
|
cyrus_sasl
|
|
cyrus_sasl.dev
|
|
# « pg_config » est une dérivation à PART dans nixpkgs : il n'est ni dans
|
|
# postgresql ni dans sa sortie « .dev », et psycopg2 s'arrête sur
|
|
# « Error: pg_config executable not found ».
|
|
postgresql.pg_config
|
|
# pycups veut cups/http.h, mysqlclient veut mysql.h. Les quatre autres
|
|
# scripts posent les mêmes (libcups2-dev, cups-devel,
|
|
# mariadb-connector-c-devel, mariadb-libs).
|
|
cups
|
|
# « cups.lib » porte libcups.so, que « out » n'a pas : sans elle la
|
|
# compilation de pycups PASSE et l'édition de liens échoue sur « -lcups ».
|
|
cups.lib
|
|
cups.dev
|
|
libmysqlclient
|
|
libmysqlclient.dev
|
|
# « less » est le PAGINATEUR ; « nodePackages.less » est lessc, le
|
|
# compilateur LESS des assets Odoo. Les quatre autres scripts les posent
|
|
# tous deux, l'un par le gestionnaire du système et l'autre par « npm
|
|
# install -g » — geste impossible ici, le préfixe npm étant le store, en
|
|
# lecture seule. nixpkgs les porte : ils se déclarent comme le reste.
|
|
less
|
|
nodePackages.less
|
|
nodePackages.rtlcss
|
|
sshpass
|
|
curl
|
|
wget
|
|
unzip
|
|
wkhtmltopdf
|
|
# « xmlsec » pose bin/xmlsec1, que les CINQ autres plateformes posent
|
|
# aussi. Le manifeste d'auth_saml (OCA server-auth, présent dans
|
|
# l'addons_path) déclare « bin: ["xmlsec1"] », et Odoo REFUSE d'installer
|
|
# ou de mettre à niveau le module tant que le binaire n'est pas dans le
|
|
# PATH : « Unable to find 'xmlsec1' in path », en boîte de dialogue.
|
|
xmlsec
|
|
# « parallel » et « shfmt » sont appelés par leur NOM NU, l'un par
|
|
# script/database/db_drop_all.py, l'autre par script/maintenance/
|
|
# format_bash.sh. Sans parallel, « make db_drop_all » annonce des bases
|
|
# détruites qui ne l'ont pas été — une opération destructrice qui rend un
|
|
# succès qu'elle n'a pas obtenu.
|
|
parallel
|
|
shfmt
|
|
# « cloud-utils » porte growpart, qu'aucune autre voie ne fournit ici.
|
|
# L'agrandissement du disque s'écrit « sudo growpart … || true » : sans le
|
|
# binaire, il rend 0 sans rien agrandir, et la VM garde la taille de son
|
|
# image pendant que le déploiement annonce la taille demandée.
|
|
cloud-utils
|
|
];
|
|
|
|
# Le profil du système ne porte PAS « /include » : la liste par défaut de
|
|
# ce qui y est lié ne contient ni les en-têtes ni les fichiers pkg-config.
|
|
# Sans cette ligne, déclarer une sortie « .dev » ne met rien nulle part, et
|
|
# « 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 —
|
|
# la variable y serait vide. sessionVariables passe par pam_env, que toute
|
|
# session traverse, y compris celle-là.
|
|
environment.sessionVariables = {
|
|
CPATH = "/run/current-system/sw/include";
|
|
LIBRARY_PATH = "/run/current-system/sw/lib";
|
|
PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig";
|
|
};
|
|
|
|
# L'agent invité, DÉCLARÉ ici plutôt que reçu de l'image.
|
|
#
|
|
# L'image épinglée l'active déjà (son configuration.nix porte la ligne), et
|
|
# c'est précisément le problème : la garantie appartient alors au tiers qui
|
|
# rebâtit l'image, pas au dépôt. Les quatre autres distributions reçoivent
|
|
# l'agent par le runcmd du déploiement, qui appelle leur gestionnaire de
|
|
# paquets — geste impossible sur un système déclaratif. Ici, c'est cette
|
|
# ligne ou rien.
|
|
#
|
|
# Sans agent, la voie Proxmox perd sa source PRIMAIRE d'adresse et retombe
|
|
# sur « ip neigh ». Déclarer l'option deux fois est sans effet : une option
|
|
# booléenne ne rompt l'évaluation que sur des valeurs DIFFÉRENTES. Hors
|
|
# QEMU, l'unité ne démarre pas — elle n'a pas de section [Install] et
|
|
# attend une règle udev sur le port virtio.
|
|
services.qemuGuest.enable = true;
|
|
|
|
# Ce que l'agent doit trouver dans son PATH.
|
|
#
|
|
# Une unité systemd n'hérite pas du profil du système : celle-ci ne porte
|
|
# que coreutils, findutils, grep, sed et systemd. Or « guest-exec » exécute
|
|
# les commandes du produit DANS ce PATH, et l'agrandissement du disque
|
|
# commence par « findmnt -no SOURCE / » — absent, donc code 127 dès la
|
|
# première ligne.
|
|
systemd.services.qemu-guest-agent.path = with pkgs; [
|
|
util-linux
|
|
e2fsprogs
|
|
cloud-utils
|
|
];
|
|
|
|
# Le port d'Odoo, OUVERT.
|
|
#
|
|
# NixOS active un pare-feu par défaut ; aucune des images cloud des quatre
|
|
# autres distributions n'en active un. Le service écoute bien sur
|
|
# 0.0.0.0:8069 et répond en local, mais l'extérieur ne reçoit RIEN — pas un
|
|
# refus, un silence, donc une attente jusqu'au délai. Ce qui sonde depuis
|
|
# l'hôte conclut « Odoo absent » sur une machine où il tourne, et le journal
|
|
# de l'installation ne porte aucune trace de la cause : elle est dans le
|
|
# pare-feu, pas dans l'application.
|
|
#
|
|
# 8069 SEUL. Le port websocket est configuré à 8072, mais Odoo ne le lie
|
|
# qu'en mode multi-processus, que cette configuration n'emploie pas : rien
|
|
# n'y écoute, et l'ouvrir donnerait un port béant sans service derrière.
|
|
# PostgreSQL n'écoute déjà que sur la boucle locale et n'a rien à ouvrir.
|
|
networking.firewall.allowedTCPPorts = [ 8069 ];
|
|
|
|
# Le service ERPLibre, DÉCLARÉ et non écrit.
|
|
#
|
|
# Sur toute autre distribution l'installation dépose l'unité par
|
|
# « tee /etc/systemd/system/erplibre.service ». Ici /etc est généré depuis
|
|
# le store et monté en lecture seule : le tee échoue sur « Read-only file
|
|
# system », et l'installation entière rend 1 à sa dernière étape, après que
|
|
# tout le reste a réussi.
|
|
#
|
|
# « wantedBy » est l'équivalent déclaratif de « systemctl enable » :
|
|
# l'activation par lien symbolique écrirait elle aussi dans /etc.
|
|
#
|
|
# L'interpréteur vient du STORE et non de /bin. /bin et /usr/bin sont ici
|
|
# un montage FUSE d'envfs, et systemd résout l'exécutable d'ExecStart
|
|
# lui-même, hors de portée de ce montage : « /bin/bash » y rend
|
|
# « 203/EXEC, Unable to locate executable ». Avec Restart=always, l'unité
|
|
# boucle alors indéfiniment. La même raison vaut pour /usr/bin/env, donc
|
|
# le shebang de run.sh ne suffirait pas davantage.
|
|
#
|
|
# La première reconstruction déclare le service AVANT qu'ERPLibre ne soit
|
|
# installé, et son démarrage échoue alors — c'est attendu, et c'est
|
|
# exactement le cas que « nixos-rebuild rend 4 » recouvre. L'installation
|
|
# le relance une fois le dépôt en place.
|
|
systemd.services.erplibre = {
|
|
description = "ERPLibre";
|
|
requires = [ "postgresql.service" ];
|
|
after = [ "network.target" "network-online.target" "postgresql.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
# Une unité systemd ne reçoit PAS le PATH d'une session : le sien ne
|
|
# porte que coreutils, findutils, grep, sed et systemd.
|
|
#
|
|
# bash — run.sh lance odoo_bin.sh et lib_db_select.sh, dont le shebang est
|
|
# « #!/usr/bin/env bash ». env est là, bash non : « env: 'bash': No such
|
|
# file or directory », et run.sh s'arrête avant Odoo.
|
|
#
|
|
# python3 — la sonde de réveil tourne AVANT que odoo_bin.sh n'active le
|
|
# venv, donc avec le python du système. Son échec est silencieux
|
|
# (« 2>/dev/null ») : sans elle, la première page ouverte attendrait le
|
|
# chargement du registre sans que rien ne le dise.
|
|
path = with pkgs; [ bash python312 ];
|
|
# L'unité est déclarée par le module, donc démarrée par la
|
|
# reconstruction — qui a lieu PENDANT « make install_os », alors que la
|
|
# source d'Odoo n'arrive qu'à « make install_odoo_18 ». Sans condition,
|
|
# run.sh échoue sur un odoo-bin absent et « Restart = always » le rejoue
|
|
# toutes les cinq secondes jusqu'à ce que l'installation le pose — vingt
|
|
# et un échecs mesurés sur une pose ordinaire, et un « nixos-rebuild »
|
|
# qui rend 4 parce qu'une unité n'a pas démarré.
|
|
#
|
|
# Une CONDITION, et non une dépendance : systemd saute l'unité en le
|
|
# disant une fois, sans la marquer en échec, et la démarre d'elle-même
|
|
# au prochain déclenchement une fois le fichier là. Le motif évite de
|
|
# figer la version d'Odoo ici, où elle vieillirait en silence.
|
|
unitConfig.ConditionPathExistsGlob = "@EL_DIR@/odoo*/odoo/odoo-bin";
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "@EL_USER@";
|
|
WorkingDirectory = "@EL_DIR@";
|
|
ExecStart = "${pkgs.bash}/bin/bash @EL_DIR@/run.sh";
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
StandardOutput = "journal+console";
|
|
};
|
|
};
|
|
}
|