Skip to main content

max / alloy

docs: fold the isolation dial into the authored docs The doc edits alloy-package-ux listed and deferred. Four files, no code. CONSOLE.md's non-goal read "not a package manager," which scoped out the wrong half. Browsing a catalog is what that line was protecting against, and explaining where a package belongs is MANIFESTO principle 6, so it is now "not a store" and the two are separable. The subcommand list gains `alloy pkg install` and `alloy pkg box`, plus why three verbs open one view and how a box's level picks its backend. MANIFESTO principle 6's "three-surface install model" becomes the dial. Three surfaces to choose between is the problem being described, not the model being proposed. Carried the `workspace` justification over, and its limit with it: blast radius, never sandboxing. STACK.md gains a Containers section, which had none despite distrobox being a settled pick. distrobox over toolbx for `host` (the export mechanism decides it, now that the GTK pivot took Ptyxis and left the wrapper doing the integration), podman directly for `workspace` with the flags evidence, flatpak as `sandboxed`. The Rust gap is named rather than papered over, per principle 1. The Containerfile was missing podman and distrobox entirely. distrobox is pinned to 1.8.2.5 per the note, in its own RUN: a pinned NVR inside the main install list would take that whole layer down with it if the pin ever goes stale, and alone it names itself. The pin was checked against Fedora's package API rather than trusted from the note. f43 ships 1.8.2.5-1.fc43.
Co-Authored-By
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-19 22:35 UTC
Signed with PGP, not checked
Commit: 8f9ff4f5eabfbb09a3e5535984cdfdbadd0b7d23
Parent: e4b8b45
4 files changed, +58 insertions, -8 deletions
M Containerfile +17 -4
@@ -94,9 +94,10 @@
94 94 #
95 95 # Sources noted per group. Anything absent from both Fedora main and
96 96 # Terra is downloaded directly (Nerd Fonts, in the layer above). The
97 - # `flatpak` client is installed so users can pull ungoogled-chromium and
98 - # other on-demand apps from Flathub post-install; no Flatpaks are
99 - # provisioned at build or first-boot time.
97 + # `flatpak` client is installed as the `sandboxed` level of the isolation
98 + # dial, which is also how users pull ungoogled-chromium and other
99 + # on-demand apps from Flathub post-install; no Flatpaks are provisioned
100 + # at build or first-boot time.
100 101 # =====================================================================
101 102 RUN dnf install -y \
102 103 # Compositor and Wayland session (Fedora main)
@@ -160,10 +161,22 @@
160 161 grim slurp \
161 162 # Browser (Gecko default; ungoogled-chromium is opt-in Flatpak per docs/STACK.md)
162 163 firefox \
163 - # Flatpak client (user-installed apps on demand; see docs/STACK.md)
164 + # Containers: the three backends behind `alloy pkg box`'s isolation
165 + # dial (docs/STACK.md#containers). The user picks a level, not a
166 + # tool. podman is the runtime and is what `workspace` calls
167 + # directly; distrobox wraps it for `host`; flatpak is `sandboxed`,
168 + # and is also the install path for ungoogled-chromium.
169 + podman \
164 170 flatpak \
165 171 && dnf clean all
166 172
173 + # distrobox is pinned. v2 is a Go rewrite, at rc.3 as of 2026-06-29, and
174 + # upstream's own announcement says v1 stays the production recommendation
175 + # and that exported binaries and apps must be re-exported after
176 + # upgrading. Fedora's 1.8.2.5 is the pin; moving it wants a release note.
177 + RUN dnf install -y 'distrobox-1.8.2.5*' \
178 + && dnf clean all
179 +
167 180 # =====================================================================
168 181 # Package removals — stock desktop pieces Alloy replaces
169 182 # =====================================================================
M docs/CONSOLE.md +20 -3
@@ -19,6 +19,8 @@
19 19 alloy audio # audio: pactl front, streams and devices [shipped]
20 20 alloy mesh # mesh VPN: tailscale front (alias: tail) [shipped]
21 21 alloy display # outputs: swaymsg output / wlr-randr front
22 + alloy pkg install # what is layered, and where a package belongs
23 + alloy pkg box # containers: the isolation dial [shipped]
22 24 alloy update # system updates: rpm-ostree front
23 25 alloy sync # syncthing front (see CONTINUITY.md)
24 26 alloy config <path> # schema-driven TOML/KDL editor
@@ -36,6 +38,21 @@
36 38 startup from `tailscale debug prefs`. That is a `debug` interface and therefore
37 39 unstable, so the lookup degrades to showing nothing rather than failing.
38 40
41 + `alloy pkg install`, `alloy pkg box`, and `alloy update` are three verbs onto one
42 + view, each landing on its own tab: installed, boxes, system. They are one view
43 + because the first and third share an object rather than a data source.
44 + `rpm-ostree install` does not touch the running system, it stages a deployment,
45 + and that staged deployment is what the system tab shows. `alloy update` keeps its
46 + top-level name because this document already specced it. Design in the wiki note
47 + `alloy-package-ux`.
48 +
49 + The boxes tab does not front a single CLI. A box's isolation level picks its
50 + backend (`host` to distrobox, `workspace` to podman directly, `sandboxed` to
51 + flatpak), so the user chooses isolation and Alloy chooses the implementation. That
52 + keeps the level as the stable interface and leaves the backend swappable. Each
53 + backend builds argv and runs nothing; the view executes through the command log,
54 + which makes "every action shows its invocation" structural rather than remembered.
55 +
39 56 Backend picks that differ from the sketch above: `net` fronts `nmcli` alone,
40 57 since Alloy is Fedora and NetworkManager is what is there. `audio` fronts
41 58 `pactl` rather than `wpctl`, because `pactl -f json` is a documented contract
@@ -139,9 +156,9 @@
139 156
140 157 The ordering below is the reverse of what this document originally planned, which put `alloy config` alone at v0.5 and every live-state subcommand at v1. The live-state views went first instead. They are small enough to carve one at a time, and each one forced a piece of shared machinery into existence against something real: the shell chrome and log pane from `net`, the second list and the `Cursor` from `audio`, the two-pane layout and `AlloyConnector` from `audio`'s routing, the background tick from watching streams appear. `alloy config` needs the form widgets and the schema parser at once, and it is a better shape to build on a shell that has already carried three screens.
141 158
142 - - **Shipped.** `alloy net`, `alloy audio`, `alloy mesh`. Plus the shell they share: frame, reserved keys, focus, command-log pane, background tick.
159 + - **Shipped.** `alloy net`, `alloy audio`, `alloy mesh`, and `alloy pkg box`. Plus the shell they share: frame, reserved keys, focus, command-log pane, background tick. `alloy pkg` forced three more pieces into it: `AlloyTabs`, a Cancel that views see before the shell claims it (a confirm needs a cancel that is not "exit the app"), and terminal suspend, so entering a box can hand the TTY to another interactive program.
143 160 - **Next.** `alloy config`, with schemas for the v0-adopted TOML configs (rio, yazi, mako, and others; the sway config takes the text-edit fallback). The largest remaining piece: schema-DSL v1 parser, `toml_edit` roundtrip layer, and the form widgets together.
144 - - **Blocked on the target machine.** `alloy display` and `alloy update` front `swaymsg`/`wlr-randr` and `rpm-ostree`, none of which exist on a non-Fedora, non-sway development box. Writing them now would mean shipping parsers checked against nothing but their own fixtures, which is exactly how the two parser bugs found so far got written. They want the QEMU image or real hardware.
161 + - **Blocked on the target machine.** `alloy display`, `alloy update`, and `alloy pkg install` front `swaymsg`/`wlr-randr` and `rpm-ostree`, none of which exist on a non-Fedora, non-sway development box. The boxes tab was never blocked this way, since podman and flatpak are both on the dev box and their output was captured from it. Writing them now would mean shipping parsers checked against nothing but their own fixtures, which is exactly how the two parser bugs found so far got written. They want the QEMU image or real hardware.
145 162 - **Then.** `alloy sync`, `alloy theme`. `alloy theme` swaps the runtime theme in place (makeover consumer, no re-login). First-boot flow (see [CONTINUITY.md](CONTINUITY.md)) is a thin shim over `alloy mesh` and `alloy sync` enrollments.
146 163 - **v1.x.** Additional adopted-tool schemas as the v0 stack grows. (`alloy hinged` was shelved with the FW12 tablet flow in the pivot.)
147 164 - **v2+.** Third-party subcommand registration (a well-known directory of ratatui adapters the console discovers at runtime), if a real ecosystem case emerges. Not planned.
@@ -153,7 +170,7 @@
153 170 - **Not a shell replacement.** Users still live in rio + zellij/tmux + helix. The console is invoked for specific tasks, then closed.
154 171 - **Not a systemd control panel.** `systemctl` is fine. If a common systemd operation shows up in daily use, add a subcommand; don't build a general systemd UI.
155 172 - **Not a launcher.** Launching is the shell (terminal-driven); there is no graphical launcher. The console is invoked by name, not discovered via search.
156 - - **Not a package manager.** `alloy update` fronts `rpm-ostree` for atomic upgrades and rollbacks; installing individual packages is a rare enough action on Silverblue that keeping it out of the console is honest.
173 + - **Not a store.** The console never browses or searches a catalog. `alloy pkg` shows what is installed, where it lives, and what each surface costs; it does not help you find software you have not named. This non-goal read "not a package manager" until 2026-07-19, which scoped out the wrong half. Browsing is what the line was protecting against. Explaining where a package belongs is MANIFESTO principle 6, and the two are separable.
157 174
158 175 ## Open questions
159 176
@@ -43,7 +43,11 @@
43 43
44 44 5. **Opinionated defaults; the configuration is the documentation.** Every default is a position we will defend in writing.
45 45
46 - 6. **The console teaches the mental model.** The wedge is the `alloy` console: a terminal-native front door to the system, including a package view that makes Silverblue's three-surface install model (rpm-ostree layered / Flatpak / distrobox) legible. That model is the hardest thing about Silverblue and the thing GNOME Software does not address well.
46 + 6. **The console teaches the mental model.** The wedge is the `alloy` console: a terminal-native front door to the system, including a package view that makes Silverblue's install model legible. That model is the hardest thing about Silverblue and the thing GNOME Software does not address well.
47 +
48 + Silverblue presents it as three surfaces to choose between (rpm-ostree layered, Flatpak, distrobox), which is the problem rather than the model. Alloy replaces the choice with one **isolation dial**: `host` sees the full home and host integration, `workspace` sees its own home and the directories you name it, `sandboxed` sees what a portal hands it. The user picks how much a piece of software can reach, and Alloy picks the backend from that. Level is the stable interface, so a better backend can be repointed underneath it.
49 +
50 + `workspace` is the reason this is a model Alloy owns rather than a menu over two tools. Neither incumbent serves it: distrobox is all-in on host integration, flatpak all-in on sandboxing, and "a box that sees one project directory and nothing else" is what people want every time they run a package manager over someone else's code. It is also described as limiting blast radius, never as sandboxing. A rootless container with a bind mount is a speed bump. `sandboxed` is the only level with a real isolation model behind it, and the view has to say so rather than offer three interchangeable degrees of safety.
47 51
48 52 ## What we author vs what we adopt
49 53
@@ -146,6 +146,22 @@
146 146 - **Zen Browser.** Gecko-based and design-forward, but its identity centers vertical tabs and sidebar-forward layout. Revisit only if Zen ships a first-class horizontal mode as a supported configuration.
147 147 - **Chromium as default, Brave, Vivaldi, Arc, Helium.** Alloy doesn't reinforce browser-engine monoculture. Ungoogled-chromium covers the "I need Blink" case without making it the default.
148 148
149 + ## Containers
150 +
151 + **podman as the runtime, distrobox at the `host` level only, flatpak as the `sandboxed` backend.** These are not three tools the user chooses between. They are the three backends behind the isolation dial in [CONSOLE.md](CONSOLE.md), and the user picks a level rather than a tool. Design in the wiki note `alloy-package-ux`.
152 +
153 + **distrobox** was picked over toolbx for `host`. Both are plain Fedora main-repo RPMs, no COPR. The deciding factor is Alloy-specific: the usual answer is that the choice does not matter because Ptyxis speaks to podman, toolbx, and distrobox directly, but Ptyxis is GTK and the 2026-07-17 pivot dropped the GTK stack, so nothing else in Alloy's stack does container integration and the wrapper has to. `distrobox-export --bin foo --export-path ~/.local/bin` writes a host wrapper script, and `~/.local/bin` is already on Fedora's default PATH. Toolbx has no export mechanism at all: its verbs are create, enter, run, list, rm, rmi, help, and getting a container tool onto the host PATH under it has no command, only a paragraph telling you to hand-write a wrapper. That cannot be taught in the console's own idiom. Distrobox also takes arbitrary images, where toolbx requires images to satisfy a checklist most upstream images fail.
154 +
155 + **podman directly** for `workspace`, because distrobox cannot express that level. Checked against distrobox-create's own documentation: `--home` relocates HOME but does not stop the host home being mounted, and the five unshare flags cover devices and sysfs, groups, ipc, netns, and process, none of them the mounts. There is no flag that withholds the host filesystem paths or the host D-Bus session socket. That is not an oversight to work around. Host integration is what distrobox is for. Two of the six rows `workspace` promises are unreachable through it, so `workspace` is podman with the wrapper's opinions left off, and Alloy writes the export wrapper itself at that level: a generated script on `~/.local/bin` that re-enters the box and forwards its arguments, starting it first if stopped. A few lines doing what `distrobox-export --bin` does. `host` keeps `distrobox-export` because `host` keeps distrobox.
156 +
157 + **Not shipping both wrappers**, despite that being Universal Blue's choice. Principle 5 is opinionated defaults and principle 6 is legibility; two wrappers doing one job, in a distro whose headline claim is making the install model legible, works against both. Bluefin affords the ambiguity because Ptyxis hides it.
158 +
159 + Alloy owns the model over these backends and does not own a container runtime, an image format, a registry, or a sandbox implementation. That boundary is load-bearing: a from-scratch container deployment system is `sap` in different clothes, which [MANIFESTO.md](MANIFESTO.md) shelves. The build cost is also badly asymmetric. Unsandboxed containers are a shell script over podman. Sandboxing is flatpak's entire reason to exist (portals, seccomp, D-Bus proxying, device and filesystem policy), a decade of security engineering with a failure mode where 90% correct is worse than not trying, because users believe the sandbox is real.
160 +
161 + **The Rust gap, named rather than papered over.** No Rust option exists in this space, mature or otherwise. The Rust projects nearby are GUIs over distrobox. Principle 1 says to name that honestly. Alloy's Rust wins here are one layer up and already held: bootc is Rust.
162 +
163 + Risk to track: distrobox v2 is a Go rewrite, at rc.3 as of 2026-06-29. Its own announcement says v1 remains the production recommendation and that exported binaries and apps must be re-exported after upgrading. The Containerfile pins Fedora's 1.8.2.5. Do not chase the rc; keep a release note ready for whenever the pin moves.
164 +
149 165 ## Shell
150 166
151 167 **Nushell as the login shell; bash unchanged as `/bin/sh` and `/bin/bash`.** Full rationale, architecture, and interoperability rules in [SHELL.md](SHELL.md).