Skip to main content

max / alloy

pkg: verify the host export path against real distrobox Installed distrobox 1.8.2.5 on fw13 — the version the Containerfile pins, not apt's 1.7.0, since checking the argv against a version Alloy does not ship would prove nothing about the image. Both host-level assumptions hold. `distrobox create --yes` is genuinely non-interactive, `distrobox enter -- distrobox-export` is the shape that reaches the export tool from the host, and `/usr/bin/{bin}` resolves for Fedora images. It stays a guess for an arbitrary image, which distrobox accepts and which need not put commands in /usr/bin. The new test exports `dnf` on purpose: an exported wrapper takes the binary's bare name, so exporting something the host already has shadows it on the PATH. `dnf` is in every Fedora image and on no Pop!_OS host, which is also the real use case. It writes into the real ~/.local/bin rather than a temp dir, so the export directory is covered too, and removes it after. rpm-ostree stays unavailable — it has no candidate on Pop!_OS and its status --json would be meaningless off an ostree system, so the installed and system tabs are still correctly blocked on the image. 138 tests pass, 6 ignored, clippy and rustdoc clean.
Co-Authored-By
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-19 23:05 UTC
Signed with PGP, not checked
Commit: e16740fc0259ad5046a4f3194124b28bb36dab97
Parent: 3466862
1 file changed, +76 insertions, -6 deletions
@@ -589,12 +589,13 @@
589 589 /// reached through `distrobox enter -- `, which is the documented host-side
590 590 /// idiom for it.
591 591 ///
592 - /// `/usr/bin/{bin}` is an assumption, and a flagged one: `--bin` wants an
593 - /// absolute path inside the container, the spec names a command rather than
594 - /// a path, and resolving it properly means asking the box where the command
595 - /// lives. Distrobox is not installed on the dev box, so this path is argv
596 - /// only — see the module docs on parsers checked against nothing but their
597 - /// own fixtures, which is the same trap.
592 + /// `/usr/bin/{bin}` because `--bin` wants an absolute path inside the
593 + /// container while the spec names a command. Verified against distrobox
594 + /// 1.8.2.5 — the version the Containerfile pins — on a real Fedora box:
595 + /// both this argv and `create`'s reach the tool and do what they claim.
596 + /// It remains a guess for an arbitrary image, which distrobox accepts and
597 + /// which need not put its commands in `/usr/bin`; resolving it properly
598 + /// means asking the box where the command lives.
598 599 fn export(&self, name: &str, spec: &SpecBox) -> Result<Vec<Effect>> {
599 600 let bins = spec.bins(name)?;
600 601 let dir = export_dir()?;
@@ -2765,6 +2766,75 @@
2765 2766 std::fs::remove_dir_all(&mount).ok();
2766 2767 }
2767 2768
2769 + /// Create and export a real `host` box through distrobox.
2770 + ///
2771 + /// The `host` path is the one Alloy hands entirely to someone else's tool,
2772 + /// so what needs checking is not the behavior but the argv: that
2773 + /// `distrobox create --yes` really is non-interactive, and that
2774 + /// `distrobox enter -- distrobox-export` is the shape that reaches the
2775 + /// export tool from the host. Both were assumptions until this ran.
2776 + ///
2777 + /// It also settles `/usr/bin/{bin}`. `--bin` wants an absolute path inside
2778 + /// the container while the spec names a command, and Fedora images put
2779 + /// commands in `/usr/bin`, so the guess holds for the image Alloy ships.
2780 + /// It is still a guess for an arbitrary image, which distrobox accepts.
2781 + ///
2782 + /// Exports `dnf`, deliberately: an exported wrapper takes the binary's
2783 + /// bare name, so exporting something the host already has shadows it on the
2784 + /// PATH. `dnf` is in every Fedora image and on no Pop!_OS host, which is
2785 + /// also the real use case — you export a tool from a box because the host
2786 + /// does not have it.
2787 + ///
2788 + /// Ignored by default: it needs distrobox, and the first `enter` runs
2789 + /// distrobox's full container setup, which pulls packages and takes minutes.
2790 + #[test]
2791 + #[ignore = "requires distrobox, creates a container, and takes minutes"]
2792 + fn a_real_host_box_is_created_and_exported_through_distrobox() {
2793 + let name = "alloy-test-host";
2794 + let spec = Spec::parse(&format!(
2795 + r#"
2796 + [box.{name}]
2797 + level = "host"
2798 + image = "registry.fedoraproject.org/fedora:42"
2799 + export = {{ bin = ["dnf"] }}
2800 + "#
2801 + ))
2802 + .unwrap();
2803 +
2804 + let (_, entry) = spec.resolve(name).unwrap();
2805 + let mut log = CommandLog::new();
2806 + let exported = export_dir().unwrap().join("dnf");
2807 + let cleanup = || {
2808 + let _ = Invocation::new("distrobox").args(["rm", "--force", name]).probe();
2809 + let _ = Invocation::new("podman").args(["rm", "--force", name]).probe();
2810 + std::fs::remove_file(&exported).ok();
2811 + };
2812 + cleanup();
2813 + assert!(!exported.exists(), "the export lands somewhere empty");
2814 +
2815 + Podman.create(name, entry).unwrap().run(&mut log).unwrap();
2816 +
2817 + for effect in Podman.export(name, entry).unwrap() {
2818 + let Effect::Run(_) = &effect else {
2819 + panic!("host exports go through distrobox-export, not a written file");
2820 + };
2821 + effect.apply(&mut log).expect("distrobox accepts the argv Alloy builds");
2822 + }
2823 +
2824 + assert!(
2825 + exported.is_file(),
2826 + "distrobox-export wrote the wrapper where Alloy asked it to"
2827 + );
2828 + let wrapper = std::fs::read_to_string(&exported).unwrap();
2829 + assert!(
2830 + wrapper.contains(name) && wrapper.contains("/usr/bin/dnf"),
2831 + "the wrapper points back at this box's binary:\n{wrapper}"
2832 + );
2833 +
2834 + cleanup();
2835 + assert!(!exported.exists(), "and the test leaves nothing on the PATH");
2836 + }
2837 +
2768 2838 /// Parse this machine's real podman and flatpak output.
2769 2839 ///
2770 2840 /// Ignored by default: needs both installed, and what it finds depends on