[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
#
|
|
|
|
|
# Pose erplibre_go_qemu_cache : le miroir de téléchargement des VM QEMU
|
|
|
|
|
# locales. Compile depuis les sources du dépôt, installe une unité systemd,
|
|
|
|
|
# et laisse le service poser lui-même ses règles de détournement.
|
|
|
|
|
#
|
|
|
|
|
# Les règles de pare-feu ne sont PAS écrites par ce script. L'unité les pose
|
|
|
|
|
# à son démarrage et les retire à son arrêt, en tubant « --print-nft » dans
|
|
|
|
|
# nft : une VM ne peut donc pas rester détournée vers un cache éteint, et le
|
|
|
|
|
# jeu de règles vient du code Go que les tests vérifient, jamais d'une copie.
|
|
|
|
|
#
|
|
|
|
|
# Usage :
|
|
|
|
|
# sudo bash install_qemu_cache.sh
|
|
|
|
|
# sudo EL_HTTP_PORT=8898 EL_TLS_PORT=8899 EL_BRIDGE=virbr0 \
|
|
|
|
|
# EL_SUBNET=192.168.122.0/24 bash install_qemu_cache.sh
|
|
|
|
|
|
|
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
EL_HTTP_PORT="${EL_HTTP_PORT:-8898}"
|
|
|
|
|
EL_TLS_PORT="${EL_TLS_PORT:-8899}"
|
|
|
|
|
# Vides par défaut : le pont et le sous-réseau sont LUS dans libvirt plus bas.
|
|
|
|
|
# Les supposer est le défaut qui rend l'installation silencieusement inutile —
|
|
|
|
|
# un hôte dont le réseau par défaut a été déplacé sur un /24 libre, ce qui
|
|
|
|
|
# arrive dès que 192.168.122 entre en collision, voit ses VM échapper au
|
|
|
|
|
# détournement sans que rien ne le signale.
|
|
|
|
|
EL_BRIDGE="${EL_BRIDGE:-}"
|
[ADD] cache qemu : messages du binaire en anglais ou en français
Le binaire ne parlait que français alors que le menu suit EL_LANG : un
opérateur anglophone lisait un statut qu'il ne comprenait pas. Chaque message
humain passe par T(), dont la clé EST le texte français, et un catalogue
anglais le traduit ; la langue vient de --lang, puis d'EL_LANG, le français
restant le défaut. Le menu demande le français pour ce qu'il RELIT, ses
motifs ne connaissant que cette langue. Un test refuse un message sans
traduction, une traduction orpheline et un verbe de format qui diverge.
--- EN ---
The binary spoke French only while the menu follows EL_LANG: an English
reader got a status he could not read. Every human message goes through T(),
whose key IS the French text, and an English catalogue translates it; the
language comes from --lang, then EL_LANG, French remaining the default. The
menu asks for French for what it READS BACK, its patterns knowing that
language alone. A test refuses a message without translation, an orphan
translation, and a format verb that drifts.
Assisted-by: Claude Opus 5
2026-09-16 02:59:01 -04:00
|
|
|
# Langue des messages du service : « fr » ou « en », comme EL_LANG de todo.py.
|
|
|
|
|
EL_LANG="${EL_LANG:-fr}"
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
EL_SUBNET="${EL_SUBNET:-}"
|
|
|
|
|
EL_NET="${EL_NET:-default}"
|
|
|
|
|
EL_CACHE_DIR="${EL_CACHE_DIR:-/var/cache/erplibre_go_qemu_cache}"
|
|
|
|
|
EL_CA_DIR="${EL_CA_DIR:-/var/lib/erplibre_go_qemu_cache}"
|
|
|
|
|
EL_ACCESS_LOG="${EL_ACCESS_LOG:-/var/log/erplibre_go_qemu_cache.jsonl}"
|
|
|
|
|
EL_EXCLUDE="${EL_EXCLUDE:-}"
|
|
|
|
|
EL_BYPASS_FILE="${EL_BYPASS_FILE:-/etc/erplibre_go_qemu_cache/bypass}"
|
|
|
|
|
# Les dépôts git tenus en miroir. Sous le répertoire du cache : c'est le
|
|
|
|
|
# seul que le service peut écrire, et le miroir grossit comme le reste.
|
|
|
|
|
EL_GIT_MIRROR_DIR="${EL_GIT_MIRROR_DIR:-${EL_CACHE_DIR:-/var/cache/erplibre_go_qemu_cache}/git}"
|
|
|
|
|
|
|
|
|
|
BIN="/usr/local/bin/erplibre_go_qemu_cache"
|
2026-09-16 03:00:17 -04:00
|
|
|
CONF_DIR="${EL_CONF_DIR:-/etc/erplibre_go_qemu_cache}"
|
|
|
|
|
PURGE_UNIT="/etc/systemd/system/erplibre-go-qemu-cache-purge.service"
|
|
|
|
|
PURGE_TIMER="/etc/systemd/system/erplibre-go-qemu-cache-purge.timer"
|
|
|
|
|
|
|
|
|
|
# Les réglages de nettoyage choisis depuis le menu survivent à une
|
|
|
|
|
# réinstallation : le fichier env est réécrit en entier, et les retrouver vides
|
|
|
|
|
# changerait en silence le comportement choisi. Une valeur passée dans
|
|
|
|
|
# l'environnement, même vide, l'emporte — c'est ainsi qu'on les désactive.
|
|
|
|
|
reglage_existant() {
|
|
|
|
|
sed -n "s/^$1=//p" "${CONF_DIR}/env" 2>/dev/null | head -n 1
|
|
|
|
|
}
|
|
|
|
|
EL_PURGE_AGE="${EL_PURGE_AGE-$(reglage_existant EL_PURGE_AGE)}"
|
|
|
|
|
EL_MAX_SIZE="${EL_MAX_SIZE-$(reglage_existant EL_MAX_SIZE)}"
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
UNIT="/etc/systemd/system/erplibre-go-qemu-cache.service"
|
|
|
|
|
SERVICE_USER="elqcache"
|
|
|
|
|
GO_MIN_MAJOR=1
|
|
|
|
|
GO_MIN_MINOR=21
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
# EL_SRC_DIR passe outre la déduction : le script se retrouve ainsi lançable
|
|
|
|
|
# depuis un chemin copié, et une vérification peut appeler UNE de ses
|
|
|
|
|
# fonctions sans que la déduction échoue faute de dépôt autour.
|
|
|
|
|
SRC_DIR="${EL_SRC_DIR:-$(cd "${SCRIPT_DIR}/../qemu_cache" && pwd)}"
|
|
|
|
|
|
|
|
|
|
log() { echo "[cache QEMU] $*"; }
|
|
|
|
|
die() {
|
|
|
|
|
echo "[cache QEMU] ERREUR : $*" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
check_root() {
|
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
|
|
|
die "à lancer en root : sudo bash $0"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detect_os() {
|
|
|
|
|
[ -f /etc/os-release ] || die "système inconnu (pas de /etc/os-release)"
|
|
|
|
|
# shellcheck disable=SC1091
|
|
|
|
|
. /etc/os-release
|
|
|
|
|
OS="${ID}"
|
|
|
|
|
OS_LIKE="${ID_LIKE:-}"
|
|
|
|
|
log "système : ${OS}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_arch_like() {
|
|
|
|
|
case "$OS" in
|
|
|
|
|
arch | manjaro | endeavouros | artix | garuda) return 0 ;;
|
|
|
|
|
*) echo "$OS_LIKE" | grep -q "arch" && return 0 || return 1 ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_debian_like() {
|
|
|
|
|
case "$OS" in
|
|
|
|
|
ubuntu | debian | linuxmint | pop | elementary | raspbian) return 0 ;;
|
|
|
|
|
*) echo "$OS_LIKE" | grep -qE "debian|ubuntu" && return 0 || return 1 ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_fedora_like() {
|
|
|
|
|
case "$OS" in
|
|
|
|
|
fedora | rhel | centos | almalinux | rocky) return 0 ;;
|
|
|
|
|
*) echo "$OS_LIKE" | grep -qE "fedora|rhel" && return 0 || return 1 ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Le réseau que les VM utilisent VRAIMENT
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
# detecter_reseau remplit EL_BRIDGE et EL_SUBNET depuis libvirt.
|
|
|
|
|
#
|
|
|
|
|
# Le réseau « default » ne sert pas toujours 192.168.122.0/24 : il est déplacé
|
|
|
|
|
# sur un /24 libre quand ce préfixe entre en collision avec ce que l'hôte
|
|
|
|
|
# route déjà — le cas de tout orchestrateur qui est lui-même une VM. Poser des
|
|
|
|
|
# règles sur le mauvais préfixe laisse une installation qui réussit, des
|
|
|
|
|
# règles bien présentes dans le noyau, et un cache que personne ne traverse.
|
|
|
|
|
detecter_reseau() {
|
|
|
|
|
local xml adresse masque
|
|
|
|
|
# Donnés à la main, les deux se suffisent. Une machine dont le réseau
|
|
|
|
|
# libvirt n'est pas démarré — ou qui gère son pont autrement — doit pouvoir
|
|
|
|
|
# poser le cache en nommant ce pont. Sonder quand même faisait mourir
|
|
|
|
|
# l'installation sur une absence qui n'empêchait rien, et le contournement
|
|
|
|
|
# annoncé en tête de ce fichier ne servait à rien.
|
|
|
|
|
if [ -n "$EL_BRIDGE" ] && [ -n "$EL_SUBNET" ]; then
|
|
|
|
|
log "réseau donné à la main : ${EL_BRIDGE}, ${EL_SUBNET}"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
xml="$(virsh -c qemu:///system net-dumpxml "$EL_NET" 2>/dev/null)" || {
|
|
|
|
|
die "réseau libvirt « ${EL_NET} » introuvable — virsh -c qemu:///system net-list" \
|
|
|
|
|
"le démarrer : sudo virsh -c qemu:///system net-start ${EL_NET}" \
|
|
|
|
|
"ou nommer le pont : EL_BRIDGE=virbr0 EL_SUBNET=192.168.122.0/24"
|
|
|
|
|
}
|
|
|
|
|
[ -n "$EL_BRIDGE" ] || EL_BRIDGE="$(echo "$xml" |
|
|
|
|
|
sed -n "s/.*<bridge name='\([^']*\)'.*/\1/p" | head -1)"
|
|
|
|
|
if [ -z "$EL_SUBNET" ]; then
|
|
|
|
|
adresse="$(echo "$xml" | sed -n "s/.*<ip address='\([^']*\)'.*/\1/p" | head -1)"
|
|
|
|
|
masque="$(echo "$xml" | sed -n "s/.*netmask='\([^']*\)'.*/\1/p" | head -1)"
|
|
|
|
|
[ "$masque" = "255.255.255.0" ] || die \
|
|
|
|
|
"le réseau « ${EL_NET} » ne sert pas un /24 (masque ${masque:-inconnu}) :" \
|
|
|
|
|
"passer EL_SUBNET à la main"
|
|
|
|
|
EL_SUBNET="${adresse%.*}.0/24"
|
|
|
|
|
fi
|
|
|
|
|
[ -n "$EL_BRIDGE" ] && [ -n "$EL_SUBNET" ] || die \
|
|
|
|
|
"pont ou sous-réseau illisibles dans le réseau « ${EL_NET} »"
|
|
|
|
|
log "réseau lu dans libvirt : ${EL_BRIDGE}, ${EL_SUBNET}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Go
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
# find_go cherche au-delà du PATH de root : un Go posé par mise ou par un
|
|
|
|
|
# gestionnaire de versions vit dans le compte de l'opérateur, que sudo ne
|
|
|
|
|
# traverse pas.
|
|
|
|
|
find_go() {
|
|
|
|
|
local candidat
|
|
|
|
|
for candidat in \
|
|
|
|
|
"$(command -v go 2>/dev/null || true)" \
|
|
|
|
|
/usr/local/go/bin/go \
|
|
|
|
|
/usr/lib/go/bin/go; do
|
|
|
|
|
[ -n "$candidat" ] && [ -x "$candidat" ] && echo "$candidat" && return 0
|
|
|
|
|
done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
go_assez_recent() {
|
|
|
|
|
local go_bin="$1" v major minor
|
|
|
|
|
v="$("$go_bin" version 2>/dev/null | sed -n 's/.*go\([0-9]*\)\.\([0-9]*\).*/\1 \2/p')"
|
|
|
|
|
[ -z "$v" ] && return 1
|
|
|
|
|
major="${v% *}"
|
|
|
|
|
minor="${v#* }"
|
|
|
|
|
[ "$major" -gt "$GO_MIN_MAJOR" ] && return 0
|
|
|
|
|
[ "$major" -eq "$GO_MIN_MAJOR" ] && [ "$minor" -ge "$GO_MIN_MINOR" ] && return 0
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
installer_go() {
|
|
|
|
|
log "Go absent ou trop ancien : installation par le gestionnaire de paquets"
|
|
|
|
|
if is_arch_like; then
|
|
|
|
|
pacman -S --needed --noconfirm go
|
|
|
|
|
elif is_debian_like; then
|
|
|
|
|
DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 update -qq
|
|
|
|
|
DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 \
|
|
|
|
|
install -y golang-go
|
|
|
|
|
elif is_fedora_like; then
|
|
|
|
|
dnf install -y golang
|
|
|
|
|
else
|
|
|
|
|
die "distribution non prévue : installer Go ${GO_MIN_MAJOR}.${GO_MIN_MINOR}+ à la main"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assurer_go() {
|
|
|
|
|
local go_bin
|
|
|
|
|
if go_bin="$(find_go)" && go_assez_recent "$go_bin"; then
|
|
|
|
|
GO="$go_bin"
|
|
|
|
|
log "Go : $("$GO" version)"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
installer_go
|
|
|
|
|
go_bin="$(find_go)" || die "Go reste introuvable après installation"
|
|
|
|
|
go_assez_recent "$go_bin" ||
|
|
|
|
|
die "le Go de la distribution est plus ancien que ${GO_MIN_MAJOR}.${GO_MIN_MINOR} ; poser une version récente à la main"
|
|
|
|
|
GO="$go_bin"
|
|
|
|
|
log "Go : $("$GO" version)"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Compilation
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
compiler() {
|
|
|
|
|
[ -f "${SRC_DIR}/go.mod" ] || die "sources absentes : ${SRC_DIR}"
|
|
|
|
|
log "compilation depuis ${SRC_DIR}"
|
|
|
|
|
# Le cache de compilation dans un temporaire : root n'a pas à laisser
|
|
|
|
|
# d'état dans son compte pour une compilation unique.
|
|
|
|
|
local gocache
|
|
|
|
|
gocache="$(mktemp -d)"
|
|
|
|
|
(
|
|
|
|
|
cd "$SRC_DIR"
|
|
|
|
|
GOCACHE="$gocache" GOFLAGS=-mod=mod "$GO" build -o "$BIN" .
|
|
|
|
|
)
|
|
|
|
|
rm -rf "$gocache"
|
|
|
|
|
chmod 0755 "$BIN"
|
|
|
|
|
log "posé : ${BIN} ($("$BIN" --version))"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Compte, répertoires, autorité
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
preparer_etat() {
|
|
|
|
|
if ! id "$SERVICE_USER" >/dev/null 2>&1; then
|
|
|
|
|
useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER" \
|
|
|
|
|
2>/dev/null ||
|
|
|
|
|
useradd --system --no-create-home --shell /sbin/nologin "$SERVICE_USER"
|
|
|
|
|
log "compte de service créé : ${SERVICE_USER}"
|
|
|
|
|
fi
|
|
|
|
|
mkdir -p "$EL_CACHE_DIR" "$EL_CA_DIR" "$CONF_DIR" "$EL_GIT_MIRROR_DIR"
|
|
|
|
|
touch "$EL_ACCESS_LOG"
|
|
|
|
|
# La liste des exceptions appartient à root : elle décide qui échappe au
|
|
|
|
|
# cache, et le compte du service ne doit pas pouvoir s'y ajouter.
|
|
|
|
|
mkdir -p "$(dirname "$EL_BYPASS_FILE")"
|
|
|
|
|
[ -f "$EL_BYPASS_FILE" ] || cat >"$EL_BYPASS_FILE" <<'BYPASS'
|
|
|
|
|
# Exceptions du cache de téléchargement des VM QEMU.
|
|
|
|
|
# Une ligne « <MAC> <nom de la VM> » par machine que le détournement doit
|
|
|
|
|
# ignorer. Relu à chaque démarrage du service.
|
|
|
|
|
BYPASS
|
|
|
|
|
chmod 0644 "$EL_BYPASS_FILE"
|
|
|
|
|
chown -R "${SERVICE_USER}:${SERVICE_USER}" "$EL_CACHE_DIR" "$EL_CA_DIR" "$EL_ACCESS_LOG" "$EL_GIT_MIRROR_DIR"
|
|
|
|
|
chmod 0755 "$EL_CACHE_DIR" "$EL_CA_DIR"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# L'autorité est créée ICI plutôt qu'au premier démarrage : l'installation
|
|
|
|
|
# doit pouvoir en dire l'empreinte, que l'opérateur compare ensuite dans la VM.
|
|
|
|
|
creer_autorite() {
|
|
|
|
|
runuser -u "$SERVICE_USER" -- "$BIN" --init-ca --ca-dir "$EL_CA_DIR" ||
|
|
|
|
|
die "l'autorité n'a pas pu être créée dans ${EL_CA_DIR}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Configuration et unité
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
ecrire_config() {
|
|
|
|
|
cat >"${CONF_DIR}/env" <<CONF
|
|
|
|
|
# Réglages du cache de téléchargement des VM QEMU.
|
|
|
|
|
# Changer une valeur puis : systemctl restart erplibre-go-qemu-cache
|
|
|
|
|
EL_CACHE_DIR=${EL_CACHE_DIR}
|
|
|
|
|
EL_CA_DIR=${EL_CA_DIR}
|
|
|
|
|
EL_HTTP_PORT=${EL_HTTP_PORT}
|
|
|
|
|
EL_TLS_PORT=${EL_TLS_PORT}
|
|
|
|
|
EL_BRIDGE=${EL_BRIDGE}
|
|
|
|
|
EL_SUBNET=${EL_SUBNET}
|
|
|
|
|
EL_ACCESS_LOG=${EL_ACCESS_LOG}
|
|
|
|
|
EL_EXCLUDE=${EL_EXCLUDE}
|
|
|
|
|
EL_BYPASS_FILE=${EL_BYPASS_FILE}
|
|
|
|
|
EL_GIT_MIRROR_DIR=${EL_GIT_MIRROR_DIR}
|
[ADD] cache qemu : messages du binaire en anglais ou en français
Le binaire ne parlait que français alors que le menu suit EL_LANG : un
opérateur anglophone lisait un statut qu'il ne comprenait pas. Chaque message
humain passe par T(), dont la clé EST le texte français, et un catalogue
anglais le traduit ; la langue vient de --lang, puis d'EL_LANG, le français
restant le défaut. Le menu demande le français pour ce qu'il RELIT, ses
motifs ne connaissant que cette langue. Un test refuse un message sans
traduction, une traduction orpheline et un verbe de format qui diverge.
--- EN ---
The binary spoke French only while the menu follows EL_LANG: an English
reader got a status he could not read. Every human message goes through T(),
whose key IS the French text, and an English catalogue translates it; the
language comes from --lang, then EL_LANG, French remaining the default. The
menu asks for French for what it READS BACK, its patterns knowing that
language alone. A test refuses a message without translation, an orphan
translation, and a format verb that drifts.
Assisted-by: Claude Opus 5
2026-09-16 02:59:01 -04:00
|
|
|
EL_LANG=${EL_LANG}
|
2026-09-16 03:00:17 -04:00
|
|
|
# Nettoyage automatique, chaque jour ; vide = désactivé. Réglable depuis le
|
|
|
|
|
# menu du cache de todo.py, sans réinstaller.
|
|
|
|
|
# EL_PURGE_AGE : ce qui n'a pas servi depuis ce délai part (ex. 90j).
|
|
|
|
|
# EL_MAX_SIZE : au-delà, le moins récemment servi part (ex. 50G).
|
|
|
|
|
EL_PURGE_AGE=${EL_PURGE_AGE}
|
|
|
|
|
EL_MAX_SIZE=${EL_MAX_SIZE}
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
CONF
|
|
|
|
|
chmod 0644 "${CONF_DIR}/env"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Le détournement est choisi une fois, à l'installation : nft s'il est là,
|
|
|
|
|
# iptables sinon. Les deux jeux de règles viennent du binaire.
|
|
|
|
|
regles_apply_cmd() {
|
|
|
|
|
if command -v nft >/dev/null 2>&1; then
|
|
|
|
|
echo "${BIN} --print-nft --bridge \${EL_BRIDGE} --subnet \${EL_SUBNET} --http-port \${EL_HTTP_PORT} --tls-port \${EL_TLS_PORT} --bypass-file \${EL_BYPASS_FILE} | nft -f -"
|
|
|
|
|
else
|
|
|
|
|
echo "${BIN} --print-iptables --bridge \${EL_BRIDGE} --subnet \${EL_SUBNET} --http-port \${EL_HTTP_PORT} --tls-port \${EL_TLS_PORT} --bypass-file \${EL_BYPASS_FILE} | sh -s"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
regles_clear_cmd() {
|
|
|
|
|
if command -v nft >/dev/null 2>&1; then
|
|
|
|
|
echo "nft delete table ip erplibre_qemu_cache 2>/dev/null || true"
|
|
|
|
|
else
|
|
|
|
|
echo "${BIN} --print-iptables --bridge \${EL_BRIDGE} --subnet \${EL_SUBNET} --http-port \${EL_HTTP_PORT} --tls-port \${EL_TLS_PORT} --bypass-file \${EL_BYPASS_FILE} | sed 's/ -A / -D /' | sh -s || true"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ecrire_unite() {
|
|
|
|
|
# « + » devant une commande la fait tourner en root même sous User= : poser
|
|
|
|
|
# une règle de pare-feu demande un privilège que le service n'a pas, et ne
|
|
|
|
|
# doit pas avoir, pendant qu'il sert des fichiers.
|
|
|
|
|
cat >"$UNIT" <<UNITE
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Cache de téléchargement ERPLibre pour les VM QEMU
|
|
|
|
|
After=network.target libvirtd.service
|
|
|
|
|
Wants=libvirtd.service
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
|
|
|
|
EnvironmentFile=${CONF_DIR}/env
|
|
|
|
|
User=${SERVICE_USER}
|
|
|
|
|
Group=${SERVICE_USER}
|
|
|
|
|
ExecStartPre=+/bin/sh -c '$(regles_apply_cmd)'
|
|
|
|
|
ExecStart=${BIN} --cache-dir \${EL_CACHE_DIR} --ca-dir \${EL_CA_DIR} --http-port \${EL_HTTP_PORT} --tls-port \${EL_TLS_PORT} --bridge \${EL_BRIDGE} --subnet \${EL_SUBNET} --access-log \${EL_ACCESS_LOG} --exclude \${EL_EXCLUDE} --git-mirror-dir \${EL_GIT_MIRROR_DIR}
|
|
|
|
|
ExecStopPost=+/bin/sh -c '$(regles_clear_cmd)'
|
|
|
|
|
Restart=on-failure
|
|
|
|
|
RestartSec=5
|
|
|
|
|
NoNewPrivileges=true
|
|
|
|
|
ProtectSystem=strict
|
|
|
|
|
ProtectHome=true
|
|
|
|
|
PrivateTmp=true
|
|
|
|
|
ReadWritePaths=${EL_CACHE_DIR} ${EL_CA_DIR} ${EL_ACCESS_LOG} ${EL_GIT_MIRROR_DIR}
|
|
|
|
|
Environment=HOME=${EL_CACHE_DIR}
|
|
|
|
|
Environment=GIT_CONFIG_GLOBAL=/dev/null
|
|
|
|
|
Environment=GIT_TERMINAL_PROMPT=0
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
UNITE
|
|
|
|
|
chmod 0644 "$UNIT"
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 03:00:17 -04:00
|
|
|
ecrire_purge() {
|
|
|
|
|
# Un minuteur quotidien qui ne fait RIEN tant que les deux réglages sont
|
|
|
|
|
# vides : le nettoyage reste manuel par défaut, et le régler suffit à
|
|
|
|
|
# l'activer, sans réinstaller. Sous le compte du service, propriétaire du
|
|
|
|
|
# magasin : un effacement en root laisserait des répertoires que le service
|
|
|
|
|
# ne pourrait plus écrire. « $$ » rend un « $ » au shell : la variable vient
|
|
|
|
|
# de l'EnvironmentFile au moment de l'exécution, jamais de ce script.
|
|
|
|
|
cat >"$PURGE_UNIT" <<UNITE
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Nettoyage automatique du cache de téléchargement ERPLibre
|
|
|
|
|
After=erplibre-go-qemu-cache.service
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
EnvironmentFile=${CONF_DIR}/env
|
|
|
|
|
User=${SERVICE_USER}
|
|
|
|
|
Group=${SERVICE_USER}
|
|
|
|
|
ExecStart=/bin/sh -c 'if [ -n "\$\$EL_PURGE_AGE" ]; then ${BIN} --cache-dir "\$\$EL_CACHE_DIR" --git-mirror-dir "\$\$EL_GIT_MIRROR_DIR" --lang "\$\$EL_LANG" --purge-older-than "\$\$EL_PURGE_AGE"; fi; if [ -n "\$\$EL_MAX_SIZE" ]; then ${BIN} --cache-dir "\$\$EL_CACHE_DIR" --git-mirror-dir "\$\$EL_GIT_MIRROR_DIR" --lang "\$\$EL_LANG" --purge-to-size "\$\$EL_MAX_SIZE"; fi'
|
|
|
|
|
NoNewPrivileges=true
|
|
|
|
|
ProtectSystem=strict
|
|
|
|
|
ProtectHome=true
|
|
|
|
|
PrivateTmp=true
|
|
|
|
|
ReadWritePaths=${EL_CACHE_DIR} ${EL_GIT_MIRROR_DIR}
|
|
|
|
|
UNITE
|
|
|
|
|
cat >"$PURGE_TIMER" <<MINUTEUR
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Nettoyage quotidien du cache de téléchargement ERPLibre
|
|
|
|
|
|
|
|
|
|
[Timer]
|
|
|
|
|
OnCalendar=daily
|
|
|
|
|
Persistent=true
|
|
|
|
|
RandomizedDelaySec=1h
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=timers.target
|
|
|
|
|
MINUTEUR
|
|
|
|
|
chmod 0644 "$PURGE_UNIT" "$PURGE_TIMER"
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
main() {
|
|
|
|
|
check_root
|
|
|
|
|
detect_os
|
|
|
|
|
detecter_reseau
|
|
|
|
|
assurer_go
|
|
|
|
|
compiler
|
|
|
|
|
preparer_etat
|
|
|
|
|
creer_autorite
|
|
|
|
|
ecrire_config
|
|
|
|
|
ecrire_unite
|
2026-09-16 03:00:17 -04:00
|
|
|
ecrire_purge
|
|
|
|
|
systemctl enable --now erplibre-go-qemu-cache-purge.timer >/dev/null 2>&1 || true
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
|
|
|
|
systemctl enable erplibre-go-qemu-cache.service >/dev/null 2>&1 || true
|
|
|
|
|
systemctl restart erplibre-go-qemu-cache.service
|
|
|
|
|
|
|
|
|
|
# Un service qui ne tient pas doit le dire ici, et non se découvrir au
|
|
|
|
|
# prochain déploiement de VM.
|
|
|
|
|
sleep 2
|
|
|
|
|
if ! systemctl is-active --quiet erplibre-go-qemu-cache.service; then
|
|
|
|
|
systemctl status --no-pager -l erplibre-go-qemu-cache.service || true
|
|
|
|
|
die "le service ne tient pas ; les règles ont été retirées à son arrêt"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log ""
|
|
|
|
|
log "cache actif sur ${EL_HTTP_PORT} (http) et ${EL_TLS_PORT} (tls)"
|
|
|
|
|
log "détournement : ${EL_BRIDGE}, depuis ${EL_SUBNET}, sortie du /24 seule"
|
|
|
|
|
log "autorité à faire approuver dans chaque VM :"
|
|
|
|
|
log " ${EL_CA_DIR}/ca.crt"
|
|
|
|
|
"$BIN" --status --cache-dir "$EL_CACHE_DIR" --ca-dir "$EL_CA_DIR" \
|
|
|
|
|
--git-mirror-dir "$EL_GIT_MIRROR_DIR" || true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|