mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-27 04:52:25 +03:00
feat(build): borealis-style multi-stage rootfs for the open guest components (#309)
## What Adds `build/` — a Dockerfile with `mesa-build`, `nestri-build`, `os-base`, `runtime`, `runtime_prod` and `runtime_debug` stages, plus the `etc/` overlay, `mkimage.sh` and a `Makefile` — laid out the way [borealis](https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/main/project-borealis) lays out its own `build/`. This moves the guest rootfs formula into this repo, targeting the four open guest components already here: `nescope`, `neshub`, `neswire`, `nescapture`. ## Two structural properties worth calling out - **No privileged host chroot.** A bare `chroot` into a hand-extracted rootfs needs `/proc`, `/sys`, `/dev` bind-mounted in first. `os-base` here is `FROM artixlinux/artixlinux:base-openrc` directly with `pacman -S` as plain `RUN` steps — a Docker build step already has its own `/proc`/`/sys`/`/dev`. - **No host-side ownership bug to guard against.** `COPY --from=` runs as root inside the build with no invoking-user uid in the loop. ## Scope boundary **Deliberately excludes Proton and Valve's `steamclient.so`** — both closed, and `CLAUDE.md` forbids closed content in this repo. `runtime_prod`, tagged `nestrilabs/nestri:base`, is a complete, bootable, Steam-less image — and also the shared foundation other builds start from. Whatever layers Proton/Steam on top of it is a closed build outside this repo, by design. ## Known gap Nothing starts a payload yet — `nesinit` isn't open code — so `/etc/init.d/nescope` boots it in plain-compositor mode (no command after `--`) rather than running a game. Real and testable, just not a full session yet. Details in `build/README.md`. ## Status Built and tagged locally as `nestrilabs/nestri:base` (podman, no `--no-cache` issues, greptile's three findings all fixed and verified against a real build). Not yet packed into a disk image or run inside nesbox. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- greptile_comment --> <h3>Greptile Summary</h3> The PR adds a multi-stage Artix/OpenRC guest-rootfs build for the open Nestri components, with production and debug image flavors. - Builds patched Mesa and the Rust workspace in dedicated builder stages. - Assembles and configures the bootable guest environment and OpenRC services. - Packs a selected container image into an ext4 root filesystem while retaining rootless container storage access. <h3>Confidence Score: 5/5</h3> The PR appears safe to merge. No blocking failure remains. <h3>Important Files Changed</h3> | Filename | Overview | |----------|----------| | build/Dockerfile | Defines the complete multi-stage build, overlays repository-root-relative configuration paths, and creates production and debug runtime targets. | | build/Makefile | Provides consistent image build and packing targets using a repository-root context and matching image tags. | | build/scripts/mkimage.sh | Keeps container-runtime operations in the invoking user's storage while escalating only filesystem creation and mounting operations. | | build/etc/conf.d/nestri-user-env | Supplies the shared service environment and export function required by the OpenRC service scripts. | | build/etc/init.d/guest-net | Configures optional guest networking from kernel parameters or stable defaults. | | build/etc/init.d/neswire | Starts the audio sink after its dependencies and pins it as the PipeWire default once the graph is ready. | <h3>Flowchart</h3> ```mermaid %%{init: {'theme': 'neutral'}}%% flowchart TD A[Arch builder] --> B[Mesa build] A --> C[Nestri workspace build] D[Artix OpenRC base] --> E[Common runtime] B --> E C --> E F[build/etc overlay] --> E E --> G[runtime_prod] E --> H[runtime_debug] G --> I[Container image] H --> J[Debug container image] I --> K[mkimage.sh] J --> K K --> L[ext4 rootfs] ``` <sub>Reviews (6): Last reviewed commit: ["refactor(build): rename the published im..."](https://github.com/nestrilabs/nestri/commit/6fecc8cc31bc425db952193074d4b9b0f861de89) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=58981739)</sub> <!-- /greptile_comment -->
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# build/ — the guest rootfs
|
||||
|
||||
Builds a bootable Artix/OpenRC image for the box's virtio-blk root: Mesa
|
||||
(virtio-gpu native context) plus the four open guest components —
|
||||
[`nescope`](../apps/nescope), [`neshub`](../apps/neshub),
|
||||
[`neswire`](../apps/neswire), [`nescapture`](../apps/nescapture) — laid out
|
||||
the way [borealis](https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/main/project-borealis)
|
||||
lays out its `build/`: one big multi-stage `Dockerfile`, `--target` picks the
|
||||
flavor, `etc/` holds the files that get overlaid onto the image verbatim.
|
||||
|
||||
```
|
||||
build/
|
||||
├── Dockerfile everything, in stages: mesa-build, nestri-build,
|
||||
│ os-base, runtime, runtime_prod, runtime_debug
|
||||
├── etc/ overlaid onto the image's /etc as-is
|
||||
├── scripts/
|
||||
│ └── mkimage.sh docker export → raw ext4, for nesbox's virtio-blk
|
||||
├── Makefile
|
||||
└── output/ `make image` writes here (gitignored)
|
||||
```
|
||||
|
||||
```sh
|
||||
make build # docker build --target runtime_prod → ghcr.io/nestrilabs/nestri/base:latest
|
||||
make build-debug # docker build --target runtime_debug → ghcr.io/nestrilabs/nestri/base:debug
|
||||
make image # + pack into output/rootfs.ext4
|
||||
make image-debug # + pack into output/rootfs-debug.ext4
|
||||
```
|
||||
|
||||
## Design notes
|
||||
|
||||
Three things worth knowing about how this is put together:
|
||||
|
||||
1. **No privileged host chroot.** A bare `chroot` into a hand-extracted
|
||||
rootfs needs `/proc`, `/sys`, `/dev` bind-mounted in first — they don't
|
||||
exist inside a chroot target until something puts them there. `os-base`
|
||||
here is `FROM artixlinux/artixlinux:base-openrc` directly, with
|
||||
`pacman -S` as plain `RUN` steps — a Docker build step already runs
|
||||
inside a real container with its own `/proc`, `/sys`, `/dev`, so that
|
||||
whole bind-mount mechanism has nothing to do.
|
||||
|
||||
2. **No host-side ownership bug to guard against.** `COPY --from=` runs as
|
||||
root inside the build with no host user in the loop, so there's no
|
||||
invoking-user uid getting stamped onto `/`, `/usr/bin`, or anywhere else
|
||||
a build step touches — a failure mode some overlay approaches need an
|
||||
explicit sanity check for doesn't exist here to check for.
|
||||
|
||||
3. **One `cargo build --release --workspace`, not one stage per binary.**
|
||||
`nescope`, `neshub`, `neswire` and `nescapture` share one Cargo workspace
|
||||
and one `Cargo.lock` — a BuildKit cache mount on `target/` gives cargo's
|
||||
own incremental compiler per-crate isolation without needing a separate
|
||||
Docker stage (and a separate full rebuild of `nesprotocol`) per binary.
|
||||
|
||||
**What is deliberately not here: Proton, and Valve's `steamclient.so`.**
|
||||
`nestri/CLAUDE.md` is explicit — *"Nothing closed may enter this repo. Not
|
||||
source, not a dependency, not a directory that 'looked convenient'."* Both
|
||||
are closed. `runtime_prod` from this Dockerfile — tagged
|
||||
`ghcr.io/nestrilabs/nestri/base:latest` — is a complete, bootable, Steam-less guest image,
|
||||
and also the shared foundation other builds start from: nesbox's jailer
|
||||
image (see `nesbox/build/`) extracts Mesa and virglrenderer from it so the
|
||||
guest and host sides of the virtio-gpu native-context protocol never drift
|
||||
apart. Whatever layers Proton and the Steam client on top of it is a closed
|
||||
build outside this repo, by design — not something this repo names, links
|
||||
to, or depends on.
|
||||
|
||||
## The nesinit gap
|
||||
|
||||
Nothing in this image starts a payload. The old `nestri-guest-hub` did that
|
||||
— per its own commit message, the open `neshub` *"loses `--proton`,
|
||||
`--steamclient-so`, `--root` and the game uid/gid, and no longer ends by
|
||||
handing the process to a controller... Deciding when the box is finished
|
||||
belongs to `nesinit`."* `nesinit` — the guest init/session-supervisor that
|
||||
would actually launch `nescope -- <payload>` and power the box down — is
|
||||
referenced in commit messages and `nesbox/PROGRESS.md` but does not exist as
|
||||
open code in either repo.
|
||||
|
||||
So `/etc/init.d/nescope` here starts nescope in **plain-compositor mode**
|
||||
(no command after `--`): it comes up, provides the Wayland/X11 environment,
|
||||
and waits for something to connect. That makes the image genuinely bootable
|
||||
and testable — `neshub`, `neswire`, `nescope` all come up under OpenRC and
|
||||
you get a real Wayland socket to point a client at — but running an actual
|
||||
game is still nesinit's job, and nesinit isn't part of this build. Whoever
|
||||
picks that up next should read `apps/neshub/README.md`'s "What it does not
|
||||
do" section first.
|
||||
|
||||
## Network defaults
|
||||
|
||||
`/etc/init.d/guest-net` reads `nestri.ip=`/`nestri.gw=` off the kernel
|
||||
command line, falling back to `172.30.0.2/24` via `172.30.0.1` if neither is
|
||||
set — nesbox's own default tap addressing. Keep these in step if that
|
||||
changes on the nesbox side.
|
||||
Reference in New Issue
Block a user