From 90b7604f1f1db23f1a0156742a160b2f34ba9d66 Mon Sep 17 00:00:00 2001 From: moha-bekh Date: Sun, 18 Oct 2026 06:28:39 +0200 Subject: [PATCH 1/2] Documentation en anglais MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README, roadmap et guide de démo traduits. Les exemples de sortie du README restent tels que la CLI les affiche, en français. Co-Authored-By: Claude Opus 5.5 --- README.md | 298 ++++++++++++++++++++-------------------- docs/ROADMAP.md | 264 +++++++++++++++++------------------ docs/how-to-use/demo.md | 103 +++++++------- 3 files changed, 333 insertions(+), 332 deletions(-) diff --git a/README.md b/README.md index e59db85..4d7eb90 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,71 @@ # distroproof -[![CI](https://github.com/moha-bekh/distroproof/actions/workflows/ci.yml/badge.svg)](https://github.com/moha-bekh/distroproof/actions/workflows/ci.yml) +[![CI](https://github.com/devutils-org/distroproof/actions/workflows/ci.yml/badge.svg)](https://github.com/devutils-org/distroproof/actions/workflows/ci.yml) -> distroless retire la distro de ton image. distroproof vérifie que ton -> programme tient sur toutes les distros. +> distroless takes the distro out of your image. distroproof checks that your +> program holds up on every distro. -Un programme qui marche sur ta machine peut casser ailleurs : musl au lieu de -glibc, noyau RHEL 4.18, BTF absent, `perf_event_paranoid` à 3, lockdown actif… -distroproof décrit précisément un environnement Linux (userspace et noyau), -lance ton programme dans des VM qui tournent avec le **vrai noyau** de chaque -distro cible, puis relie chaque échec à la différence d'environnement qui -l'explique. +A program that works on your machine can break elsewhere: musl instead of +glibc, a RHEL 4.18 kernel, no BTF, `perf_event_paranoid` set to 3, lockdown +enabled… distroproof describes a Linux environment precisely (userspace and +kernel), runs your program in VMs booted on the **real kernel** of each target +distro, then ties every failure to the environment difference that explains +it. -Toutes les phases du POC sont implémentées : la sonde `envprobe` (utilisable -seule) et l'orchestrateur `distroproof` (VM, matrice, rapport, TUI). +Every phase of the POC is implemented: the `envprobe` probe (usable on its +own) and the `distroproof` orchestrator (VMs, matrix, report, TUI). -## Avancement +## Status -| Phase | Contenu | État | +| Phase | Scope | Status | | --- | --- | --- | -| 0 | Workspace, binaire statique musl, CI | ✅ | -| 1 | Sonde `envprobe` : 18 catégories (kernel, BPF, perf, LSM, libc…) | ✅ | -| 2 | Rapport JSON versionné, schéma publié, `envprobe diff` | ✅ | -| 3 | VM QEMU/KVM avec noyau et rootfs de distro | ✅ | -| 4 | Matrice déclarative `distroproof.toml` | ✅ | -| 5 | Rapport de matrice, corrélation des échecs, JUnit / Markdown | ✅ | -| 6 | TUI de monitoring | ✅ | - -Détail et critères de sortie : [docs/ROADMAP.md](docs/ROADMAP.md). Démo de bout en bout -sur de vraies VM : [`scripts/demo.sh`](scripts/demo.sh) et +| 0 | Workspace, static musl binary, CI | ✅ | +| 1 | `envprobe` probe: 18 categories (kernel, BPF, perf, LSM, libc…) | ✅ | +| 2 | Versioned JSON report, published schema, `envprobe diff` | ✅ | +| 3 | QEMU/KVM VMs with the distro's kernel and rootfs | ✅ | +| 4 | Declarative matrix in `distroproof.toml` | ✅ | +| 5 | Matrix report, failure correlation, JUnit / Markdown | ✅ | +| 6 | Monitoring TUI | ✅ | + +Details and exit criteria: [docs/ROADMAP.md](docs/ROADMAP.md). End-to-end +demo on real VMs: [`scripts/demo.sh`](scripts/demo.sh) and [docs/how-to-use/demo.md](docs/how-to-use/demo.md). ## Crates -| Crate | Rôle | +| Crate | Role | | --- | --- | -| `envprobe-core` | Bibliothèque des sondes, modèle du rapport, diff | -| `envprobe` | CLI de la sonde | -| `distroproof` | Orchestrateur de VM, matrice, rapport et TUI | +| `envprobe-core` | Probe library, report model, diff | +| `envprobe` | Probe CLI | +| `distroproof` | VM orchestrator, matrix, report and TUI | ## envprobe -`envprobe` liste tout ce qui peut changer le comportement d'un programme -userspace ou kernel-facing : +`envprobe` lists everything that can change how a userspace or kernel-facing +program behaves: -- **Identité** : distro, famille, arch, virtualisation, conteneur. -- **Noyau** : release, cmdline, config (`CONFIG_BPF*`, `KPROBES`, `DEBUG_INFO_BTF`…), modules. -- **Sécurité et privilèges** : sysctls, LSM, SELinux, AppArmor, lockdown, Secure Boot, - capabilities, seccomp, user namespace. -- **BPF et perf** : types de programmes, types de maps et helpers, sondés par de - vrais appels `bpf()` ; `perf_event_open` logiciel et matériel, PMU ; tracefs, kallsyms, BTF. -- **Userspace** : libc et sa version, bibliothèques courantes, FHS (merged-usr, - multiarch), `/bin/sh`, coreutils, init, gestionnaire de paquets, cgroups, XDG. +- **Identity**: distro, family, arch, virtualization, container. +- **Kernel**: release, cmdline, config (`CONFIG_BPF*`, `KPROBES`, `DEBUG_INFO_BTF`…), modules. +- **Security and privileges**: sysctls, LSM, SELinux, AppArmor, lockdown, Secure Boot, + capabilities, seccomp, user namespaces. +- **BPF and perf**: program types, map types and helpers, probed with real + `bpf()` calls; software and hardware `perf_event_open`, PMU; tracefs, kallsyms, BTF. +- **Userspace**: libc and its version, common libraries, FHS (merged-usr, + multiarch), `/bin/sh`, coreutils, init, package manager, cgroups, XDG. -Les fonctionnalités sont sondées directement, jamais déduites d'un numéro de -version, car les noyaux RHEL rétroportent beaucoup. La sonde est en lecture -seule et ne plante jamais : une information absente ou refusée apparaît avec -sa raison. +Features are probed directly, never inferred from a version number, because +RHEL kernels backport a lot. The probe is read-only and never crashes: missing +or denied information shows up with its reason. ```sh -envprobe # résumé lisible -envprobe --json # rapport complet -envprobe --json --section kernel,bpf # sections choisies -envprobe diff a.json b.json # différences, code de sortie 1 s'il y en a -envprobe schema # schéma JSON du rapport +envprobe # human-readable summary +envprobe --json # full report +envprobe --json --section kernel,bpf # selected sections +envprobe diff a.json b.json # differences, exit code 1 if any +envprobe schema # JSON schema of the report ``` -Extrait d'un diff entre un hôte Arch et un rootfs Alpine (même noyau) : +Excerpt of a diff between an Arch host and an Alpine rootfs (same kernel): ```text [libc] @@ -84,84 +83,83 @@ Extrait d'un diff entre un hôte Arch et un rootfs Alpine (même noyau) : + managers apk ``` -### Format du rapport +### Report format -Chaque entrée porte un statut : +Each entry carries a status: -- `ok` : la valeur. -- `unavailable` : information absente, avec la raison (fichier absent, syscall inexistant…). -- `denied` : accès refusé (EPERM, EACCES, seccomp…). +- `ok`: the value. +- `unavailable`: information missing, with the reason (file absent, syscall not implemented…). +- `denied`: access refused (EPERM, EACCES, seccomp…). -Le champ `privileged` signale les résultats qui changent selon les droits du -processus. Le format est versionné (`schema_version`) et son schéma est publié -dans [`schema/envprobe-report.schema.json`](schema/envprobe-report.schema.json). +The `privileged` field flags results that depend on the process's privileges. +The format is versioned (`schema_version`) and its schema is published in +[`schema/envprobe-report.schema.json`](schema/envprobe-report.schema.json). -### Sonder une autre distro sans VM +### Probing another distro without a VM -Pour comparer l'userspace d'une autre distro sans VM ni Docker (le noyau reste -celui de l'hôte) : +To compare another distro's userspace without a VM or Docker (the kernel stays +the host's): ```sh -scripts/probe-rootfs.sh --json +scripts/probe-rootfs.sh --json ``` -Le script lance la sonde dans des namespaces user, mount et pid, sans root. +The script runs the probe in user, mount and pid namespaces, without root. ## distroproof -`distroproof` démarre de vraies VM QEMU/KVM sur les images cloud officielles -de chaque distro : c'est **le noyau de la distro** qui tourne (4.18 sur -Rocky 8, 5.14 sur Rocky 9…), pas celui de l'hôte. +`distroproof` boots real QEMU/KVM VMs from each distro's official cloud +images: it is **the distro's kernel** that runs (4.18 on Rocky 8, 5.14 on +Rocky 9…), not the host's. ```sh distroproof run --target rocky-8 -- envprobe --json > rocky8.json -distroproof up # matrice de distroproof.toml -distroproof check # valide le fichier sans lancer de VM -distroproof images # catalogue et état du cache +distroproof up # matrix from distroproof.toml +distroproof check # validate the file without starting VMs +distroproof images # catalog and cache status distroproof report matrix.json --format markdown ``` -Prérequis sur l'hôte : `qemu-system-x86_64`, `qemu-img`, `curl`, KVM -(`/dev/kvm`, repli automatique sur TCG sinon) et OVMF (`edk2-ovmf`, `ovmf`) -pour les images qui ne démarrent qu'en UEFI. Les binaires se choisissent avec `DISTROPROOF_QEMU`, -`DISTROPROOF_QEMU_IMG`, `DISTROPROOF_OVMF_CODE` et `DISTROPROOF_OVMF_VARS`. +Host requirements: `qemu-system-x86_64`, `qemu-img`, `curl`, KVM (`/dev/kvm`, +automatic fallback to TCG otherwise) and OVMF (`edk2-ovmf`, `ovmf`) for images +that only boot with UEFI. Binaries can be chosen with `DISTROPROOF_QEMU`, +`DISTROPROOF_QEMU_IMG`, `DISTROPROOF_OVMF_CODE` and `DISTROPROOF_OVMF_VARS`. -### Cibles +### Targets -| Image | Noyau observé | Remarque | +| Image | Observed kernel | Notes | | --- | --- | --- | -| `debian-10`, `debian-11`, `debian-12`, `debian-13` | 4.19, 5.10, 6.1, 6.12 | 4.19 sans BTF ; 13 démarre en UEFI | -| `ubuntu-18.04`, `ubuntu-20.04`, `ubuntu-22.04`, `ubuntu-24.04` | 4.15, 5.4, 5.15, 6.8 | 4.15 sans BTF | -| `rocky-8`, `rocky-9` | 4.18, 5.14 | noyaux RHEL avec backports ; 9 démarre en UEFI | -| `fedora-latest` | 6.19 (Fedora 44) | dernière release, résolue en ligne | +| `debian-10`, `debian-11`, `debian-12`, `debian-13` | 4.19, 5.10, 6.1, 6.12 | 4.19 without BTF; 13 boots with UEFI | +| `ubuntu-18.04`, `ubuntu-20.04`, `ubuntu-22.04`, `ubuntu-24.04` | 4.15, 5.4, 5.15, 6.8 | 4.15 without BTF | +| `rocky-8`, `rocky-9` | 4.18, 5.14 | RHEL kernels with backports; 9 boots with UEFI | +| `fedora-latest` | 6.19 (Fedora 44) | latest release, resolved online | | `arch` | rolling | | | `alpine` | 6.18 `virt` | musl, busybox | -Les images sont téléchargées une fois, vérifiées contre le checksum publié -par la distro et gardées dans `$XDG_CACHE_HOME/distroproof/images`. Chaque -exécution travaille sur un overlay qcow2 jetable, supprimé à la fin, même -après un timeout ou un Ctrl+C ; QEMU meurt avec distroproof. - -### Fonctionnement - -- QEMU démarre en pause (`-S`), distroproof se connecte à son socket QMP et - aux deux ports série (console sur ttyS0, canal de l'agent sur ttyS1), puis - lance la VM. -- cloud-init (seed NoCloud écrit en Rust) extrait un disque payload (archive - tar brute : agent, `envprobe` statique, répertoire de travail) et lance - l'agent. Celui-ci applique les sysctls, crée l'utilisateur de test, lance - `envprobe` puis la commande, et relaie stdout et stderr ligne à ligne. -- Les sorties complètes, les codes de retour et le rapport sont écrits sur un - disque de sortie (tar brut), puis la VM s'éteint. Pas de 9p ni de virtiofs, - absents des noyaux RHEL. -- Au premier boot, l'agent extrait aussi noyau et initramfs : les exécutions - suivantes bootent en direct (`-kernel`), en 6 à 13 s, et peuvent modifier - la ligne de commande du noyau. -- L'orchestrateur n'écrit jamais dans le terminal : il émet des événements - typés (`Booting`, `Output`, `Metrics`, `Finished`…) que la sortie texte, - la sortie JSON (`--progress json`) et la TUI consomment. - -### Matrice : `distroproof.toml` +Images are downloaded once, verified against the checksum published by the +distro and kept in `$XDG_CACHE_HOME/distroproof/images`. Each run works on a +throwaway qcow2 overlay that is deleted at the end, even after a timeout or a +Ctrl+C; QEMU dies with distroproof. + +### How it works + +- QEMU starts paused (`-S`); distroproof connects to its QMP socket and to the + two serial ports (console on ttyS0, agent channel on ttyS1), then starts the + VM. +- cloud-init (NoCloud seed written in Rust) extracts a payload disk (raw tar + archive: agent, static `envprobe`, working directory) and starts the agent. + The agent applies the sysctls, creates the test user, runs `envprobe` then + the command, and relays stdout and stderr line by line. +- Full outputs, exit codes and the report are written to an output disk (raw + tar), then the VM shuts down. No 9p or virtiofs, which RHEL kernels lack. +- On first boot, the agent also extracts the kernel and initramfs: later runs + boot directly (`-kernel`) in 6 to 13 s and can change the kernel command + line. +- The orchestrator never writes to the terminal: it emits typed events + (`Booting`, `Output`, `Metrics`, `Finished`…) consumed by the text output, + the JSON output (`--progress json`) and the TUI. + +### Matrix: `distroproof.toml` ```toml [defaults] @@ -187,26 +185,28 @@ kernel_cmdline = "lockdown=confidentiality" name = "debian-nopmu" image = "debian-12" pmu = false # -cpu host,pmu=off -caps = ["perfmon", "bpf"] # capabilities de l'utilisateur de test +caps = ["perfmon", "bpf"] # capabilities of the test user [run] -setup = "./scripts/install-deps.sh" # en root, avant la sonde -command = "./target/release/montool --selftest" -as_user = "test" # ou "root" -# exclude = [".git", "target"] # copié dans /distroproof/work -# include = ["target/release/montool"] +setup = "./scripts/install-deps.sh" # as root, before the probe +command = "./target/release/mytool --selftest" +as_user = "test" # or "root" +# exclude = [".git", "target"] # copied to /distroproof/work +# include = ["target/release/mytool"] ``` -Une erreur (clé inconnue, image inexistante, durée invalide…) est signalée -avec sa ligne et sa colonne, sans lancer de VM. `distroproof up` lance les -cibles en parallèle (`--jobs`), joint à chaque résultat le rapport `envprobe` -pris juste avant la commande, puis : +Errors (unknown key, nonexistent image, invalid duration…) are reported with +their line and column, without starting any VM. `distroproof up` runs the +targets in parallel (`--jobs`), attaches to each result the `envprobe` report +taken right before the command, then: -- affiche le tableau cibles × résultat, avec durée, temps de boot, noyau et - extrait de stderr ; -- compare chaque cible en échec à la cible réussie la plus proche et désigne - la différence suspecte (BTF absent, paranoid à 3, musl…) ; -- écrit si demandé `--json`, `--junit` (CI) et `--markdown` (commentaire de PR). +- prints the targets × result table, with duration, boot time, kernel and a + stderr excerpt; +- compares each failed target with the closest successful one and points out + the suspicious difference (missing BTF, paranoid set to 3, musl…); +- writes `--json`, `--junit` (CI) and `--markdown` (PR comment) on request. + +Sample output (the CLI prints its messages in French): ```text [ubuntu-18.04] échec (code 1) @@ -215,36 +215,36 @@ pris juste avant la commande, puis : aussi : kernel.CONFIG_DEBUG_INFO_BTF : y → absente de la config ``` -Exemples : [`distroproof.toml`](distroproof.toml) (dogfooding d'envprobe sur -10 cibles, lancé en CI par [`matrix.yml`](.github/workflows/matrix.yml)) et -[`examples/require-btf`](examples/require-btf) (échec volontaire sans BTF). +Examples: [`distroproof.toml`](distroproof.toml) (envprobe dogfooding on 10 +targets, run in CI by [`matrix.yml`](.github/workflows/matrix.yml)) and +[`examples/require-btf`](examples/require-btf) (deliberate failure without BTF). ### TUI -Dans un terminal, `distroproof up` ouvre une TUI (`--no-tui` pour la sortie -texte, automatique sans TTY) : une carte par cible avec état, temps de boot, -jauges CPU et mémoire et graphe Braille ; logs série en direct avec -recherche ; rapport `envprobe` replié par section et diff contre une autre -cible ; console série interactive. Avec `--hold`, chaque VM reste allumée -après la commande avec un shell root sur la console. +In a terminal, `distroproof up` opens a TUI (`--no-tui` for text output, +automatic without a TTY): one card per target with state, boot time, CPU and +memory gauges and a Braille graph; live serial logs with search; `envprobe` +report folded by section and diff against another target; interactive serial +console. With `--hold`, each VM stays up after the command with a root shell +on the console. -| Touche | Action | +| Key | Action | | --- | --- | -| `hjkl`, flèches | sélection, défilement | -| `espace`, `r`, `x` | pause / reprise (QMP), relance, kill | -| `c`, `i`, `Ctrl+]` | ouvrir la console, saisir, sortir | -| `/`, `n`, `N` | recherche dans les logs | -| `p` | épingler le panneau sur la cible sélectionnée | -| `d`, `D` | diff du rapport contre une autre cible | -| `D L R C` | vue dashboard, logs, rapport, console du panneau | -| `Ctrl+W` `s` `v` `q` `hjkl` `<>+-` | tiling : découper, fermer, focus, taille | -| `1`…`9` | workspaces (un par fichier avec plusieurs `-f`) | -| `f` | filtre : toutes, en cours, échecs, terminées | -| `:` ou `Ctrl+P` | palette de commandes | -| `?`, `q` | aide, quitter | - -Couleurs dans `$XDG_CONFIG_HOME/distroproof/theme.toml` (true color, repli -256 couleurs) : +| `hjkl`, arrows | selection, scrolling | +| `space`, `r`, `x` | pause / resume (QMP), restart, kill | +| `c`, `i`, `Ctrl+]` | open the console, type, leave | +| `/`, `n`, `N` | search the logs | +| `p` | pin the pane to the selected target | +| `d`, `D` | diff the report against another target | +| `D L R C` | pane view: dashboard, logs, report, console | +| `Ctrl+W` `s` `v` `q` `hjkl` `<>+-` | tiling: split, close, focus, resize | +| `1`…`9` | workspaces (one per file with several `-f`) | +| `f` | filter: all, running, failed, finished | +| `:` or `Ctrl+P` | command palette | +| `?`, `q` | help, quit | + +Colors live in `$XDG_CONFIG_HOME/distroproof/theme.toml` (true color, 256-color +fallback): ```toml [colors] @@ -260,13 +260,15 @@ rustup target add x86_64-unknown-linux-musl cargo build --release --target x86_64-unknown-linux-musl ``` -Les binaires sont statiques : `envprobe` tourne tel quel dans n'importe quelle -VM ou conteneur x86_64, et `distroproof` trouve l'`envprobe` posé à côté de lui. L'édition de liens passe par `rust-lld` (voir -`.cargo/config.toml`), ce qui permet aussi de compiler depuis arm64 vers x86_64. +The binaries are static: `envprobe` runs as is in any x86_64 VM or container, +and `distroproof` finds the `envprobe` sitting next to it. Linking goes +through `rust-lld` (see `.cargo/config.toml`), which also allows building for +x86_64 from arm64. -Tests : `cargo test --workspace`. Pour régénérer le schéma après un changement -du modèle : `UPDATE_SCHEMA=1 cargo test -p envprobe-core --test reports`. +Tests: `cargo test --workspace`. To regenerate the schema after changing the +model: `UPDATE_SCHEMA=1 cargo test -p envprobe-core --test reports`. -## Licence +## License -Au choix, sous [MIT](LICENSE-MIT) ou [Apache 2.0](LICENSE-APACHE). +Licensed under either [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE), at +your option. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3a0c9a3..721892b 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,124 +1,124 @@ -# Roadmap — distroproof (POC Rust) +# Roadmap — distroproof (Rust POC) Oct 9, 2026 · @Mohamed Bekheira -## Vision et conventions +## Vision and conventions -Objectif du POC : un outil Rust qui décrit précisément un environnement Linux (userspace + kernel), puis lance des VM « distro cible » pour y exécuter un programme et comparer les résultats. Nom : `distroproof`. On commence par la sonde (`envprobe`), seule et utile dès la phase 1. +POC goal: a Rust tool that precisely describes a Linux environment (userspace + kernel), then starts "target distro" VMs to run a program in them and compare the results. Name: `distroproof`. We start with the probe (`envprobe`), standalone and useful from phase 1. -Chaque phase est autonome : un agent la réalise sans lire les suivantes. Une phase n'est terminée que lorsque tous ses critères de sortie sont cochés. +Each phase is self-contained: an agent completes it without reading the following ones. A phase is only done when all its exit criteria are checked. -Règles pour l'agent, valables à toutes les phases : +Rules for the agent, valid in every phase: -- Rust stable, édition 2021, workspace Cargo. `cargo fmt`, `cargo clippy -- -D warnings` et `cargo test` passent avant chaque commit. -- La sonde ne plante jamais : chaque information absente ou inaccessible devient une valeur `Unknown` / `Unavailable` avec la raison (permission, fichier absent, syscall refusé). -- Sonder les fonctionnalités, ne jamais déduire depuis un numéro de version (backports RHEL). -- La sonde n'a aucun effet de bord : lecture seule, pas d'écriture de sysctl, pas de chargement durable de programme BPF. -- Binaire de la sonde compilé en statique (`x86_64-unknown-linux-musl`) pour tourner tel quel dans n'importe quelle VM cible. -- Dépendances minimales et justifiées dans le message de commit. Pas de `unsafe` hors d'un module `sys` dédié et commenté. -- Ne pas présumer root : chaque sonde indique si le résultat dépend des privilèges. +- Stable Rust, 2021 edition, Cargo workspace. `cargo fmt`, `cargo clippy -- -D warnings` and `cargo test` pass before every commit. +- The probe never crashes: any missing or inaccessible information becomes an `Unknown` / `Unavailable` value with the reason (permission, missing file, refused syscall). +- Probe features, never infer from a version number (RHEL backports). +- The probe has no side effects: read-only, no sysctl writes, no persistent BPF program loading. +- The probe binary is built statically (`x86_64-unknown-linux-musl`) so it runs as is in any target VM. +- Minimal dependencies, justified in the commit message. No `unsafe` outside a dedicated, commented `sys` module. +- Don't assume root: each probe states whether its result depends on privileges. -## Phase 0 — Setup du projet +## Phase 0 — Project setup -But : un squelette qui compile en statique et tourne en CI, avant toute fonctionnalité. +Goal: a skeleton that builds statically and runs in CI, before any feature. -Tâches : +Tasks: -1. Créer un workspace Cargo avec trois crates : `envprobe-core` (lib, les sondes), `envprobe` (binaire CLI) et `distroproof` (binaire orchestrateur, vide pour l'instant). -2. Ajouter la cible musl et un profil release optimisé taille (`lto`, `strip`, `opt-level = "s"`). -3. CLI avec `clap` : `envprobe` sans argument affiche un résumé lisible ; `--json` est réservé pour la phase 2. -4. CI GitHub Actions : fmt, clippy, tests, build musl, et vérification que le binaire est bien statique (`file` ou `ldd` → « not a dynamic executable »). -5. `README.md` court et ce fichier de roadmap dans `docs/ROADMAP.md`. +1. Create a Cargo workspace with three crates: `envprobe-core` (lib, the probes), `envprobe` (CLI binary) and `distroproof` (orchestrator binary, empty for now). +2. Add the musl target and a size-optimized release profile (`lto`, `strip`, `opt-level = "s"`). +3. CLI with `clap`: `envprobe` with no argument prints a human-readable summary; `--json` is reserved for phase 2. +4. GitHub Actions CI: fmt, clippy, tests, musl build, and a check that the binary is really static (`file` or `ldd` → "not a dynamic executable"). +5. A short `README.md` and this roadmap file in `docs/ROADMAP.md`. -Critères de sortie : +Exit criteria: -- [x] `cargo build --release --target x86_64-unknown-linux-musl` produit un binaire statique. -- [x] La CI est verte sur une PR vide. -- [x] `envprobe --version` fonctionne. +- [x] `cargo build --release --target x86_64-unknown-linux-musl` produces a static binary. +- [x] CI is green on an empty PR. +- [x] `envprobe --version` works. -## Phase 1 — Sonde d'environnement (envprobe) +## Phase 1 — Environment probe (envprobe) -But : `envprobe` affiche tout ce qui peut changer le comportement d'un programme userspace ou kernel-facing (perf, BPF) sur la machine courante. Un module Rust par catégorie, chacun exposant une fonction `probe() -> Section`. +Goal: `envprobe` shows everything that can change the behavior of a userspace or kernel-facing program (perf, BPF) on the current machine. One Rust module per category, each exposing a `probe() -> Section` function. -Ordre conseillé : identité et kernel d'abord, puis userspace, puis BPF/perf (le plus délicat). +Suggested order: identity and kernel first, then userspace, then BPF/perf (the trickiest). -| Catégorie | Informations | Sources | +| Category | Information | Sources | | --- | --- | --- | -| Identité | distro, version, famille (`ID_LIKE`), arch, virtualisé ou non, conteneur ou non | `/etc/os-release`, `uname()`, `/sys/class/dmi/id/*`, `/proc/1/cgroup`, `/.dockerenv`, `/run/systemd/container` | -| Kernel | version, release, cmdline, config (`CONFIG_BPF*`, `KPROBES`, `UPROBES`, `FTRACE`, `DEBUG_INFO_BTF`, `PERF_EVENTS`) | `uname()`, `/proc/cmdline`, `/proc/config.gz` puis `/boot/config-$(uname -r)` | -| Modules | modules chargés, chargement possible | `/proc/modules`, `/sys/module` | +| Identity | distro, version, family (`ID_LIKE`), arch, virtualized or not, container or not | `/etc/os-release`, `uname()`, `/sys/class/dmi/id/*`, `/proc/1/cgroup`, `/.dockerenv`, `/run/systemd/container` | +| Kernel | version, release, cmdline, config (`CONFIG_BPF*`, `KPROBES`, `UPROBES`, `FTRACE`, `DEBUG_INFO_BTF`, `PERF_EVENTS`) | `uname()`, `/proc/cmdline`, `/proc/config.gz` then `/boot/config-$(uname -r)` | +| Modules | loaded modules, whether loading is possible | `/proc/modules`, `/sys/module` | | Sysctls | `perf_event_paranoid`, `perf_event_max_sample_rate`, `unprivileged_bpf_disabled`, `bpf_jit_enable`, `bpf_jit_harden`, `kptr_restrict`, `dmesg_restrict`, `yama.ptrace_scope` | `/proc/sys/kernel/*`, `/proc/sys/net/core/*` | -| Sécurité | LSM actifs, mode SELinux, profils AppArmor, lockdown, Secure Boot | `/sys/kernel/security/lsm`, `/sys/fs/selinux/enforce`, `/sys/kernel/security/apparmor`, `/sys/kernel/security/lockdown`, efivars | -| Privilèges | uid/euid, capabilities effectives (`CAP_BPF`, `CAP_PERFMON`, `CAP_SYS_ADMIN`), seccomp, `NoNewPrivs` | `/proc/self/status` (`CapEff`, `Seccomp`) | -| BTF | présence et taille de vmlinux BTF, BTF des modules | `/sys/kernel/btf/vmlinux`, `/sys/kernel/btf/*` | -| BPF | types de programmes, types de maps, helpers clés, JIT | appels `bpf(BPF_PROG_LOAD)` minimaux, équivalent de `bpftool feature probe` | -| perf | `perf_event_open` en mode logiciel et matériel, PMU disponibles | appel test sur `PERF_COUNT_SW_CPU_CLOCK` et `PERF_COUNT_HW_CPU_CYCLES`, `/sys/bus/event_source/devices` | -| Tracing | tracefs monté et où, debugfs, kallsyms lisible | `/proc/mounts`, `/sys/kernel/tracing`, `/sys/kernel/debug/tracing`, `/proc/kallsyms` | -| libc | glibc ou musl, version | `gnu_get_libc_version()` si dispo, sinon lecture de `/lib/ld-musl-*` ou de l'interpréteur ELF de `/bin/sh` | -| Libs | présence et version des libs courantes (libbpf, libelf, zlib, openssl, libz) | `ldconfig -p` si dispo, sinon scan de `/lib`, `/lib64`, `/usr/lib*` | -| FHS | merged-usr, `/lib64` existe, multiarch Debian, chemins des libs | liens symboliques de `/bin`, `/lib`, `/sbin` ; `/usr/lib/x86_64-linux-gnu` | -| Shell et outils | cible de `/bin/sh`, coreutils GNU ou busybox | `readlink /bin/sh`, `/bin/ls` vers busybox | -| Init et services | PID 1 (systemd, OpenRC, runit…) | `/proc/1/comm`, `/proc/1/exe` | -| Gestionnaire de paquets | apt, dnf, pacman, zypper, apk, xbps, nix | présence des binaires dans le `PATH` | -| cgroups | v1, v2 ou hybride | `/sys/fs/cgroup`, `/proc/mounts` | -| XDG | variables définies et valeurs effectives | variables d'environnement | +| Security | active LSMs, SELinux mode, AppArmor profiles, lockdown, Secure Boot | `/sys/kernel/security/lsm`, `/sys/fs/selinux/enforce`, `/sys/kernel/security/apparmor`, `/sys/kernel/security/lockdown`, efivars | +| Privileges | uid/euid, effective capabilities (`CAP_BPF`, `CAP_PERFMON`, `CAP_SYS_ADMIN`), seccomp, `NoNewPrivs` | `/proc/self/status` (`CapEff`, `Seccomp`) | +| BTF | presence and size of vmlinux BTF, module BTF | `/sys/kernel/btf/vmlinux`, `/sys/kernel/btf/*` | +| BPF | program types, map types, key helpers, JIT | minimal `bpf(BPF_PROG_LOAD)` calls, equivalent to `bpftool feature probe` | +| perf | software and hardware `perf_event_open`, available PMUs | test call on `PERF_COUNT_SW_CPU_CLOCK` and `PERF_COUNT_HW_CPU_CYCLES`, `/sys/bus/event_source/devices` | +| Tracing | tracefs mounted and where, debugfs, kallsyms readable | `/proc/mounts`, `/sys/kernel/tracing`, `/sys/kernel/debug/tracing`, `/proc/kallsyms` | +| libc | glibc or musl, version | `gnu_get_libc_version()` if available, otherwise read `/lib/ld-musl-*` or the ELF interpreter of `/bin/sh` | +| Libs | presence and version of common libs (libbpf, libelf, zlib, openssl, libz) | `ldconfig -p` if available, otherwise scan `/lib`, `/lib64`, `/usr/lib*` | +| FHS | merged-usr, `/lib64` exists, Debian multiarch, library paths | symlinks of `/bin`, `/lib`, `/sbin`; `/usr/lib/x86_64-linux-gnu` | +| Shell and tools | target of `/bin/sh`, GNU coreutils or busybox | `readlink /bin/sh`, `/bin/ls` pointing to busybox | +| Init and services | PID 1 (systemd, OpenRC, runit…) | `/proc/1/comm`, `/proc/1/exe` | +| Package manager | apt, dnf, pacman, zypper, apk, xbps, nix | binaries present in `PATH` | +| cgroups | v1, v2 or hybrid | `/sys/fs/cgroup`, `/proc/mounts` | +| XDG | defined variables and effective values | environment variables | -Les sondes BPF et perf doivent utiliser des syscalls directs (via le crate `libc` ou `nix`), sans dépendre de libbpf, pour garder un binaire statique simple. Un échec `EPERM` ou `EACCES` est un résultat, pas une erreur. +The BPF and perf probes must use direct syscalls (through the `libc` or `nix` crate), without depending on libbpf, to keep a simple static binary. An `EPERM` or `EACCES` failure is a result, not an error. -Critères de sortie : +Exit criteria: -- [x] Toutes les catégories du tableau sont implémentées. -- [x] Exécuté en utilisateur normal puis en root, la sortie diffère là où c'est attendu (privilèges, BPF, perf). -- [x] Exécuté dans un conteneur Docker par défaut, la sonde détecte le conteneur et le seccomp sans planter. -- [x] Tests unitaires sur les parseurs (`os-release`, `config.gz`, `/proc/self/status`) avec des fixtures de plusieurs distros. +- [x] Every category in the table is implemented. +- [x] Run as a normal user then as root, the output differs where expected (privileges, BPF, perf). +- [x] Run in a default Docker container, the probe detects the container and seccomp without crashing. +- [x] Unit tests on the parsers (`os-release`, `config.gz`, `/proc/self/status`) with fixtures from several distros. -## Phase 2 — Sortie structurée et diff +## Phase 2 — Structured output and diff -But : rendre le rapport exploitable par une machine et comparer deux environnements, base de toute la suite. +Goal: make the report machine-readable and compare two environments, the foundation for everything that follows. -Tâches : +Tasks: -1. Modèle de données versionné (`schema_version`) avec `serde`. Chaque valeur porte son statut : `Ok(valeur)`, `Unavailable(raison)`, `Denied(errno)`. -2. `envprobe --json` écrit le rapport complet sur stdout ; `--section kernel,bpf` filtre. -3. Schéma JSON publié dans le dépôt, généré avec `schemars`. -4. Sous-commande `envprobe diff a.json b.json` : affiche les différences par section, en couleur, avec un code de sortie non nul s'il y en a. -5. Collecter des rapports réels comme fixtures : ta machine, un conteneur Debian, un conteneur Alpine, plus tout ce que tu as sous la main. +1. Versioned data model (`schema_version`) with `serde`. Each value carries its status: `Ok(value)`, `Unavailable(reason)`, `Denied(errno)`. +2. `envprobe --json` writes the full report to stdout; `--section kernel,bpf` filters it. +3. JSON schema published in the repository, generated with `schemars`. +4. `envprobe diff a.json b.json` subcommand: shows the differences per section, in color, with a non-zero exit code if there are any. +5. Collect real reports as fixtures: your machine, a Debian container, an Alpine container, plus anything else at hand. -Critères de sortie : +Exit criteria: -- [x] Le JSON valide contre le schéma publié. -- [x] `diff` entre ta machine et un conteneur Alpine fait ressortir libc, `/bin/sh` et gestionnaire de paquets, mais pas le kernel (même noyau). -- [x] Tests de non-régression sur au moins trois fixtures. +- [x] The JSON validates against the published schema. +- [x] `diff` between your machine and an Alpine container shows libc, `/bin/sh` and the package manager, but not the kernel (same kernel). +- [x] Regression tests on at least three fixtures. -## Phase 3 — VM avec noyau et rootfs de distro +## Phase 3 — VM with the distro's kernel and rootfs -But : `distroproof run --target debian-12 -- envprobe --json` démarre une VM avec le **vrai noyau de la distro** et son rootfs, y lance la commande et récupère la sortie. C'est la phase la plus risquée : commencer par une seule cible qui marche de bout en bout. +Goal: `distroproof run --target debian-12 -- envprobe --json` starts a VM with the **real distro kernel** and its rootfs, runs the command in it and collects the output. This is the riskiest phase: start with a single target that works end to end. -Tâches : +Tasks: -1. Choix techniques : QEMU + KVM piloté en sous-processus (pas de lib de virtualisation au début). Images cloud officielles des distros (qcow2) comme source, car elles contiennent noyau, initramfs et config d'origine. -2. Cache local dans `$XDG_CACHE_HOME/distroproof/images` : téléchargement, vérification du checksum publié, overlay qcow2 jetable par exécution. -3. Injection : partage du répertoire de travail via virtiofs ou 9p, ou à défaut une seconde image disque. Configuration initiale via cloud-init (seed ISO) pour créer l'utilisateur et lancer la commande. -4. Récupération de stdout, stderr et du code de retour via la console série (`-serial`) ou virtio-vsock, puis extinction propre de la VM. -5. Timeouts et nettoyage garantis, même si la VM ne boote pas. -6. Cibles dans l'ordre : `debian-12`, `ubuntu-22.04`, `rocky-9`, `rocky-8`, `fedora-latest`, `arch`, `alpine`. +1. Technical choices: QEMU + KVM driven as a subprocess (no virtualization library at first). Official distro cloud images (qcow2) as the source, since they contain the original kernel, initramfs and config. +2. Local cache in `$XDG_CACHE_HOME/distroproof/images`: download, verification of the published checksum, throwaway qcow2 overlay per run. +3. Injection: share the working directory through virtiofs or 9p, or failing that a second disk image. Initial configuration through cloud-init (seed ISO) to create the user and start the command. +4. Collect stdout, stderr and the exit code through the serial console (`-serial`) or virtio-vsock, then shut the VM down cleanly. +5. Guaranteed timeouts and cleanup, even if the VM fails to boot. +6. Targets in order: `debian-12`, `ubuntu-22.04`, `rocky-9`, `rocky-8`, `fedora-latest`, `arch`, `alpine`. -Exigence d'architecture (prépare la phase 6) : l'orchestrateur n'écrit jamais directement dans le terminal. Il émet des événements typés, par exemple `VmEvent { Downloading, Booting, Running, Output(line), Metrics(..), Exited(code), TimedOut }`, dans un channel tokio. La sortie texte, la sortie JSON et plus tard la TUI ne sont que des consommateurs de ce flux. Le PID de chaque processus QEMU et un socket QMP par VM (`-qmp unix:...`) sont exposés dès cette phase. +Architecture requirement (prepares phase 6): the orchestrator never writes directly to the terminal. It emits typed events, for example `VmEvent { Downloading, Booting, Running, Output(line), Metrics(..), Exited(code), TimedOut }`, on a tokio channel. The text output, the JSON output and later the TUI are only consumers of this stream. The PID of each QEMU process and one QMP socket per VM (`-qmp unix:...`) are exposed from this phase on. -Piste d'accélération à évaluer ensuite : extraire noyau et initramfs de l'image et booter en direct (`-kernel`, `-initrd`), à la manière de virtme-ng, pour passer sous les 5 secondes. +Speed-up to evaluate afterwards: extract the kernel and initramfs from the image and boot them directly (`-kernel`, `-initrd`), like virtme-ng does, to get under 5 seconds. -Critères de sortie : +Exit criteria: -- [x] Les 7 cibles bootent et renvoient un rapport `envprobe` JSON. -- [x] Le rapport de chaque VM montre bien le noyau de la distro (4.18 sur Rocky 8, 5.14 sur Rocky 9…) et non celui de l'hôte. -- [x] Une exécution répétée réutilise le cache et laisse le disque propre. -- [x] Temps de boot mesuré et affiché par cible. +- [x] The 7 targets boot and return a JSON `envprobe` report. +- [x] Each VM's report shows the distro's kernel (4.18 on Rocky 8, 5.14 on Rocky 9…) and not the host's. +- [x] A repeated run reuses the cache and leaves the disk clean. +- [x] Boot time is measured and shown per target. -## Phase 4 — Fichier déclaratif et matrice +## Phase 4 — Declarative file and matrix -But : décrire sa matrice de test dans un fichier versionné du projet testé, à la manière d'une infra as code. `distroproof up` lit le fichier et lance toutes les cibles en parallèle. +Goal: describe the test matrix in a versioned file of the project under test, infrastructure-as-code style. `distroproof up` reads the file and runs every target in parallel. -Exemple de format visé (`distroproof.toml`) : +Target format example (`distroproof.toml`): ```toml [defaults] @@ -142,78 +142,78 @@ kernel_cmdline = "lockdown=confidentiality" [run] setup = "./scripts/install-deps.sh" -command = "./target/release/montool --selftest" -as_user = "test" # ou "root" +command = "./target/release/mytool --selftest" +as_user = "test" # or "root" ``` -Tâches : +Tasks: -1. Parser et valider le fichier (`serde` + `toml`), messages d'erreur avec ligne et colonne. -2. Variantes par cible : sysctls, cmdline noyau, utilisateur root ou non, capabilities accordées, PMU exposée ou non (`-cpu host` vs modèle générique). -3. Exécution parallèle bornée (`--jobs`), avec affichage de la progression par cible. -4. `envprobe` est lancé automatiquement avant la commande de l'utilisateur et son rapport joint au résultat. +1. Parse and validate the file (`serde` + `toml`), with error messages giving line and column. +2. Per-target variants: sysctls, kernel cmdline, root or non-root user, granted capabilities, PMU exposed or not (`-cpu host` vs a generic model). +3. Bounded parallel execution (`--jobs`), with per-target progress display. +4. `envprobe` runs automatically before the user's command and its report is attached to the result. -Critères de sortie : +Exit criteria: -- [x] Une matrice de 5 cibles avec variantes tourne en une commande. -- [x] Le rapport `envprobe` de chaque cible reflète bien les variantes demandées (paranoid, lockdown). -- [x] Un fichier invalide donne une erreur claire sans lancer de VM. +- [x] A 5-target matrix with variants runs in one command. +- [x] Each target's `envprobe` report reflects the requested variants (paranoid, lockdown). +- [x] An invalid file gives a clear error without starting any VM. -## Phase 5 — Tests et rapport +## Phase 5 — Tests and report -But : savoir en un coup d'œil où le programme marche, où il casse, et pourquoi, en reliant chaque échec à une différence d'environnement. +Goal: see at a glance where the program works, where it breaks, and why, by tying each failure to an environment difference. -Tâches : +Tasks: -1. Rapport de matrice : tableau cibles × résultat (succès, échec, timeout), durée, extrait de stderr. -2. Corrélation : pour chaque cible en échec, `envprobe diff` automatique contre la cible réussie la plus proche, pour pointer le suspect (BTF absent, paranoid à 3, musl…). -3. Formats de sortie : terminal, JSON, JUnit XML (pour la CI) et Markdown (pour un commentaire de PR). -4. Intégration CI : exemple de workflow GitHub Actions avec KVM activé et cache des images. -5. Dogfooding : faire tourner `envprobe` lui-même dans la matrice comme premier projet testé. +1. Matrix report: targets × result table (success, failure, timeout), duration, stderr excerpt. +2. Correlation: for each failed target, an automatic `envprobe diff` against the closest successful target, to point out the suspect (missing BTF, paranoid set to 3, musl…). +3. Output formats: terminal, JSON, JUnit XML (for CI) and Markdown (for a PR comment). +4. CI integration: example GitHub Actions workflow with KVM enabled and image caching. +5. Dogfooding: run `envprobe` itself in the matrix as the first project under test. -Critères de sortie : +Exit criteria: -- [x] Un échec volontaire (programme exigeant BTF) est détecté sur une cible sans BTF, et le rapport désigne BTF comme cause probable. -- [x] La matrice tourne en CI sur une PR et publie un résumé Markdown. +- [x] A deliberate failure (a program requiring BTF) is detected on a target without BTF, and the report names BTF as the probable cause. +- [x] The matrix runs in CI on a PR and publishes a Markdown summary. -## Phase 6 — TUI de monitoring +## Phase 6 — Monitoring TUI -But : une TUI moderne qui donne la vue d'ensemble de la matrice en temps réel, dans l'esprit de [btop](https://github.com/aristocratos/btop) pour les graphes et jauges, et de [TUIOS](https://github.com/Gaurav-Gosain/tuios) pour la gestion des panneaux (tiling, interface modale façon vim, palette de commandes). Elle se branche uniquement sur le bus d'événements de la phase 3, sans toucher à l'orchestrateur. +Goal: a modern TUI giving a real-time overview of the matrix, in the spirit of [btop](https://github.com/aristocratos/btop) for graphs and gauges, and of [TUIOS](https://github.com/Gaurav-Gosain/tuios) for pane management (tiling, vim-style modal interface, command palette). It plugs only into the phase 3 event bus, without touching the orchestrator. -Stack : `ratatui` + `crossterm`, `tokio` pour la boucle d'événements, `tui-term` + `vt100` pour afficher la console série d'une VM dans un panneau avec ses séquences ANSI. +Stack: `ratatui` + `crossterm`, `tokio` for the event loop, `tui-term` + `vt100` to show a VM's serial console in a pane with its ANSI sequences. -Vues : +Views: -- **Dashboard** : une carte par cible (style btop) avec état coloré, temps de boot, durée, jauges CPU et mémoire, sparkline de charge sur les dernières secondes. -- **Détail** : logs série en direct de la cible sélectionnée, avec défilement et recherche. -- **Rapport** : le rapport `envprobe` de la cible et son diff contre une autre cible, sections repliables. -- **Console** : console série interactive d'une VM dans un panneau, pour déboguer à la main. -- **Layout** : panneaux en tiling redimensionnables, plusieurs workspaces (une matrice par workspace, par exemple). +- **Dashboard**: one card per target (btop style) with colored state, boot time, duration, CPU and memory gauges, load sparkline over the last seconds. +- **Detail**: live serial logs of the selected target, with scrolling and search. +- **Report**: the target's `envprobe` report and its diff against another target, with collapsible sections. +- **Console**: interactive serial console of a VM in a pane, for manual debugging. +- **Layout**: resizable tiled panes, several workspaces (one matrix per workspace, for example). -Métriques : CPU et mémoire lus depuis `/proc//stat` et `/proc//status` (rafraîchissement 1 s), état de la VM et stats mémoire invité via QMP (`query-status`, balloon), débit disque et réseau via `query-blockstats`. +Metrics: CPU and memory read from `/proc//stat` and `/proc//status` (1 s refresh), VM state and guest memory stats through QMP (`query-status`, balloon), disk and network throughput through `query-blockstats`. -Interactions : +Interactions: -- Navigation modale façon vim (`hjkl`, `/` pour chercher), palette de commandes sur `:` ou `Ctrl+P`, support souris. -- Actions sur une cible : relancer, tuer, mettre en pause et reprendre (QMP `stop` / `cont`), ouvrir la console, lancer un diff. -- Filtrer les cibles par état (échecs seulement, en cours…). +- Vim-style modal navigation (`hjkl`, `/` to search), command palette on `:` or `Ctrl+P`, mouse support. +- Actions on a target: restart, kill, pause and resume (QMP `stop` / `cont`), open the console, start a diff. +- Filter targets by state (failures only, running…). -Rendu : thèmes configurables dans `$XDG_CONFIG_HOME/distroproof/theme.toml`, true color avec repli 256 couleurs, caractères Braille pour les graphes comme btop. Activée seulement si stdout est un TTY ou avec `--tui` ; sinon repli automatique sur la sortie texte, ce qui garde la CI intacte. +Rendering: themes configurable in `$XDG_CONFIG_HOME/distroproof/theme.toml`, true color with a 256-color fallback, Braille characters for graphs like btop. Enabled only if stdout is a TTY or with `--tui`; otherwise automatic fallback to text output, which keeps CI intact. -Critères de sortie : +Exit criteria: -- [x] Une matrice de 7 cibles s'affiche en temps réel, avec états, jauges et logs, sans scintillement ni blocage de l'interface pendant les boots. -- [x] Pause, reprise, relance et kill fonctionnent depuis la TUI. -- [x] La console série interactive d'une VM est utilisable dans un panneau. -- [x] Sans TTY, la même commande produit la sortie texte habituelle. -- [x] La TUI reste fluide sur un terminal de 80 × 24 et s'adapte au redimensionnement. +- [x] A 7-target matrix is shown in real time, with states, gauges and logs, without flicker or UI freezes during boots. +- [x] Pause, resume, restart and kill work from the TUI. +- [x] A VM's interactive serial console is usable in a pane. +- [x] Without a TTY, the same command produces the usual text output. +- [x] The TUI stays smooth on an 80 × 24 terminal and adapts to resizing. -## Pistes futures +## Future directions -Hors POC, à considérer une fois la phase 5 validée : +Outside the POC, to consider once phase 5 is validated: -- **Boot rapide** : boot direct noyau + initramfs et rootfs partagé en virtiofs, voire microVM (Firecracker, Cloud Hypervisor, libkrun) pour viser la seconde. -- **Noyaux à la carte** : en plus des noyaux de distro, des noyaux LTS upstream (5.10, 5.15, 6.1, 6.6, 6.12) ou un noyau compilé par l'utilisateur. -- **Arm64** : mêmes cibles via QEMU TCG ou un runner arm64. -- **Mode `envprobe` standalone** : distribuer la sonde seule, comme `doctor` intégrable dans n'importe quel outil perf/BPF. -- **Base de profils** : publier les rapports `envprobe` des distros courantes pour comparer sans même lancer de VM. +- **Fast boot**: direct kernel + initramfs boot and a rootfs shared over virtiofs, or even microVMs (Firecracker, Cloud Hypervisor, libkrun) to aim for one second. +- **Kernels on demand**: besides distro kernels, upstream LTS kernels (5.10, 5.15, 6.1, 6.6, 6.12) or a user-built kernel. +- **Arm64**: the same targets through QEMU TCG or an arm64 runner. +- **Standalone `envprobe`**: ship the probe on its own, as a `doctor` that any perf/BPF tool can embed. +- **Profile database**: publish `envprobe` reports for common distros, to compare without even starting a VM. diff --git a/docs/how-to-use/demo.md b/docs/how-to-use/demo.md index 9681b40..7affae8 100644 --- a/docs/how-to-use/demo.md +++ b/docs/how-to-use/demo.md @@ -1,82 +1,81 @@ -# Démo de distroproof +# distroproof demo -Six actes, du plus simple au plus complet. Les cinq premiers sont scriptés -par [`scripts/demo.sh`](../../scripts/demo.sh) ; le sixième, la TUI, se joue -à la main en suivant le déroulé ci-dessous. Tout tourne sur de vraies VM. +Six acts, from the simplest to the most complete. The first five are scripted +by [`scripts/demo.sh`](../../scripts/demo.sh); the sixth, the TUI, is played +by hand following the walkthrough below. Everything runs on real VMs. -## Préparation +## Setup ```sh -# Arch : sudo pacman -S qemu-base edk2-ovmf ; Debian/Ubuntu : qemu-system-x86 qemu-utils ovmf +# Arch: sudo pacman -S qemu-base edk2-ovmf ; Debian/Ubuntu: qemu-system-x86 qemu-utils ovmf rustup target add x86_64-unknown-linux-musl cargo build --release --target x86_64-unknown-linux-musl export PATH="$PWD/target/x86_64-unknown-linux-musl/release:$PATH" distroproof images pull debian-12 ubuntu-18.04 ubuntu-22.04 rocky-8 rocky-9 alpine ``` -Le premier `pull` télécharge environ 3 Gio. Lancez une fois chaque cible -avant la démo : le premier boot extrait noyau et initramfs, les suivants -bootent en direct en quelques secondes. +The first `pull` downloads about 3 GiB. Run each target once before the demo: +the first boot extracts the kernel and initramfs, later ones boot directly in +a few seconds. ```sh -scripts/demo.sh --list # les actes -scripts/demo.sh # actes 1 à 5 (~1 min avec le cache chaud) -scripts/demo.sh 5 # un seul acte +scripts/demo.sh --list # the acts +scripts/demo.sh # acts 1 to 5 (~1 min with a warm cache) +scripts/demo.sh 5 # a single act ``` -## Actes scriptés +## Scripted acts -1. **Le catalogue et le cache** : `distroproof images` liste les cibles, leur - source et ce qui est déjà en cache (image vérifiée, noyau extrait). -2. **Le vrai noyau de chaque distro** : `uname -r` sur l'hôte, puis dans - Rocky 8 (4.18), Rocky 9 (5.14) et Alpine (`virt`). Ce ne sont pas des - conteneurs : chaque VM tourne sur le noyau de sa distro. -3. **Deux distros, un diff** : rapport `envprobe` dans Debian 12 et dans - Alpine, puis `envprobe diff` : libc (glibc → musl), `/bin/sh`, gestionnaire - de paquets, et cette fois le noyau aussi. -4. **Une matrice déclarative** : `distroproof check` valide - `distroproof.toml` ; un fichier qui vise une image inexistante est refusé - avec ligne et colonne, sans lancer de VM ; puis `distroproof up` lance la - matrice de dogfooding avec ses variantes (paranoid, lockdown, PMU masquée). -5. **Pourquoi ça casse** : [`examples/require-btf`](../../examples/require-btf) - exige `/sys/kernel/btf/vmlinux`. Il casse sur Ubuntu 18.04 et distroproof - désigne le BTF comme cause probable, puis écrit le résumé Markdown destiné - à la PR. +1. **The catalog and the cache**: `distroproof images` lists the targets, + their source and what is already cached (verified image, extracted kernel). +2. **Each distro's real kernel**: `uname -r` on the host, then in Rocky 8 + (4.18), Rocky 9 (5.14) and Alpine (`virt`). These are not containers: each + VM runs on its distro's kernel. +3. **Two distros, one diff**: an `envprobe` report in Debian 12 and in Alpine, + then `envprobe diff`: libc (glibc → musl), `/bin/sh`, package manager, and + this time the kernel too. +4. **A declarative matrix**: `distroproof check` validates + `distroproof.toml`; a file targeting a nonexistent image is rejected with + line and column, without starting any VM; then `distroproof up` runs the + dogfooding matrix with its variants (paranoid, lockdown, hidden PMU). +5. **Why it breaks**: [`examples/require-btf`](../../examples/require-btf) + requires `/sys/kernel/btf/vmlinux`. It breaks on Ubuntu 18.04 and + distroproof names BTF as the probable cause, then writes the Markdown + summary meant for the PR. -## Acte 6 — La TUI +## Act 6 — The TUI ```sh distroproof up --hold ``` -`--hold` garde chaque VM allumée après la commande, avec un shell root sur la -console série : c'est ce qui rend la console interactive utilisable une fois -la matrice terminée. Sans `--hold`, la console montre le boot puis s'arrête -avec la VM. +`--hold` keeps each VM up after the command, with a root shell on the serial +console: that is what makes the interactive console usable once the matrix is +done. Without `--hold`, the console shows the boot and then stops with the VM. -Déroulé conseillé : +Suggested walkthrough: -| Étape | Touches | Ce qu'on montre | +| Step | Keys | What to show | | --- | --- | --- | -| Vue d'ensemble | — | une carte par cible : état, temps de boot, jauges CPU et mémoire, graphe Braille ; l'interface reste fluide pendant les boots | -| Navigation | `hjkl`, `f` | sélection d'une cible ; `f` filtre : toutes, en cours, échecs, terminées | -| Logs | `L`, `/` `noyau`, `n` `N` | logs série en direct de la cible, recherche et saut entre occurrences | -| Pause, reprise | `espace` | `stop` / `cont` via QMP : la jauge CPU retombe à zéro puis repart | -| Kill, relance | `x`, `r` | la carte passe en échec, puis la VM redémarre sur un overlay neuf | -| Rapport | `R`, `Entrée`, `h` `l` | rapport `envprobe` replié par section, déplier et replier | -| Diff | `d`, `d`, `D` | diff contre la cible suivante qui a un rapport ; `D` revient au rapport seul | -| Console | `c`, `i`, `Ctrl+]` | console série dans un panneau ; `i` pour saisir (`uname -a`, `dmesg \| tail`), `Ctrl+]` pour sortir | -| Tiling | `Ctrl+W v`, `Ctrl+W s`, `Ctrl+W hjkl`, `Ctrl+W <>+-`, `Ctrl+W q` | découper, changer de panneau, redimensionner, fermer | -| Épingler | `p` | fixe le panneau sur la cible sélectionnée, pour suivre deux cibles côte à côte | -| Palette | `:` ou `Ctrl+P` | toutes les actions par leur nom | -| Aide, sortie | `?`, `q` | `q` deux fois si des VM tournent encore : tout est arrêté et nettoyé | +| Overview | — | one card per target: state, boot time, CPU and memory gauges, Braille graph; the UI stays smooth during boots | +| Navigation | `hjkl`, `f` | select a target; `f` filters: all, running, failed, finished | +| Logs | `L`, `/` `kernel`, `n` `N` | the target's live serial logs, search and jump between matches | +| Pause, resume | `space` | QMP `stop` / `cont`: the CPU gauge drops to zero, then picks up again | +| Kill, restart | `x`, `r` | the card turns to failed, then the VM restarts on a fresh overlay | +| Report | `R`, `Enter`, `h` `l` | `envprobe` report folded by section, expand and collapse | +| Diff | `d`, `d`, `D` | diff against the next target that has a report; `D` goes back to the plain report | +| Console | `c`, `i`, `Ctrl+]` | serial console in a pane; `i` to type (`uname -a`, `dmesg \| tail`), `Ctrl+]` to leave | +| Tiling | `Ctrl+W v`, `Ctrl+W s`, `Ctrl+W hjkl`, `Ctrl+W <>+-`, `Ctrl+W q` | split, switch pane, resize, close | +| Pin | `p` | pin the pane to the selected target, to follow two targets side by side | +| Palette | `:` or `Ctrl+P` | every action by name | +| Help, quit | `?`, `q` | press `q` twice if VMs are still running: everything is stopped and cleaned up | -Plusieurs matrices côte à côte, un workspace par fichier (`1`, `2` pour -passer de l'une à l'autre) : +Several matrices side by side, one workspace per file (`1`, `2` to switch +between them): ```sh distroproof up -f distroproof.toml -f examples/require-btf/distroproof.toml ``` -Pour vérifier le repli : `distroproof up | cat` donne la sortie texte -habituelle, sans TUI, comme en CI. +To check the fallback: `distroproof up | cat` gives the usual text output, +without the TUI, just like in CI. From 659dc57492e582be4a704e9ef0698d0881486d5e Mon Sep 17 00:00:00 2001 From: moha-bekh Date: Sun, 18 Oct 2026 06:44:29 +0200 Subject: [PATCH 2/2] Translate the whole project to English Code comments, CLI help and messages, envprobe values (yes/no, reasons), TUI, guest agent, scripts, workflows and example configs are now in English. The report fixtures, collected on real machines, were converted with the same mapping, so they still match what the probes produce, and the JSON schema was regenerated from the translated doc comments. ci.yml now greps the English values (CAP_BPF yes/no, seccomp filter). schema_version stays at 1: the report structure is unchanged, only the human-readable values are. Co-Authored-By: Claude Opus 5.5 --- .cargo/config.toml | 4 +- .github/workflows/ci.yml | 20 +- .github/workflows/matrix.yml | 22 +- Cargo.toml | 4 +- README.md | 10 +- crates/distroproof/Cargo.toml | 2 +- crates/distroproof/src/agent.sh | 60 ++--- crates/distroproof/src/config.rs | 83 ++++--- crates/distroproof/src/dirs.rs | 6 +- crates/distroproof/src/event.rs | 52 ++--- crates/distroproof/src/guest.rs | 41 ++-- crates/distroproof/src/images.rs | 158 ++++++------- crates/distroproof/src/iso.rs | 29 ++- crates/distroproof/src/main.rs | 159 +++++++------ crates/distroproof/src/matrix.rs | 22 +- crates/distroproof/src/metrics.rs | 16 +- crates/distroproof/src/model.rs | 56 ++--- crates/distroproof/src/progress.rs | 50 ++--- crates/distroproof/src/qemu.rs | 62 +++--- crates/distroproof/src/report.rs | 135 ++++++----- crates/distroproof/src/sys.rs | 26 +-- crates/distroproof/src/tui/input.rs | 102 ++++----- crates/distroproof/src/tui/layout.rs | 18 +- crates/distroproof/src/tui/mod.rs | 52 ++--- crates/distroproof/src/tui/render.rs | 138 ++++++------ crates/distroproof/src/tui/state.rs | 86 ++++--- crates/distroproof/src/tui/theme.rs | 18 +- crates/distroproof/src/vm.rs | 100 ++++----- crates/envprobe-core/Cargo.toml | 2 +- crates/envprobe-core/src/diff.rs | 10 +- crates/envprobe-core/src/lib.rs | 51 ++--- crates/envprobe-core/src/parse/elf.rs | 6 +- crates/envprobe-core/src/parse/kconfig.rs | 4 +- crates/envprobe-core/src/parse/ldconfig.rs | 4 +- .../envprobe-core/src/parse/libc_version.rs | 6 +- crates/envprobe-core/src/parse/mod.rs | 4 +- crates/envprobe-core/src/parse/mounts.rs | 2 +- crates/envprobe-core/src/parse/os_release.rs | 2 +- crates/envprobe-core/src/parse/status.rs | 6 +- crates/envprobe-core/src/probes/bpf.rs | 52 ++--- crates/envprobe-core/src/probes/btf.rs | 4 +- crates/envprobe-core/src/probes/cgroups.rs | 8 +- crates/envprobe-core/src/probes/clib.rs | 25 ++- crates/envprobe-core/src/probes/fhs.rs | 16 +- crates/envprobe-core/src/probes/identity.rs | 16 +- crates/envprobe-core/src/probes/init.rs | 8 +- crates/envprobe-core/src/probes/kernel.rs | 14 +- crates/envprobe-core/src/probes/libs.rs | 10 +- crates/envprobe-core/src/probes/mod.rs | 2 +- crates/envprobe-core/src/probes/modules.rs | 16 +- crates/envprobe-core/src/probes/packages.rs | 4 +- crates/envprobe-core/src/probes/perf.rs | 8 +- crates/envprobe-core/src/probes/privileges.rs | 18 +- crates/envprobe-core/src/probes/security.rs | 20 +- crates/envprobe-core/src/probes/shell.rs | 4 +- crates/envprobe-core/src/probes/sysctl.rs | 2 +- crates/envprobe-core/src/probes/tracing.rs | 12 +- crates/envprobe-core/src/probes/xdg.rs | 8 +- crates/envprobe-core/src/sys.rs | 52 ++--- crates/envprobe-core/src/util.rs | 18 +- .../fixtures/reports/alpine-3.20-rootfs.json | 92 ++++---- .../reports/archarm-orbstack-root.json | 210 +++++++++--------- .../reports/archarm-orbstack-user.json | 72 +++--- .../fixtures/reports/debian-12-rootfs.json | 82 +++---- crates/envprobe-core/tests/parsers.rs | 4 +- crates/envprobe-core/tests/reports.rs | 29 +-- crates/envprobe/Cargo.toml | 2 +- crates/envprobe/src/main.rs | 36 +-- distroproof.toml | 10 +- examples/require-btf/distroproof.toml | 4 +- examples/require-btf/require-btf.sh | 8 +- schema/envprobe-report.schema.json | 14 +- scripts/demo.sh | 52 ++--- scripts/probe-rootfs.sh | 8 +- scripts/selftest.sh | 8 +- 75 files changed, 1268 insertions(+), 1308 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index f714b6d..8149ba4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,5 @@ -# Les cibles musl embarquent leur CRT (self-contained) : rust-lld suffit pour -# lier un binaire statique, y compris en compilation croisée depuis arm64. +# musl targets ship their own CRT (self-contained): rust-lld is enough to link +# a static binary, including when cross-compiling from arm64. [target.x86_64-unknown-linux-musl] linker = "rust-lld" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c007afc..a51bc74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: targets: x86_64-unknown-linux-musl - uses: Swatinem/rust-cache@v2 - run: cargo build --release --target x86_64-unknown-linux-musl - - name: Vérifier que le binaire est statique + - name: Check that the binary is static run: | bin=target/x86_64-unknown-linux-musl/release/envprobe file "$bin" @@ -51,25 +51,25 @@ jobs: with: name: envprobe-x86_64-linux-musl - run: chmod +x envprobe - - name: Utilisateur normal + - name: Normal user run: ./envprobe | tee user.txt - name: Root run: sudo ./envprobe | tee root.txt - - name: La sortie diffère selon les privilèges + - name: Output differs with privileges run: | ! diff -q user.txt root.txt - grep -E '^ CAP_BPF +non' user.txt - grep -E '^ CAP_BPF +oui' root.txt - - name: Conteneurs Docker par défaut + grep -E '^ CAP_BPF +no' user.txt + grep -E '^ CAP_BPF +yes' root.txt + - name: Default Docker containers run: | for image in debian:12 alpine:3.20 rockylinux:9; do echo "::group::$image" docker run --rm -v "$PWD/envprobe:/envprobe:ro" "$image" /envprobe | tee "docker-${image%%:*}.txt" grep -E '^ container +docker' "docker-${image%%:*}.txt" - grep -E '^ seccomp +filtre' "docker-${image%%:*}.txt" + grep -E '^ seccomp +filter' "docker-${image%%:*}.txt" echo "::endgroup::" done - - name: Rapports JSON et diff hôte / Alpine + - name: JSON reports and host / Alpine diff run: | ./envprobe --json > host.json sudo ./envprobe --json > host-root.json @@ -85,8 +85,8 @@ jobs: grep -q '^\[libc\]' diff.txt grep -q '^\[shell\]' diff.txt grep -q '^\[packages\]' diff.txt - # Même noyau : la release ne change pas. La config peut manquer dans - # le conteneur (/boot absent), d'où la vérification sur release seule. + # Same kernel: the release does not change. The config can be missing in + # the container (no /boot), hence the check on release only. ! grep -qE '^[-+] release ' diff.txt - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/matrix.yml b/.github/workflows/matrix.yml index d72dce9..a2cd488 100644 --- a/.github/workflows/matrix.yml +++ b/.github/workflows/matrix.yml @@ -1,7 +1,7 @@ -name: Matrice +name: Matrix -# Dogfooding : distroproof lance envprobe dans des VM de chaque distro cible -# (distroproof.toml) et publie un résumé Markdown sur la PR. +# Dogfooding: distroproof runs envprobe in VMs of each target distro +# (distroproof.toml) and posts a Markdown summary on the PR. on: pull_request: @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Activer KVM + - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ | sudo tee /etc/udev/rules.d/99-kvm4all.rules @@ -29,7 +29,7 @@ jobs: sudo udevadm trigger --name-match=kvm ls -l /dev/kvm - - name: Installer QEMU et OVMF + - name: Install QEMU and OVMF run: | sudo apt-get update sudo apt-get install -y --no-install-recommends qemu-system-x86 qemu-utils ovmf @@ -40,8 +40,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo build --release --target x86_64-unknown-linux-musl - # Images cloud et noyaux extraits : renouvelés chaque semaine pour - # suivre les mises à jour des distros. + # Cloud images and extracted kernels: renewed every week to follow + # distro updates. - id: week run: echo "week=$(date +%G-%V)" >> "$GITHUB_OUTPUT" - uses: actions/cache@v4 @@ -50,18 +50,18 @@ jobs: key: distroproof-images-${{ steps.week.outputs.week }}-${{ hashFiles('distroproof.toml') }} restore-keys: distroproof-images- - - name: Lancer la matrice + - name: Run the matrix run: | set +e target/x86_64-unknown-linux-musl/release/distroproof up --no-tui --jobs 3 \ --json matrix.json --junit junit.xml --markdown summary.md echo "code=$?" >> "$GITHUB_ENV" - - name: Résumé + - name: Summary if: always() run: cat summary.md >> "$GITHUB_STEP_SUMMARY" - - name: Commentaire de PR + - name: PR comment if: always() && github.event_name == 'pull_request' continue-on-error: true env: @@ -79,5 +79,5 @@ jobs: junit.xml summary.md - - name: Code de sortie de la matrice + - name: Matrix exit code run: exit "$code" diff --git a/Cargo.toml b/Cargo.toml index cd6c762..f2339bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = ["crates/envprobe-core", "crates/envprobe", "crates/distroproof"] version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" -repository = "https://github.com/moha-bekh/distroproof" +repository = "https://github.com/devutils-org/distroproof" [workspace.dependencies] envprobe-core = { path = "crates/envprobe-core" } @@ -25,7 +25,7 @@ toml = "1" tui-term = "0.3" vt100 = "0.16" -# Le hachage des images (plusieurs centaines de Mio) reste rapide en debug. +# Keeps image hashing (several hundred MiB) fast in debug builds. [profile.dev.package.sha2] opt-level = 3 diff --git a/README.md b/README.md index 4d7eb90..b0d9e8f 100644 --- a/README.md +++ b/README.md @@ -206,13 +206,11 @@ taken right before the command, then: the suspicious difference (missing BTF, paranoid set to 3, musl…); - writes `--json`, `--junit` (CI) and `--markdown` (PR comment) on request. -Sample output (the CLI prints its messages in French): - ```text -[ubuntu-18.04] échec (code 1) - comparée à ubuntu-22.04 : 65 différences significatives - cause probable : btf.vmlinux : présent, 5286152 octets → indisponible (/sys/kernel/btf/vmlinux absent) - aussi : kernel.CONFIG_DEBUG_INFO_BTF : y → absente de la config +[ubuntu-18.04] failed (code 1) + compared with ubuntu-22.04: 65 significant differences + probable cause: btf.vmlinux: present, 5286152 bytes → unavailable (/sys/kernel/btf/vmlinux missing) + also: kernel.CONFIG_DEBUG_INFO_BTF: y → not in the config ``` Examples: [`distroproof.toml`](distroproof.toml) (envprobe dogfooding on 10 diff --git a/crates/distroproof/Cargo.toml b/crates/distroproof/Cargo.toml index 9b0e807..43863da 100644 --- a/crates/distroproof/Cargo.toml +++ b/crates/distroproof/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "distroproof" -description = "Lance un programme dans une matrice de VM de distros" +description = "Runs a program across a matrix of distro VMs" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/distroproof/src/agent.sh b/crates/distroproof/src/agent.sh index 1adb661..59594d7 100644 --- a/crates/distroproof/src/agent.sh +++ b/crates/distroproof/src/agent.sh @@ -1,18 +1,18 @@ #!/bin/sh -# Agent invité de distroproof, lancé en root par cloud-init (runcmd) depuis le -# disque payload extrait dans /distroproof. +# distroproof guest agent, run as root by cloud-init (runcmd) from the payload +# disk extracted into /distroproof. # -# Canal vers l'hôte : /dev/ttyS1, une ligne par message. -# @ READY agent démarré -# @ STAGE <étape> début d'étape (setup, probe, command, kernel) -# @ EXIT <étape> fin d'étape -# @ WARN problème non bloquant (sysctl refusé…) -# @ DONE disque de sortie écrit, extinction imminente -# > stdout de l'étape en cours -# ! stderr de l'étape en cours +# Channel to the host: /dev/ttyS1, one line per message. +# @ READY agent started +# @ STAGE stage start (setup, probe, command, kernel) +# @ EXIT stage end +# @ WARN non-blocking problem (sysctl refused…) +# @ DONE output disk written, shutting down soon +# > stdout of the current stage +# ! stderr of the current stage # -# Les sorties complètes, les codes de retour et le rapport envprobe sont -# écrits dans une archive tar sur le disque de sortie (série dp-output). +# Full outputs, exit codes and the envprobe report are written to a tar +# archive on the output disk (serial dp-output). set -u D=/distroproof OUT=$D/out @@ -28,8 +28,8 @@ warn() { say WARN "$*"; echo "$*" >>"$OUT/warnings"; } mkdir -p "$OUT" say READY "$(cut -d' ' -f1 /proc/uptime)" -# Paramètres de la tâche : DP_USER, DP_CAPS, DP_PROBE, DP_EXTRACT_KERNEL, -# DP_SETUP, DP_COMMAND (1 si job/setup.sh, job/command.sh existent). +# Job parameters: DP_USER, DP_CAPS, DP_PROBE, DP_EXTRACT_KERNEL, +# DP_SETUP, DP_COMMAND (1 if job/setup.sh, job/command.sh exist). . $D/job/env disk() { @@ -48,8 +48,8 @@ relay() { done } -# run <étape> : sorties copiées dans $OUT/<étape>.{out,err} -# et relayées ligne à ligne sur le canal. +# run : outputs copied to $OUT/.{out,err} +# and relayed line by line on the channel. run() { stage=$1 shift @@ -68,19 +68,19 @@ run() { return $rc } -# Utilisateur de test. as_user