Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[0.12.4] - 2026-06-11

  • yoe skills install adds yoe’s AI skills to your project. The skills that power /new-unit, /diagnose, /audit-unit, and friends now ship inside the yoe binary; run yoe skills install to drop editable copies into your project’s .claude/skills so Claude Code picks them up. They’re yours to edit, and yoe skills update refreshes them to the latest versions when you upgrade yoe. New projects from yoe init get them installed automatically.
  • The skills are also available as a Claude Code plugin. If you’d rather Claude Code manage updates for you, add the marketplace with /plugin marketplace add yoebuild/yoe and install yoe@yoe — the same skills, delivered the standard plugin way.
  • New projects ignore the right files out of the box. yoe init now writes a .gitignore that also covers the local apk repository (repo/) and your per-developer local.star, so a fresh project stays clean in Git without you curating ignores by hand.

[0.12.3] - 2026-06-09

  • Ubuntu arm64 images boot under software emulation too. When QEMU has no KVM acceleration (an x86 host, or an arm64 host without /dev/kvm), Ubuntu’s EFI-only arm64 kernel now boots instead of aborting QEMU, so the boot test and yoe run work the same with or without hardware acceleration.

[0.12.2] - 2026-06-09

  • Ubuntu arm64 images now reach a login prompt in QEMU. Ubuntu’s arm64 kernel needs UEFI firmware to boot, which the QEMU launcher now supplies automatically (from the host’s qemu-efi-aarch64 / edk2-aarch64 package) instead of hanging with a blank console; if the firmware isn’t installed, the run now fails with a clear message naming the package to install.

[0.12.1] - 2026-06-08

  • Ubuntu arm64 images now boot in QEMU. The launcher no longer trips over an unused initramfs reference Ubuntu leaves behind, so arm64 Ubuntu images start cleanly instead of failing with “could not load initrd.”

[0.12.0] - 2026-06-05

Ubuntu support is ready to use in this version.

  • Building Ubuntu images no longer runs the machine out of memory. Large Ubuntu builds could grow to tens of gigabytes and get killed; they now stay flat at a few hundred megabytes and complete reliably.
  • Fresh Debian and Ubuntu image builds are faster. The package index is now built once when it is needed instead of being rebuilt after every package, so build time no longer grows sharply with the number of packages.
  • The attr library builds reliably across architectures. It no longer intermittently fails to build from a build-system timestamp race that showed up on arm64.

[0.11.7] - 2026-06-04

  • Ubuntu images now bring up the wired network automatically. NetworkManager manages and DHCPs the ethernet port out of the box, so the image is reachable over SSH on first boot with no connection profile — matching Debian.
  • Passwordless root SSH login now works on Ubuntu dev images. Root could log in on the serial console but SSH rejected the empty password; Ubuntu images now get the same permissive dev-login drop-in Debian already had.
  • Ubuntu images build correctly in projects that also use Debian. The two distros’ build toolchains no longer share an identity, which previously could crash an Ubuntu image build; each distro now builds with its own toolchain.

[0.11.6] - 2026-06-04

  • The unit list now shows cached status the moment yoe opens. Cached units no longer render as blank lines until the first build; their status is correct on startup.
  • Ubuntu packages now reuse the cache instead of rebuilding every run. Building an Ubuntu image no longer re-fetches and re-extracts every package on each yoe build; unchanged packages are reused from the cache, the same as Debian and Alpine.
  • ncurses now builds on Ubuntu. The terminal library no longer fails to compile under Ubuntu’s newer toolchain, so Ubuntu images that pull in ncurses build successfully.
  • bash now builds on Ubuntu. The shell no longer fails to compile under Ubuntu’s newer toolchain, so Ubuntu images that pull in bash build successfully.

[0.11.5] - 2026-06-04

  • Ubuntu is now a selectable distro alongside Alpine and Debian. Point a project at module-ubuntu and set the distro to ubuntu (per image, or via the default-distro override) to build images from Ubuntu’s package archive. Ubuntu and Debian images can live side by side in one project without their packages colliding. (Still debugging, not ready for use).
  • The debian_feed(...) builtin is now apt_feed(...) and takes a distro. One builtin serves every apt-based distro; pass distro = "debian" or distro = "ubuntu". It also accepts an optional arch_urls map so a single feed can pull amd64 from one mirror and arm64 from another — needed for Ubuntu, whose ports architectures live on a separate host.
  • Build failures now name the unit and task that broke. When a build fails, the output leads with a clear ❌ FAILED: <unit> task: <task> line before the log, so you can tell at a glance which unit failed even when several are building in parallel.
  • Build status lines now carry icons for quick scanning. Each unit reports with an at-a-glance marker — ⚡ cached, 🔨 building, ✅ done, ❌ failed — and freshly packaged artifacts are flagged with 📦.
  • Fixed source units failing to build in projects that mix two distros. When a project combined distros that share image names (such as Debian and Ubuntu, which both ship dev-image), a source unit’s build-time -dev dependencies could be silently dropped for one of the distros, leaving it to build against an empty sysroot and fail (for example, “zlib support requested but not found”). Both distros’ build dependencies now resolve correctly.
  • Boot smoke test output now carries status emojis. A --boot-test run flags each stage at a glance — 🚀 launch, 🔑 reaching the login prompt and connecting over SSH, 🩺 health check, and a final ✅ pass or ❌ fail.

[0.11.4] - 2026-06-04

  • yoe run now boots arm64 images correctly. On arm64 (and other direct-kernel-boot targets) the launcher looked for the kernel at the wrong path, so QEMU started with nothing to boot and sat at a blank console. It now takes the kernel — and, for Debian, the initramfs — straight from the image’s own /boot, so arm64 images boot the same kernel they ship. This also makes yoe run --boot-test pass for arm64 images.

[0.11.3] - 2026-06-04

  • yoe run --boot-test boots an image and verifies it came up. It boots headless, waits for the login prompt, SSHes in to run a health check, then powers off — a one-command pass/fail smoke test for CI or a quick local sanity check. --timeout bounds it; --distro picks which distro’s image to run when the name exists in several.

[0.11.2] - 2026-06-04

  • Build failures now show the unit’s log right where they happen. When a unit fails to build, yoe prints the tail of that unit’s build log inline instead of only pointing at a file on disk, so you can see the actual error without opening anything — and so failures are diagnosable from CI output where the log file is thrown away with the runner.
  • Per-task build lines now name their unit. During parallel builds the task progress lines from different units interleave; each line now leads with the unit name so you can tell which build it belongs to.
  • Units with patches build on machines that have no git identity configured. Applying a unit’s patches no longer depends on a global git user.name/user.email, so builds succeed out of the box on fresh machines and CI runners instead of failing with “empty ident name not allowed.”

[0.11.1] - 2026-06-04

  • Source downloads no longer fail intermittently with “gzip: invalid header.” Some download mirrors serve a .tar.gz in a way that made yoe save an already-decompressed archive under a compressed name, so a build would break or succeed depending on which mirror you happened to reach. Downloads are now fetched verbatim, so a unit that builds once builds every time.

[0.11.0] - 2026-06-03

  • Debian Trixie support. yoe can now build Debian images alongside Alpine. Set defaults.distro = "debian" to target a whole project at Debian, or tag an individual image(...) with distro = "debian", and yoe builds it through the Debian backend — glibc toolchain, .deb packaging, a signed apt repo, and a fully configured, bootable rootfs. base-image and dev-image are available for Debian out of the box.
  • yoe deploy, run, and flash work for Debian targets. Deploy wires the project’s dev feed into apt and installs with apt-get (Alpine still installs over apk); run and flash locate Debian images. apt-get install and apt-get upgrade work offline against your project repo, which apt verifies with the project’s own GPG key. The edit-deploy loop is identical on both distros. (not fully tested yet)
  • yoe build takes a --distro flag. When the same image name exists in more than one distro (say a base-image from both the Alpine and Debian modules), pick which one to build for a single command — yoe build --distro debian base-image — instead of editing local.star.
  • The TUI shows and selects the default distro. The status header displays the active distro, and TUI Setup (press s) has a Default Distro picker that persists to local.star and re-walks the build cache immediately so status reflects the chosen backend.
  • yoe log and yoe diagnose work on feed-based projects again. They no longer fail with an undefined: alpine_feed error and now find the build log for images and other machine-specific units.
  • Repository and build layout are now split by distro. So Alpine and Debian artifacts can coexist, APK repos moved to repo/<project>/alpine/<arch>/, Debian repos live under repo/<project>/debian/, and build output moved to build/<distro>/<unit>.<scope>/. Update any hardcoded repo URLs and /etc/apk/repositories entries; old repo/<project>/<arch>/ and build/<unit>.<scope>/ directories are stranded and can be removed. prefer_modules is now keyed by distro: prefer_modules = {"alpine": {"xz": "alpine.main"}, "debian": {...}} — rewrap any existing flat pins under their distro key.

[0.10.15] - 2026-05-26

  • TUI Setup gained a QEMU settings sub-screen. Press s then Enter on “QEMU settings” to adjust the guest’s RAM with ←/→, toggle the graphical display, and add or remove host:guest port forwards for yoe run. Choices persist to local.star and apply automatically the next time you launch the guest — no need to remember --memory, --display, or --port flags for routine work.
  • The QEMU settings screen shows the equivalent qemu command. A live preview at the bottom of the sub-screen renders the exact qemu-system-* invocation yoe run would emit with the current Memory / Display / Ports values, so you can confirm what each tweak changes before launching — and copy-paste the line to drive QEMU directly.
  • New qt-image boots straight into a Qt 6 Quick demo on the framebuffer. Build with yoe build qt-image and run with yoe run qt-image --display; QEMU opens a window showing the demo scene (a “Hello from yoe!” message rendered through the linuxfb platform plugin and the software scene graph). Useful as a quick end-to-end check that a yoe-built image’s graphical stack works on hardware that ships virtio-gpu, Bochs, or a plain VESA/EFI framebuffer.
  • yoe run --display now actually opens a QEMU window. Previously the flag dropped -nographic but didn’t tell QEMU what to display with; running an image showed only an empty terminal. The launcher now attaches a virtio-vga adapter and keeps the serial console muxed onto host stdio (-serial mon:stdio) so kernel logs stay visible alongside the framebuffer window. Headless yoe run (no --display) is unchanged.
  • The kernel ships framebuffer and DRM drivers for QEMU and common PC GPUs out of the box. linux now merges a graphics.cfg fragment that enables virtio-gpu, Bochs, vesafb, efifb, and DRM fbdev emulation, so every yoe image exposes /dev/fb0 on first boot without per-image configuration.

[0.10.14] - 2026-05-26

  • Modules tab no longer scrolls the title off the top. When a project declared one or more feeds, the FEEDS section pushed the body past the terminal height and you’d lose the title, tab bar, and column header. The viewport now sizes itself to leave room for the FEEDS section.
  • The edit shortcut is hidden for feed-supplied units. Units coming from a feed (e.g. alpine_feed()) have no .star file to open, so the Units tab no longer advertises e edit when the cursor is on one, and pressing e on such a unit is a silent no-op.

[0.10.13] - 2026-05-26

  • alpine_feed() declares a feed directly in a module’s MODULE.star. A module can now expose thousands of upstream Alpine packages as yoe units with a single declaration — point alpine_feed() at a checked-in directory of APKINDEX files and the named packages become available to image artifacts with no per-package .star file. Package units materialize lazily as the image’s runtime closure needs them, so working memory stays bounded by the closure size rather than the catalog size.
  • The Modules tab shows declared feeds. Each alpine_feed() call appears in a FEEDS section under the regular module list with its parent module and the total package count.
  • yoe update-feeds refreshes feed APKINDEX files from upstream. Run inside a module repo, the new subcommand fetches every alpine_feed()’s APKINDEX for every active arch, verifies the upstream RSA signature against the keys the module declared, and writes the new indices to disk for the maintainer to review with git diff and commit. Signature verification is pure-Go and never consults the host’s /etc/apk/keys/ — the trust list the feed declares is the one that’s actually enforced.

[0.10.12] - 2026-05-22

  • CI builds base-image from source on every push to main. A full end-to-end build — bootstrap toolchain, kernel, and image assembly — now runs in CI, so build regressions surface immediately instead of at the next release.
  • yoe run works inside a QEMU guest (qemu-in-qemu). When no /dev/kvm is available, yoe run now falls back to TCG software emulation instead of failing with a KVM error, so you can launch a guest from within a guest. It prints a one-line note that emulation is in use.
  • yoe run --port can remap a machine’s default forwards. A --port entry whose guest port matches a machine forward now replaces it instead of adding a second, colliding one — so a nested yoe run can move its host-side ports off the ones the outer guest already holds.
  • yoe run flags work after the image name. yoe run base-image --port … previously ignored every flag that followed the image name; flags and the image name may now appear in any order.
  • yoe run explains a port conflict instead of failing cryptically. When a QEMU guest is already running, yoe run (and the TUI r key) now report which host port is taken and that an earlier run is probably still up, rather than an opaque exit status 1. Other QEMU launch failures now include the reason QEMU printed.
  • yoe run remembers the QEMU guest memory. Pass --memory 8G once and the value is saved to local.star, so later runs reuse it without the flag. Set it without a run via yoe config set qemu-memory 8G, or on the TUI Setup page with ←/→. Clear it with an empty value to fall back to the machine default.
  • QEMU can now be installed into an image. Adding QEMU pulled in filesystem libraries that conflicted with the bundled e2fsprogs, aborting the image build; that conflict is now resolved.
  • QEMU machines now default to 4 GB RAM. The old 1 GB default was too small for memory-heavy unit builds run inside the guest — a self-hosted yoe build of the Linux kernel was OOM-killed at the link step. Bump the memory field in your machine file if you need more or less.
  • TUI clean (c and C) now works on image units. Previously failed with permission errors on the root-owned files left by image builds; now routes through the same container-side rm that yoe clean uses.
  • selfhost-image. Bootable image that bundles yoe, Go, Docker, git, and the dev-image tool set (tested on QEMU, soon native ARM systems).

[0.10.11] - 2026-05-20

  • beagleplay machine. New target for the TI AM625 BeaglePlay. Build with --machine beagleplay.
  • jukebox-image. Music server image running Navidrome (Subsonic API).
  • Image rootfs preserves per-file ownership from packages. Services like Navidrome and Postgres that need a specific data-dir owner now start cleanly. Side effect: rm -rf build/ fails because some files are root-owned — use yoe clean instead.
  • New “Security and Threat Model” doc. What the build container does and doesn’t protect against. Short version: treat units like curl | sh.
  • Files tab for image units shows rootfs contents, not the whole destdir/. Now matches the unit’s SIZE column.
  • Filtering the Units tab to no matches no longer scrolls the tab bar off the screen.
  • module-rpi renamed to module-bsp. Common boards live in one module.
  • New “Boards” section in the docs. Dedicated pages for BeaglePlay, Raspberry Pi, and QEMU.
  • SD-card images boot on BeaglePlay (and other K3 boards). Previously hung in ROM with no serial output.

[0.10.10] - 2026-05-15

  • Building a prebuilt package no longer fails on a fresh checkout. Building an Alpine prebuilt package (or any image that pulls one in) on a clean tree, or after a toolchain version bump, failed with Unable to find image 'yoe/toolchain-musl:...' / pull access denied because nothing built the toolchain container first. yoe now builds the container automatically before the units that run inside it, the same way it already did for source-built packages.

[0.10.9] - 2026-05-15

  • The TUI home header is easier to scan. Field names (Machine, Image, Query, Units, feed, Modules, Diagnostics) now render in plain white and their values in a bright accent color, so the data stands out from the labels at a glance. The search expression keeps its own coloring.
  • The TUI tab bar now sits on the top line next to [yoe]. The Machine/Image line and the build progress bar moved down a row, so the tabs are the first thing you see and the layout reads top-to-bottom.
  • Image builds now resolve virtual packages the same way every run. When several packages claimed the same virtual name (for example the providers of ifupdown-any), which one an image picked could change from one build to the next — so a package would drift in and out of the image, never staying cached and sometimes silently dropping out entirely. Resolution is now stable, so the same project always produces the same image and the cache holds.
  • The TUI Setup page can now adjust how many units build in parallel. A “Parallel builds” row sits below Machine and Image; press ←/→ (or h/l) to raise or lower the count. The choice is saved per project and used by the next build.
  • yoe build now builds independent units in parallel. Units whose dependencies are ready build concurrently instead of one at a time, so a full build finishes much faster. Up to 5 units build at once by default; change it with yoe build -j N, yoe config set parallel-builds N, or a parallel_builds line in local.star. The value is remembered per project, and yoe config show reports the one in effect.
  • u on the unit list toggles a unit’s source between pin and dev mode. The same dev-mode prompt the detail view offers is now one keypress away from the list, acting on the unit under the cursor.
  • g / G jump to the top / bottom of the unit list. The keys were documented and worked on the Modules and Diagnostics tabs but were a no-op on the Units list itself; they now move the cursor there too.
  • Press ? on any TUI page for a keyboard cheat sheet. A centered help box lists every shortcut for the page you’re on — navigation, build, inspect, filter, and the page-specific actions — with plain-language descriptions. When the list is taller than your terminal it scrolls (↑/↓, PgUp/PgDn, g/G) with the title and footer pinned; any other key dismisses it.
  • The TUI tab bar now uses zellij-style ribbon tabs. Each tab is a rounded colored banner sitting on a dark bar, with the active one highlighted in amber so the selected tab is obvious at a glance. Needs a powerline-patched terminal font to render the rounded tab edges.
  • yoe deploy now actually installs your dev-mode rebuilds. Iterating in dev mode and deploying used to silently drop your edits when the version number hadn’t changed; deploy now reliably installs the rebuilt package. Restart the service from $ to pick up the new binary.
  • Dev mode can track an upstream branch automatically. A unit that declares both a tag and a branch flips to tracking that upstream branch when toggled into dev mode, so git pull, git push, and git log @{u}.. just work. The SRC column shows dev-mod when your checkout is past the pin, and the detail page shows how many commits ahead you are.
  • P pins the current HEAD with no picker. Pressing P records the checked-out tag (or commit SHA) as the unit’s pin — no popup. Available in dev and dev-mod; a dirty tree prompts you to commit or stash first.
  • The SRC column shows pin for yoe-managed checkouts instead of leaving the cell blank, so you can tell at a glance which units are pinned.
  • Toggling dev → pin no longer looks like data loss. It resets the existing checkout to the pin in place and keeps the clone’s full history instead of deleting the source tree and re-cloning on the next build.

[0.10.8] - 2026-05-13

  • Fix patch application when the cache path is relative. applyPatches built the patch path relative to the project root (e.g. cache/modules/.../*.patch) but invoked git am with cmd.Dir = srcDir, so git looked for the file inside the source tree and failed with could not open '...patch'. The path is now resolved to absolute before exec. The bug was masked in long-lived build dirs because src/ already had the patches committed and the prep step short-circuits via the “commits beyond upstream” check; fresh builds (or any project with YOE_CACHE unset and modules pulled from cache) hit it.

  • Language runtimes move out of the toolchain container. nodejs, npm, python3, py3-setuptools, and py3-pip are no longer baked into toolchain-musl’s Dockerfile. nodejs_app and python_venv now add the matching apks to deps, so the same Alpine prebuilt the device runs is also what builds the unit. Projects that don’t use Python or Node.js stop paying for them entirely; bumping a runtime version is now a unit edit rather than a Dockerfile change. Matches the bun setup and CLAUDE.md’s “no installing packages in the container” rule. Migration: any unit that invokes python3, node, or npm in its build steps without using the corresponding class now needs the runtime in its deps (meson and ca-certificates are updated in this release as examples). The toolchain-musl container version bumps to 19 so the leaner image rebuilds on first use.

[0.10.7] - 2026-05-12

  • Bun apps can ship as part of an image. A new bun_app class packages a Bun project plus its node_modules tree as a regular yoe unit, so apps that need a specific set of npm packages get them baked into the apk. Bun runs TypeScript natively, so the entry point can be a plain .ts file with no compile step. bun-image ships a bun-hello demo — log in and run bun-hello "..." to see the bundled figlet dependency render an ASCII-art greeting.
  • New bun-image. A ready-to-boot image with the bun runtime (and bunx) plus the dev-image diagnostic userland, so bun install <pkg> and bun run work on first login without a separate apk add.
  • npm dependencies can ship as part of an image. A new nodejs_app class packages a Node.js app plus its node_modules tree as a regular yoe unit, so apps that need a specific set of npm packages get them baked into the apk instead of installed at first boot. Drop a normal package.json (and optionally package-lock.json) next to your unit and the class runs npm install / npm ci against it at build time. nodejs-image ships a nodejs-hello demo — log in and run nodejs-hello "..." to see the bundled figlet dependency render an ASCII-art greeting.
  • New nodejs-image. A ready-to-boot image with node, npm, and the dev-image diagnostic userland, so npm install <pkg> works on first login without a separate apk add.
  • yoe init projects now pin zstd to Alpine. Source-built zstd ships libzstd.so.1 at a different soversion than Alpine’s zstd-libs, so any image that mixes source consumers (curl, file) with Alpine prebuilts that link against Alpine’s libzstd (nodejs, bun) used to fail with an apk conflict over the shared .so. New projects get the pin out of the box; existing projects can copy the "zstd": "alpine" entry into their prefer_modules block.
  • Patches live next to the unit, not under a separate patches/ tree. A unit’s patches = [...] paths are now relative to the unit’s own directory with no patches/ prefix — e.g., patches = ["mdnsd/0001-….patch"] next to mdnsd.star. yoe dev extract writes patches into <unit-dir>/<unit>/ alongside the .star file, so a module’s patches travel with the module that defines them. Migration: drop the leading patches/ from existing patches = [...] entries and move the files to match.
  • yoe dev status works through project errors. It now walks the build directory directly instead of evaluating the project, so unrelated issues like a duplicate-provides collision in a module no longer block you from seeing which units have uncommitted local edits.

[0.10.6] - 2026-05-12

  • TUI help bar stays pinned to the bottom of the screen on the Units tab, even when only a handful of units are visible. The unit list pads with blank rows so the keyboard shortcuts and the cursor-name strip don’t float up under a short query result.
  • yoe module sync works even when modules have errors. The command now reads only PROJECT.star and re-syncs the declared modules, so a broken module that’s blocking the rest of the build can be re-fetched as soon as the upstream fix lands — no more chicken-and-egg.
  • One ctx struct in .star files instead of five separate globals. Unit and image definitions now reference ctx.arch, ctx.machine, ctx.project_version, ctx.machine_config, ctx.provides, and ctx.runtime_deps — what used to be ARCH, MACHINE, PROJECT_VERSION, MACHINE_CONFIG, PROVIDES, and RUNTIME_DEPS. One named entry point is easier to discover and reason about than five floating predeclared names. External modules that referenced the old globals need a one-line rename.
  • Pip dependencies can ship as part of an image. A new python_venv class packages a Python virtualenv plus its pip dependencies as a regular yoe unit, so apps that need a specific set of PyPI packages get them baked into the apk instead of installed at first boot. python-image ships a python-hello demo — log in and run python-hello "..." to see the bundled pyfiglet dependency render an ASCII-art greeting.
  • New python-image. A ready-to-boot image with python3, pip, and the dev-image diagnostic userland, so pip install <pkg> works on first login without a separate apk add.
  • yoe deploy python3 (and other openssl consumers) now installs onto a running device. Previously apk rejected the install with a libssl3>=3.3.0 conflict against the source-built openssl. Source units that declare virtual provides now publish them with this unit’s version, so >= constraints resolve the way they do on Alpine.
  • <hostname>.local now resolves over IPv4. On DHCP networks mdnsd was announcing only the IPv6 link-local address, so ssh user@host.local failed on plain IPv4 LANs. The host’s A record is now published as soon as the lease arrives.

[0.10.5] - 2026-05-09

  • Build progress bar. While a build is in flight the feed banner at the top of the screen swaps out for a live progress bar showing the percentage done, how many units have finished, and how many are still to build. The bar disappears and the feed banner returns once the build settles.

[0.10.4] - 2026-05-08

  • Search bar clears with Ctrl+U. Readline’s kill-line shortcut wipes the query input back to a blank bar in one keystroke — faster than holding Backspace or pressing \ to snap to the saved default. Live-applied like a backspace, so the unit list updates to “showing all” immediately.
  • Tab completions show up under the query bar. When the search input can’t be advanced further (multiple equally-good matches), the candidate list now renders as a vertical column directly under the query bar — closer to the cursor than the previous horizontal blob at the bottom of the screen, and easier to scan for the next character to type. Long lists truncate with a “(N more — type a letter to narrow)” hint.
  • Fresh projects from yoe init build out of the box. The generated PROJECT.star now pins xz to the Alpine module, matching the canonical e2e-project template. Without this, kmod’s depmod failed at image-assembly time because module-core’s xz is static-only and doesn’t ship liblzma.so.5.
  • Switching a unit/module to dev mode transfers far less data. The depth-limited fetch (last 100 / 1000 commits, last year, last month) now narrows to the unit’s pinned ref instead of fanning out across every branch the upstream tracks, and adds --filter=blob:none so file content comes down on demand instead of all at once. For a Linux-kernel-sized repo that’s the difference between a multi-gigabyte fetch and tens of megabytes; git log and git blame still work, and missing blobs are fetched lazily when needed.
  • TUI / makes refining an existing query faster. When you press / and the active query is non-empty, the bar opens with a trailing space so you can immediately type the next term — no need to press End or space first. A blank query still opens empty.
  • Toggle any unit or module between pinned and dev mode from the TUI. A new SRC column on the units and modules tabs surfaces whether each source dir is yoe-managed (pin), tracking upstream (dev), has commits beyond upstream (dev-mod), or has uncommitted edits (dev-dirty). Press u on a unit’s detail page (or a module row) to switch between pin and dev — yoe asks whether to rewrite origin to SSH, then how much history to fetch (full / last 1000 commits / last 100 commits) so the Linux kernel’s full history doesn’t have to come down every time. A spinner runs while the fetch is in flight so you can see something is happening. Once you’re happy with a dev-mod HEAD, P captures it back into the .star pin so other people building the project pick it up. A dev* unit is left untouched at build time, so yoe build won’t overwrite your working tree or undo in-flight changes.
  • TUI size column no longer overflows on big artifacts. Sizes like a 1003 KiB kernel image render as 1003K instead of 1003.4K, keeping the column aligned. The decimal still shows for small values (e.g. 9.9K, 1.2M) where it carries useful precision.
  • Device hostname now matches the machine, not the image. A fleet of raspberrypi4s flashed with dev-image no longer all answer to yoe-dev.local; each board comes up as <machine>.local (e.g. raspberrypi4.local, qemu-x86_64.local) so they’re distinguishable on the LAN out of the box. Set hostname = "..." on an image to override (e.g. a branded kiosk image).
  • TUI help bar reflects the active mode. While typing in the search bar, the bottom help row swaps to the keys that actually work there (type filter, tab complete, ⌫ delete, enter apply, esc cancel) instead of pretending b build, q quit, etc. still fire. Out of search-edit it shows the navigation shortcuts as before.
  • Tab in the search bar always shows progress. When Tab can’t advance the input (multiple candidates with no common prefix to extend — most visibly when you’ve just opened the bar, or typed a single ambiguous letter), the candidate list now flashes in place of the help bar instead of silently doing nothing. Single-candidate completions still splice in. Empty pool flashes “no completions”.

[0.10.3] - 2026-05-07

  • docker-image starts dockerd at boot. Pulls in Alpine’s docker-openrc package (which ships /etc/init.d/docker and the /etc/conf.d/docker config template upstream maintains) and adds the default-runlevel symlink at packaging time, so dockerd is supervised on a fresh boot without manual rc-update add.
  • prefer_modules on project() pins a unit to a specific module. Set prefer_modules = {"xz": "alpine"} in PROJECT.star and the xz unit registers only from module-alpine, regardless of which module wins the default last-module shadowing. Use it when module-core’s source-built version of a package is broken or under-configured and the Alpine prebuilt is the right answer; the shadow appears on the Diagnostics tab the same way an ordinary cross-module shadow does.
  • modprobe works on the booted system. Image assembly now runs depmod inside the rootfs after apk add, so /lib/modules/<ver>/ carries a real modules.dep index instead of just bare .ko files. The kernel build still skips depmod (the toolchain container has no copy of it); the rootfs’s own kmod supplies it via chroot.
  • Kernel ships container-runtime CONFIG by default. A container.cfg fragment (overlayfs, bridge/veth, the full netfilter chain including NFT_COMPAT so iptables-nft works, IPv4 + IPv6 NAT, namespaces, seccomp, cgroup BPF, eBPF) is merged into the kernel’s defconfig during the build of the upstream linux unit and the Raspberry Pi linux-rpi4 / linux-rpi5 units, so dockerd and containerd start cleanly on every supported board without per-image kernel customisation. The cost on non-container images is a few hundred KB of kernel modules that nothing references.
  • TUI flash remembers the last device. Picking and confirming a flash target writes flash_device = "/dev/sdX" to local.star, and re-entering the flash view positions the cursor on that device when it shows up in the candidate list. Reflashing the same SD card or USB stick is now f → Enter → y.
  • /etc/os-release now reports the project version. VERSION, VERSION_ID, and PRETTY_NAME come from version = "..." in PROJECT.star, so tools that read /etc/os-release (and humans on the device) can tell which build is running. Templates can reach the value as {{.project_version}}.
  • Image rows in the TUI show the project version. The image() class defaults each image unit’s version to PROJECT_VERSION (from PROJECT.star), so the VERSION column in the units table — which used to be blank for image rows — now shows the version the resulting .img represents.
  • OpenRC replaces the old rcS startup script. Services now boot under Alpine’s OpenRC service manager, so they get dependency ordering, supervised start/stop, and proper status/restart commands (rc-service sshd restart, rc-status) instead of the silent run-everything-in-/etc/init.d/S* pattern. Units declare services = ["sshd"] (plain names, no S40 prefix) and the resulting apk drops the script in /etc/init.d/ plus a runlevel symlink in /etc/runlevels/default/.
  • Source-built libraries auto-declare what they ship. Each .apk yoe builds from a destdir now lists provides = so:<soname>=<ver>-r<rel> for every shared library in the package, matching Alpine’s convention. Alpine prebuilt packages whose upstream PKGINFO declares depend = so:libcrypto.so.3 or similar now resolve cleanly against yoe-source-built openssl, zlib, etc. — no manual SONAME bookkeeping in the .star file.
  • module-alpine packages now ship with their upstream metadata intact. Prebuilt Alpine apks pass through yoe’s pipeline verbatim — only the signature is swapped for the project’s key — so replaces, provides, triggers, and post-install hooks (busybox applet symlink creation, sshd privsep user adds, …) reach the on-target system the way Alpine intended. Image assembly drops --no-scripts so those hooks actually run; this fixes the no-/sbin/init kernel panic that hit when relying on Alpine’s busybox.
  • Alpine packages no longer end up with doubled--r filenames. alpine_pkg splits upstream pkgver like 1.2.5-r11 into yoe’s separate version + release fields, so the published apk is musl-1.2.5-r11.apk instead of musl-1.2.5-r11-r0.apk. Apk’s solver finds the file at the URL it constructs from the index, fixing “package mentioned in index not found” on every module-alpine package.
  • noarch passthrough packages route correctly across the repo. apk-tools constructs fetch URLs from PKGINFO’s arch = field (always <base>/noarch/<file> for noarch packages), but its solver only reads one arch’s APKINDEX per repo. Three coordinated fixes:
    • Passthrough alpine_pkg units with arch = noarch publish under <repo>/noarch/ (where apk fetches them from).
    • Each per-arch APKINDEX now scans the sibling noarch/ tree at generation time, so the solver sees noarch packages from any arch’s perspective.
    • A noarch publish refreshes every per-arch APKINDEX (since each one references those noarch entries).
    • Cache validation also looks under noarch/ for the published apk, so noarch units don’t rebuild on every invocation.
  • base-files ships an Alpine-style runlevel baseline. OpenRC services cgroups, devfs, dmesg (sysinit), bootmisc, hostname, modules, sysctl (boot), and mount-ro, killprocs (shutdown) are now wired into the rootfs via /etc/runlevels/<level>/<svc> symlinks, so a fresh image boots with the hostname set, kernel modules loaded, the cgroup hierarchy mounted (so container runtimes don’t trip on “Devices cgroup isn’t mounted”), and shutdown that unmounts cleanly.
  • TUI SIZE column for images shows installed content, not partition size. An image whose machine reserves a 600 MB rootfs partition now reports the ~50 MB actually populated by apk add, so you can see what your image contains rather than how big the partition was sized.
  • New docker-image. Builds a dev-image-style rootfs that also ships Docker (engine, CLI, buildx, containerd, runc) so you can poke at the docker userspace on a yoe-built system. Kernel and init still need the container pieces before dockerd can actually launch a container — that’s the next step.
  • Files tab on the unit detail page. Tab into a sortable list of every file the unit installs and its on-disk size — easy to spot the biggest payloads or confirm a binary actually landed where you expected without leaving the TUI.
  • Drop into a shell on the source. Press $ in the units tab or detail page to open a shell in the unit’s checked-out source directory, or in the Modules tab to open a shell in a module’s clone — handy for git status, spot-edits, or running an out-of-tree command without leaving the TUI.
  • VERSION column in the unit table. Each row now shows the unit’s declared version next to its module, sortable from the o cycle, and the same version appears next to the unit name on the detail page — so spotting a stale pin or confirming what’s about to build is a glance, not a file open.
  • TUI auto-follow no longer yanks the cursor mid-navigation. The units list scrolls to whatever is actively building only when you’re idle — pressing j/k or typing a query keeps the cursor where you put it, while b still hands control back to the build so you can watch what’s compiling.

[0.10.2] - 2026-05-05

  • yoe init lists module-core last so it wins shadowing. New projects now order modules so module-core’s source-built units (busybox, openssl, …) take precedence over Alpine prebuilts and over module-rpi, avoiding image-assembly path collisions. Existing projects can move module-core to the end of their modules = [...] list in PROJECT.star to get the same behavior.
  • Images with network-config and busybox build again. A path collision on /usr/share/udhcpc/default.script (busybox ships an example script there; network-config installs the real one) was aborting apk add at image-assembly time.

[0.10.1] - 2026-05-05

  • TUI flash offers sudo chown on permission denied. Previously the flash view just showed “permission denied” and dead-ended — matching the CLI’s behavior, the TUI now prompts to run sudo chown $USER /dev/... and retries the write automatically.
  • TUI home screen has tabs. Press tab to cycle between Units (the existing list), Modules (declared modules with git status), and Diagnostics (shadowed units and duplicate provides). The diagnostics tab carries a count badge so issues are visible from any tab.
  • --allow-duplicate-provides is on by default. No more passing the flag on every yoe invocation while the linux-firmware-* fan-out keeps tripping the strict check.
  • Modules renamed: units-*module-*. units-core, units-rpi, units-alpine, and units-jetson are now module-core, module-rpi, module-alpine, and module-jetson. Update module(...) URLs and any path = "modules/units-..." entries in your PROJECT.star.
  • helix actually runs on the device. Was previously bundled as a glibc-linked binary that failed silently with hx: not found; now uses Alpine’s musl build.
  • Images that include apk-tools or libcurl build again. A collision between the source-built ca-certificates and Alpine’s ca-certificates-bundle was aborting apk add at image-assembly time.
  • SIZE column in the TUI updates as each unit finishes. No more waiting for the whole image to complete before transitive deps show their size, and partial sizes survive a mid-build failure.
  • Modules show their declared name. The TUI’s MODULE column and any diagnostic that names a module now use the name set in MODULE.star’s module_info(name = ...) instead of the path basename — so a module referenced via path = "modules/units-core" displays as core if that’s what it calls itself. Falls back to the path basename when no module_info is declared.
  • dev-image ships helix instead of vim. Drops the editor entry that was unintentionally resolving to Alpine’s gvim (and its X11/GTK runtime closure), keeping the image lean.
  • Unit detail shows what uses it and what it pulls in. The detail page now opens with two new sections above the build log: USED BY traces back through runtime_deps to show which packages you wrote in image() pulled this unit in, e.g. dev-image → yazi → libpango → cairo, so you can answer “why is this on my device?” at a glance. PULLS IN shows the unit’s runtime-deps as a tree. Drilling into an image starts from exactly the packages you wrote in image() (plus machine packages), then expands each one to show what it drags in transitively.
  • TUI layout overhaul. Title and banners stay put when the list is long, the help bar is always the last line, status messages flash in its place, and pressing / turns the Query: header itself into the search input. Long unit names get an ellipsis instead of breaking column alignment.
  • Sort columns from the keyboard. Press o to cycle the unit table through NAME → CLASS → MODULE → SIZE → DEPS → STATUS; O flips direction. The active column shows or next to its label.
  • Help bar highlights shortcut keys. Each shortcut letter renders in amber matching [yoe], so you can scan keys without reading every word.
  • Cursor follows the work. The TUI opens with the cursor on the default image, jumps to whatever unit is currently building, and the cursor’s full unit name is always visible just above the help bar.
  • Configure the default image per developer. local.star accepts image = "..." to override defaults.image. Pick an image from the new Image entry in Setup (s) and the choice is saved — and the active search re-anchors to in:<image>. Flows through yoe run, yoe config show, and the TUI.
  • More columns in the unit table. Each row now also shows the module that owns the unit (after shadow resolution), its install size after build (.img size for images), and how many units it pulls into a runtime closure — so bloat is easy to spot before flashing.
  • yoe --help works and lists global options. --help, -h, and help all print usage, including --project, --show-shadows, and --allow-duplicate-provides.

[0.10.0] - 2026-05-05

Errata: due to an issue in the alpine module, you must currently run with: yoe --allow-duplicate-provides.

  • BREAKING CHANGE This project has been moved to a new Github org: https://github.com/yoebuild. yoe update from previous versions will not work and you will need to download and manually install the 0.10.0 binary.
  • TUI search is now a query language; defaults to your image’s working set. Press / to filter by type:, module:, status:, or in: (closure of any unit), in addition to plain substring search. Tab completes field names and values. The TUI starts filtered to in:<your-default-image>, so a project with thousands of units shows just what your image needs. Press S to save the current query to local.star as the new default; press \ to snap back to it. The header shows Query: … Units: N/M so you always know how many of the project’s units the current filter is showing.
  • Use apk-tools from alpine layer for now. It is built with docs.
  • yoe repo clean drops stale .apk files. Removes any .apk in the project’s local repo whose name+version no longer matches a current unit (unit deleted, version bumped, release suffix changed) and re-signs the regenerated APKINDEX. Without this, apk add happily picks the highest- versioned candidate even when that candidate is leftover from a since-deleted unit — which is how a LUA=no-built apk-tools (“apk has been built without help”) could keep winning over Alpine’s prebuilt long after the source unit was removed.
  • Source-built openssl no longer collides with Alpine’s libcrypto3 / libssl3. The openssl unit in units-core now declares provides = ["libcrypto3", "libssl3"], so any package whose runtime_deps reach libcrypto3 or libssl3 (e.g. units-alpine’s apk-tools) routes back to the source-built openssl instead of pulling Alpine’s split libcrypto3 /libssl3 packages alongside. Without this, image-time apk add aborted with trying to overwrite usr/lib/libcrypto.so.3 owned by openssl-3.4.1-r0.
  • units-alpine now lives in its own repo. yoe init and the e2e project pull units-alpine and units-jetson from github.com/yoebuild/ instead of carrying units-alpine inside this repo. Existing projects with path = "modules/units-alpine" should switch to a remote module(...) ref.
  • Shadow notices are off by default. Cross-module unit shadowing and provides overrides no longer print a stderr notice on every load. Pass --show-shadows to see them when you actually want to audit which module won.
  • --allow-duplicate-provides lets multiple units share a virtual. When set, units in the same module may declare the same provides (apk-style “any of these satisfies”); the first one wins for PROVIDES lookup. Needed for units-alpine’s linux-firmware-* fan-out, where ~100 packages all provide linux-firmware-any.
  • patches= resolves relative to the unit’s own .star file directory. A unit can now ship its patches alongside its definition (e.g. units/bsp/foo/patches/0001-fix.patch next to units/bsp/foo.star), and the same patches=["patches/foo/0001-fix.patch"] works whether the unit is loaded from a local module override or a fetched remote module. Previously patches were resolved against the project root, which meant module-shipped patches couldn’t be found unless every consumer copied them.

[0.9.1] - 2026-05-01

  • yoe deploy <unit> now installs the package’s runtime deps too. Previously it only built and published the named unit, so deploying a package with runtime_deps outside what the device already had on disk failed with a cryptic apk add error like sqlite (no such package). Deploy now walks the full runtime closure (the same expansion image() does at image-build time), so every transitive dep ends up in the feed before apk add runs.
  • Deploy refreshes the device’s apk index every time. The on-device apk update step now uses apk --no-cache update, forcing a refetch of every repo’s APKINDEX instead of trusting whatever is in /var/cache/apk/. apk-tools 2.x can otherwise hold onto a stale index across a yoe-dev rebuild and silently miss packages you just published.
  • Added sqlite unit

[0.9.0] - 2026-05-01

  • New design doc on libc and init choice. docs/libc-and-init.md lays out why yoe is musl + OpenRC + Alpine today, where that stack works (gateways, IoT, networking gear), where it doesn’t (Jetson, vendor BSPs, Adaptive AUTOSAR), and the planned rootfs-base abstraction that would let a single yoe codebase serve both Alpine and Ubuntu/L4T projects. Establishes the invariant that yoe stays apk-native on every target — Debian-derived bases get a deb_pkg conversion class, not dpkg/apt on the device.
  • Pull packages straight from Alpine. A new units-alpine module wraps prebuilt Alpine .apk files as yoe units via the alpine_pkg() class — no source build, no patches, just fetch + verify + repack. musl and sqlite-libs ship today; add more by pinning a version and sha256.
  • musl now comes from Alpine. The hand-rolled musl unit that copied the dynamic linker out of the build container is gone; musl is now an Alpine apk wrapped by alpine_pkg(). Output is byte-identical to the Alpine package other projects already ship.
  • .apk URLs work as a source type. Yoe’s source workspace now recognises .apk extensions and bare-copies them so the unit’s install task can extract the multi-stream gzip with GNU tar. Bare-copied sources also keep their URL filename, so install steps can reference the file by name instead of by cache hash.
  • Override an upstream unit by name. Define a unit with the same name in a higher-priority module (or in the project itself) and it shadows the upstream one — no provides boilerplate needed. The project root beats every module, and later modules beat earlier ones. A notice on stderr tells you which one won.
  • Deploy from the TUI. Press D on a non-image unit to deploy it to a running yoe device — host prompt is pre-filled from the last-used target, build + ssh + apk add output stream into the view, and the host is saved back to local.star on success.
  • Deploy actually updates the device’s apk index. yoe deploy and yoe device repo add previously wrote to /etc/apk/repositories.d/yoe-dev.list, which apk-tools 2.x ignores. They now append a marker block to /etc/apk/repositories so the next apk update actually fetches the dev feed and apk add <unit> finds the freshly built package.
  • TUI starts a feed automatically. When you launch yoe, it brings up the project’s apk feed (or reuses one already running on the LAN), so devices configured with yoe device repo add can pull packages without any extra setup. Status is shown in the header.
  • SSH target shorthand. yoe deploy and yoe device repo {add,remove,list} accept [user@]host[:port] — e.g. yoe device repo add localhost:2222 for a QEMU vm or yoe deploy myapp pi@dev-pi.local:2200. The --ssh-port flag is gone.
  • APK live deployment tooling. yoe deploy <unit> <host> builds and installs a unit on a running yoe device with full apk dependency resolution. Pair with yoe serve and yoe device repo add to keep a device pointed at your dev feed for ad-hoc apk add from the device. See docs/feed-server.md.

[0.8.6] - 2026-04-30

  • Container runtime build path documented. docs/containers.md now walks through what it takes to ship Docker, containerd, and runc on a musl yoe rootfs — why prebuilt “static” binaries don’t work, the per-component build breakdown, and how cgo units like runc plug into yoe’s existing Go toolchain and toolchain-musl container via deps instead of needing a new Go+GCC container image.
  • Rename debug units to dev.
  • Expand roadmap. Reorganized as a pointer index into the design docs, with new sections for the app-developer build/deploy loop, hardware access, testing, self-hosting, and distribution variants.
  • New testing design doc at docs/testing.md covers the planned yoe test driver, build-time package QA, on-device upstream tests (Yocto ptest analog), image smoke tests, and CI integration.
  • Kernel modules now ship in images — the linux, linux-rpi4, and linux-rpi5 units previously built only the in-tree kernel image, so drivers compiled as loadable modules (Wi-Fi, USB, sound, many filesystems) were silently dropped. Modules are now built and installed to /lib/modules/<kver>/ in the rootfs, so modprobe finds them at runtime.
  • Fix rPI4 builds package arch did not match what apk was expecting.

[0.8.5] - 2026-04-30

  • `Yazi, Zellij, and Go units added.
  • Clear error when an image’s rootfs won’t fit the partition. Yoe points at the partition size to bump instead of failing mid-mkfs.ext4 with a cryptic ext2 error.
  • SSH works out of the box on dev-image. sshd starts on boot with per-device host keys; ssh -p 2222 user@localhost (password password) just works, and passwordless root SSH matches the serial console.
  • Image rebuilds recover from prior failed builds. A previous failure no longer wedges the next run on “Permission denied” — yoe reports the real error and cleans up automatically.
  • New binary class for prebuilt binaries. Units can ship upstream release binaries with SHA256 verification, no rebuild from source. Used by go, helix, and yazi.
  • apk add works against the signed repo. Image-time and on-target apk commands no longer fail with “BAD signature” or need --allow-untrusted / --keys-dir.
  • apk add and apk upgrade work on yoe-built devices. dev-image ships apk-tools and the project’s signing key, so OTA-style updates use stock apk commands. See docs/on-device-apk.md.
  • Signed apks and APKINDEX. Every artifact is RSA-signed at build time and verified by stock apk on the target. yoe key generate / yoe key info manage the project key; see docs/signing.md.
  • Rootfs builds with APK. Much faster.
  • provides is now a list. Use provides = ["a", "b"]; the string form provides = "x" no longer parses.
  • replaces is documented. New “Shadow files” section in docs/naming-and-resolution.md covers when to use it and how to read apk’s “trying to overwrite” errors.
  • “One .apk per unit” principle, documented. Image-to-image variation belongs at runtime, not in build-flag forks. See docs/naming-and-resolution.md.
  • SSH configured to autostart and work with blank passwords for dev builds.

[0.8.4] - 2026-04-29

  • Networking picks the better DHCP client when available. The default S10network runs dhcpcd if it’s on PATH (IPv6 SLAAC, DHCPv6, IPv4LL fallback) and falls back to busybox udhcpc otherwise — so an image that ships dhcpcd gets the modern client without changing the init script.
  • File conflicts in image builds now fail loudly. Units can declare replaces = ["pkg", ...] to opt into shadowing another package’s files (e.g. util-linux over busybox’s /bin/dmesg); apk honors that at install time and rejects any conflict that wasn’t declared. Image assembly no longer passes --force-overwrite, so a new shadow becomes a real error instead of a buried warning.
  • Unit edits no longer get masked by stale cache hits. Editing a unit’s description, license, runtime deps, replaces, conffiles, build environment, scope, image partitions, image excludes, or install-step files now invalidates the cache as it should — previously these silently kept the old apk. A new test in internal/resolve fails if a future Unit field is added without being incorporated into the cache key.
  • ip works again on dev-image. iproute2 no longer pulls in libelf at link time, so /sbin/ip runs without “Error relocating /sbin/ip: elf_getdata: symbol not found” on images that don’t ship elfutils.
  • Boot no longer hangs when DHCP fails. The default network init script waits briefly for the link to come up before starting udhcpc, runs udhcpc in the background, and limits its retries — so dev-image reaches a login shell even when no DHCP server is reachable, instead of looping on “Network is down”.
  • Image rootfs is assembled by upstream apk add. yoe no longer loops tar xzf over each apk; image builds run apk add against the project’s local repo, getting real dependency resolution, file-conflict detection, and an installed-package database in /lib/apk/db for free. On-target you can now apk info, apk verify, and (once apk-tools ships as a unit) apk add and apk upgrade against the same repo.
  • Service symlinks ship inside the apk. A unit’s services = [...] declaration is materialized as real /etc/init.d/SXX<name> symlinks inside the package’s data tar at build time. On-target apk add <pkg> produces the same rootfs as image-time assembly — yoe never patches the rootfs after install.
  • Repo layout switched to Alpine-nativerepo/<project>/<arch>/<pkg>-<ver>-r<N>.apk plus a per-arch APKINDEX.tar.gz. .apk filenames no longer carry a scope suffix. Existing repo/ directories are obsolete; the next build repopulates the new layout.
  • Yoe-built apks install with upstream Alpine apk-tools. .apk files and APKINDEX produced by yoe now round-trip through stock apk add --allow-untrusted: no checksum errors, no format warnings, and package metadata (name, version, arch, deps, origin, commit, install size) matches what apk index itself would emit.
  • Nine new units in dev-imagee2fsprogs (mkfs.ext4 / fsck.ext4 / tune2fs on the target), eudev (full udev for dynamic /dev), iproute2 (full ip/tc), dhcpcd (a DHCP client beyond busybox udhcpc), bash, less, file, procps-ng (real ps/top/free/vmstat), and htop are now built and included in dev-image so they’re available out of the box on a booted dev system. gperf is also added as a build-time dependency for eudev.
  • Updated units roadmaputil-linux, kmod, and ca-certificates are marked done; dropbear is dropped (the project standardizes on openssh); remaining work is now nftables (blocked on libmnl/libnftnl/gmp deps) and dbus.
  • Documented when NOT to use providesdocs/naming-and-resolution.md now spells out that provides is for leaf artifacts only (kernel, base-files, init, bootloader). Using it for build-time libraries or runtime alternatives forks every transitive consumer into a per-machine apk. Runtime alternatives like mdev vs eudev should ship side-by-side and be selected at boot from init scripts.
  • Image rootfs assembly now warns on path collisions — when two packages install to the same path (e.g., busybox’s /sbin/ip symlink vs iproute2’s full binary), the later package silently overwrote the earlier one with no trace. Image assembly now emits a warning: line per collision naming the surviving package and the shadowed ones, plus a total count. The warnings appear in the image’s build.log (and on terminal when yoe build -v is used). Existing dev-image builds surface 27 expected shadows of busybox applets by full alternatives — no behavior change, just visibility.

[0.8.3] - 2026-04-28

  • mDNS via new mdnsd unit — the dev-image now answers <hostname>.local on the LAN, so ssh user@yoe-dev.local works without knowing the device’s IP. Uses troglobit/mdnsd (a small dbus-free mDNS responder) and ships a default _ssh._tcp service record so the host A record is advertised and SSH discovery works for Bonjour-aware tools.
  • NTP at boot via new ntp-client unit — boards without a battery- backed RTC (e.g., Raspberry Pi) booted at 1970, which broke TLS with “certificate is not yet valid”. ntp-client does a blocking initial sync at S20 (retried a few times to cover DNS settling right after udhcpc) so subsequent services start with real time, then leaves a busybox ntpd daemon running to discipline drift over uptime. Added to dev-image by default. base-files also gets /var/run so daemons that write a pidfile have a place to put it.
  • Fix simpleiot failing to start at boot — the unit installed the binary as /usr/bin/simpleiot but its init script invoked /usr/bin/siot, so booting the dev image showed siot: not found and the service never ran. The binary now installs as siot to match upstream. go_binary gains a binary kwarg for cases where the installed command name should differ from the apk package name.
  • Per-developer machine override via local.star — when you switch machines from the TUI’s setup view, yoe now writes local.star at the project root with your selection. Subsequent yoe commands use that machine without you re-passing --machine every time. The file is gitignored so each developer can pin their own target. --machine on the command line still wins.
  • yoe flash list and TUI device pickeryoe flash list enumerates removable USB sticks and SD cards (filtered against the disk hosting the running system). In the TUI, pressing f on an image unit opens a device picker with a live progress bar during the write. yoe never invokes sudo itself; if the device isn’t writable, it prompts once for consent and runs sudo chown <you> /dev/....
  • Honest flash progressyoe flash now opens the target device with O_DIRECT so writes bypass the kernel page cache and the progress bar tracks actual device throughput. Previously the bar could hit 100% with hundreds of MB still buffered in RAM, freezing the UI for tens of seconds during the final flush. With O_DIRECT the wait is paid out across the write itself, and “Flash complete” appears when the data is really on the card.
  • Fix yoe flash rejecting non-system disksflash previously refused to write to /dev/sda, /dev/nvme0n1, and /dev/vda regardless of the actual layout. It now detects which disk hosts the running system (/, /boot, /boot/efi, /usr) and refuses only that disk, so flashing to a USB or external SATA drive named /dev/sda works on machines whose root is on NVMe.
  • Fix images silently shipping without packages — if an artifact’s apk was missing from the local repo (e.g., its build was cancelled), the image used to build anyway with a warning: package X not found, skipping and produce a kernel-panicking rootfs. Image assembly now hard-fails with a clear message naming the missing package. The build cache now also treats a unit as out-of-date when its apk has gone missing, and rebuilding any unit invalidates its dependents — so reruns auto-recover instead of reusing stale outputs.

[0.8.2] - 2026-04-24

  • Fix extlinux install under Docker 29--privileged containers no longer auto-populate /dev/loop*, so losetup --find failed during image assembly. Pre-create /dev/loop0..31 with mknod before calling losetup.

[0.8.1] - 2026-04-24

  • Fix rootfs ownership on booted systems — files under /, /bin, /etc, /usr, etc. are now owned by root:root on the booted system instead of showing up as whatever user built the project.
  • Compare rootfs ownership handling across projectsdocs/comparisons.md now has a section explaining how Alpine, Debian, Buildroot, Yocto, and NixOS handle root ownership during image builds, and where [yoe] fits.

[0.8.0] - 2026-04-24

  • Class task merge semantics — units passing tasks=[...] to a class (autotools, cmake, go_binary) no longer fully replace the class’s default task list. Instead, overrides are merged by name: a same-named task replaces in place (preserving position and using the override’s steps fully), a new-named task is appended, and task("name", remove=True) drops a base task. This lets units add a new task (e.g., init-script) without restating the class-generated build task. The merge is implemented in a new classes/tasks.star helper (merge_tasks(base, overrides)) shared by the three classes. The simpleiot unit dropped its duplicated build task as a result; existing units that override build are unaffected (replace-in-place yields the same result as the previous full-replacement semantics).
  • Fix install_template/install_file path resolution for helper functions — template paths now resolve relative to the .star file containing the install_template()/install_file() call, not to the file that ultimately calls unit(). Previously, a helper like base_files(name = "base-files-dev") in units/base/base-files.star invoked from images/dev-image.star looked for templates under images/base-files-dev/ instead of units/base/base-files/, breaking the dev-image build. The base directory is now captured at install-step construction time from the Starlark caller frame; existing units that define and use install steps in the same .star file are unaffected.
  • File templates — units can declare external template files (.tmpl) and static files in a directory alongside the .star file and install them via new install_template() and install_file() step-value constructors placed directly in task(..., steps=[...]) alongside shell strings. Templates render through Go text/template with a unified map[string]any context auto-populated with name/version/release/arch/machine/console/project and any extra kwargs passed to unit(). The context map and the contents of the unit’s files directory are hashed so template edits and extra-kwarg changes invalidate the cache. Install steps run on the host (not inside the sandbox), so $DESTDIR / $SRCDIR / $SYSROOT in install paths expand to host paths rather than the container bind-mount paths. base-files, network-config, and simpleiot migrated off inline heredocs. See docs/file-templates.md.
  • CLI flag parsing with flag.NewFlagSet — refactored all subcommands (build, run, flash, init, clean, log, refs, graph) from manual switch-based parsing to Go’s flag.NewFlagSet. Adds free --help for every subcommand, consistent -flag/--flag support, and repeatable flags (e.g., --port). Net reduction of ~70 lines.
  • Go module cache — Go units now persist module and build caches across builds via cache_dirs = {"/go/cache": "go"}. The executor mounts cache/go/ from the project directory into the container, and GOMODCACHE and GOCACHE point to it. Subsequent builds skip module downloads.
  • Fix service enablement for S-prefixed init scripts — services declared with an S<NN> prefix (like S10network) no longer get a symlink created on top of the actual script, which was causing a symlink loop and breaking networking at boot.
  • Unit environment field — units can declare environment = {"KEY": "VAL"} which the executor merges into the build environment for all tasks. The Go class uses this for GOMODCACHE/GOCACHE so custom tasks (like simpleiot) get the cache env vars automatically.
  • QEMU port forwarding in machine configqemu_config() now accepts a ports field (e.g., ports = ["2222:22", "8118:8118"]) for default port forwarding. CLI --port flags extend these. Fixed a bug where multiple ports created duplicate QEMU netdevs. Fixed hostfwd syntax to use QEMU’s host-:guest format. QEMU machines default to SSH (2222:22), HTTP (8080:80), and SimpleIoT (8118:8118).
  • Service enablement moved to units — units now declare services = ["sshd"] to indicate which init scripts they provide. The image assembly auto-enables services by reading service metadata from installed APKs and creating S50<name> symlinks (or custom priority like S10network). The services parameter on image() is removed.
  • Design specs — added docs/starlark-packaging-images.md (move packaging and image assembly to composable Starlark tasks) and docs/file-templates.md (external template files using Go text/template, replacing inline heredocs in units).
  • Go class uses golang containergo_binary() now defaults to the golang:1.24 external container image instead of toolchain-musl. Cross-compilation is handled via GOARCH/GOOS environment variables with CGO_ENABLED=0 for static binaries, so the container always runs at host architecture (no QEMU overhead).
  • Per-unit sandbox and shell selection — units now have sandbox (bool, default false) and shell (string, default “sh”) fields. The autotools, cmake, and image classes set sandbox=True, shell="bash" for bwrap isolation. External containers (like golang:1.24) use the defaults — no bwrap, POSIX sh — since they don’t ship bwrap or bash.
  • simpleiot unit — new go_binary unit for SimpleIoT v0.18.5, an IoT application for sensor data, telemetry, and device management.
  • ca-certificates unit — Mozilla CA bundle for TLS verification. Added to dev-image alongside simpleiot.
  • Per-task container resolution — tasks can override the unit-level container via task(container = "..."). The executor resolves the container per-task, falling back to the unit default.
  • TUI: amber [yoe] title — the top-left title in the TUI now renders [yoe] in amber on black, matching the project logo.
  • Fix module URLs in init generated project file.

[0.7.1] - 2026-04-06

  • Unit release field — units can now specify release = N for packaging revisions (apk -rN suffix). Defaults to 0. Bump when the unit definition changes but the upstream version doesn’t.
  • Build metadata — each unit’s build directory now contains a build.json with status, start/finish times, duration, build disk usage, installed size (destdir/apk), and input hash. The TUI detail view shows build time and sizes alongside the unit name.
  • Persistent build output — executor output (executor.log) is now written for both CLI and TUI builds, so the TUI detail view shows build output regardless of how the build was triggered.

[0.7.0] - 2026-04-06

  • Container units — build containers are now Starlark units (toolchain-musl) instead of an embedded Dockerfile. Containers participate in the DAG, caching, and versioning. Classes set container and container_arch explicitly. run(host = True) enables host-side execution for container builds. The embedded Dockerfile and EnsureImage() are removed. Container images are tagged with arch for explicitness (yoe-ng/toolchain-musl:15-x86_64). Cross-arch containers use docker buildx automatically.
  • Container image prefix renamed — Docker image prefix changed from yoe-ng/ to yoe/ (e.g., yoe/toolchain-musl:15-x86_64). Arch is always included in the tag for explicitness. Cross-arch containers use docker buildx automatically.
  • TUI: detail view log search — press / in the unit detail view to search build output and logs. Matching lines are highlighted in yellow; n/N jump to next/previous match. First esc clears the search, second returns to the unit list.
  • TUI: color-coded unit types — unselected units are now subtly colored by class: blue for regular units, magenta for images, cyan for containers. Selected unit uses a brighter green for visibility. Search (/) also matches unit class, so typing “image” or “container” filters to units of that type.
  • E2E build test scripts — added yoe_e2e, yoe_e2e_x86_64, and yoe_e2e_arm64 shell functions in envsetup.sh that build base-image from the e2e test project for x86_64 and arm64 (cross-build via QEMU user-mode).

[0.6.0] - 2026-04-03

  • TUI: ctrl+f/ctrl+b page scrolling — added vim-style page-forward and page-back keybindings in both the unit list and detail views, alongside the existing PgUp/PgDn keys.
  • Heavy development notice — GitHub releases and yoe update now remind users to clean their build directory and re-create projects with each new release.
  • Updated plan/spec indexes — all specs and plans marked with current implementation status; added plans INDEX.
  • Remove repository() builtin — the repository(path = "...") config in PROJECT.star is removed. APK repos are now always at repo/<project-name>/, derived from the project name. This eliminates a confusing override that defeated per-project repo scoping.
  • TUI: show all units — removed the filter that only showed units reachable from image definitions. The TUI now lists all units in the project.
  • README: “Is Yoe-NG Right for You?” — new section clarifying when to use Yocto vs Yoe-NG. Added container workloads on the target device to the roadmap in Design Priorities.
  • Fix yoe update download URL — binary name now matches goreleaser’s naming convention (yoe-Linux-x86_64) instead of incorrectly including the version (yoe-v0.1.0-Linux-x86_64), which caused 404 errors.
  • Unit name collision detection — duplicate unit names now error at evaluation time with a clear message showing which module first defined the unit.
  • PROVIDES collision detection — two units providing the same virtual name in the same module now error. Units from higher-priority modules (later in the module list) override lower-priority ones with a notice.
  • --project flagyoe --project projects/customer-a.star build selects an alternate project file. Available on all subcommands.
  • Per-project APK repo — package repositories are now scoped per project name (repo/<project>/) to prevent stale packages across project switches.
  • README: Principles section — added six core design principles covering leveraging existing infrastructure, aggressive caching, custom containers per unit, no intermediate formats, one tool for all levels, and tracking upstream closely.
  • README: Build dependencies and caching — new section explaining the three kinds of build dependencies (host tools via containers, library deps via sysroot/apk, language-native deps via their own package managers), symmetric caching at the unit level, and how native builds unlock existing package ecosystems (e.g., PyPI wheels on ARM).
  • README: Cross-compilation is optional — updated from “no cross compilation” to “cross compilation is optional,” acknowledging that Go and some C/C++ packages cross-compile easily while fussy packages can avoid it.
  • Raspberry Pi in yoe init — rpi machine added to the project initialization template.
  • Fix false “old build layout” warningwarnOldLayout was written for the old build/<arch>/<unit>/ directory structure but the current layout is build/<unit>.<scope>/, causing every build directory to trigger a spurious warning.

[0.5.1] - 2026-04-02

  • Remove version from release binary name to fix stable download URL.

[0.5.0] - 2026-04-02

BASE-IMAGE boots on RPI4

  • Tasks replace build stepsbuild = [...] replaced by tasks = [...] with named build phases. Each task has run (shell string), fn (Starlark function), or steps (mixed list). Classes (autotools, cmake, go) are now pure Starlark.
  • run() builtin — Starlark functions can execute shell commands directly during builds. Errors show .star file and line number, not generated shell. run(cmd, check=False) returns exit code/stdout/stderr for conditional logic. run(cmd, privileged=True) runs directly in the container as root for operations like losetup/mount that bwrap can’t do.
  • Unit scope — units declare scope = "machine", "noarch", or "arch" (default). Machine-scoped units (kernels, images) build per-machine. Build directories are flat: build/<name>.<scope>/. Repo is flat with scope in filenames: repo/<name>-<ver>-r0.<scope>.apk.
  • Machine-portable images — images no longer hard-code machine-specific packages or partitions. MACHINE_CONFIG and PROVIDES inject machine hardware specifics automatically. base-image works across QEMU x86, QEMU arm64, and Raspberry Pi without changes.
  • PROVIDES virtual packages — units and kernels declare provides to fulfill virtual names. provides = "linux" on linux-rpi4 means images that list "linux" get the RPi kernel when building for raspberrypi4.
  • Image assembly in Starlark — disk image creation moved from Go to classes/image.star using run(). Fully readable, customizable, forkable.
  • Raspberry Pi BSP module (units-rpi) — machine definitions, kernel fork units, GPU firmware, and boot config for Raspberry Pi 4 and 5.
  • Runtime dependency resolution — image assembly now resolves transitive runtime dependencies automatically. RUNTIME_DEPS predeclared variable available after unit evaluation. Three-phase loader: machines → units → images.
  • Layers renamed to moduleslayer()module(), LAYER.starMODULE.star, yoe layeryoe module, layers/modules/. Aligns terminology with Go modules model used for dependency resolution.

[0.4.0] - 2026-03-31

ARM BUILDS ON X86 NOW WORK

  • TUI global notifications — the TUI now shows a yellow banner for background operations like container image rebuilds. Previously these events were only visible in build log files.
  • cmake added to build container — cmake is now available as a bootstrap tool in the container (version bump to 14), enabling units that use the cmake build system.
  • xz switched to cmake — the xz unit now uses the cmake class instead of autotools with gettext workarounds, simplifying the build definition.
  • TUI reloads .star files before each build — editing unit definitions or classes no longer requires restarting the TUI. The project is re-evaluated from Starlark on each build, picking up any changes to build steps, deps, or configuration.
  • Fix xz autoreconf failure — xz’s configure.ac uses AM_GNU_GETTEXT macros which require gettext’s m4 files. The xz unit now provides stub m4 macros and skips autopoint, allowing autoreconf to succeed without gettext installed in the container.
  • Cross-architecture builds — build arm64 and riscv64 images on x86_64 hosts using QEMU user-mode emulation. Target arch is resolved from the machine definition. Run yoe container binfmt for one-time setup, then yoe build base-image --machine qemu-arm64 works transparently.
  • Arch-aware build directories — build output is now stored under build/<arch>/<unit>/ and APK repos under build/repo/<arch>/, supporting multi-arch builds in the same project. Note: existing build caches under build/<unit>/ will need to be rebuilt (yoe clean --all).
  • yoe container binfmt — new command to register QEMU user-mode emulation for cross-architecture container builds. Shows what it will do and prompts for confirmation.
  • Multi-arch QEMUyoe run now auto-detects cross-architecture execution and uses software emulation (-cpu max) instead of KVM. Container includes qemu-system-aarch64 and qemu-system-riscv64.
  • TUI setup menu — press s to open a setup view for selecting the target machine. Shows available machines with their architecture and highlights the current selection. Designed to accommodate future setup options.

[0.3.4] - 2026-03-30

  • Build lock files — a PID-based .lock file is written during builds so other yoe instances can detect in-progress work instead of marking active builds as failed. Builds are skipped if another process is already building the same unit.
  • yoe clean --locks — removes stale lock files left behind by crashed or killed builds.
  • TUI edit for cached layers — pressing e on a unit now also searches the layer cache, so editing works for units from layers cloned via yoe layer sync.

[0.3.3] - 2026-03-30

  • HTTPS layer URLsyoe init now uses HTTPS URLs for the units-core layer instead of SSH, removing the need for SSH key setup to get started.

[0.3.2] - 2026-03-30

  • TUI scrolling — both the unit list and detail log views are now scrollable. The unit list shows / overflow indicators when there are more units than fit on screen. The detail view supports j/k, PgUp/PgDn, g/G navigation through the full build output and log, with auto-follow during active builds.
  • Auto-sync layersyoe build and other commands that load the project now automatically clone missing layers on first use, matching the lazy container-build pattern. Existing cached layers are not fetched/updated, so there is no added latency on subsequent runs. Explicit yoe layer sync is still available to update layers.
  • TUI confirmation prompts — quitting (q/ctrl+c) and cancelling a build (x) now prompt for confirmation when builds are active, preventing accidental loss of in-progress builds. Declining a prompt clears the message cleanly.
  • Fix build cancellation not stopping containers — cancelling a build (via TUI quit or ctrl+c on the CLI) now explicitly stops the Docker container (docker stop) instead of only killing the CLI client, which left containers running in the background.
  • Fix stale cache after cancelled builds — the cache marker is now removed before building so a cancelled or failed rebuild no longer appears cached from a previous successful build.

[0.3.1] - 2026-03-30

ALL UNITS ARE NOW BUILDING

  • Per-unit sysroots — each unit’s build sysroot is assembled from only its transitive deps, not every previously built unit. Fixes busybox symlinks shadowing container tools (e.g., musl-linked expr breaking autoconf).
  • Run from TUI — press r on an image unit to launch it in QEMU.
  • Log writer plumbing — container stdout/stderr in image assembly and source fetch/prepare output now route through the build log writer instead of os.Stdout. Fixes TUI alt-screen corruption during background builds.
  • Autotools maintainer-mode overridemake invocations pass ACLOCAL=true AUTOCONF=true AUTOMAKE=true AUTOHEADER=true MAKEINFO=true to prevent re-running versioned autotools (e.g., aclocal-1.16) that aren’t in the container. Fixes gawk and similar packages.
  • rcS init scriptbase-files now includes /etc/init.d/rcS which runs all /etc/init.d/S* scripts at boot.
  • network-config unit — new unit that configures a network interface via an init script.
  • Build failure context — when a unit fails, the output now lists all downstream units blocked by the failure. The TUI shows cached units in blue and displays the full build queue (waiting/cached) before work begins.
  • dev-image — added kmod and util-linux to the development image.
  • Image rootfs dep fix — image assembly now follows only runtime_deps when resolving packages, not build-time deps. Fixes build-only packages (e.g., gettext via xz) being installed into the rootfs and overflowing the partition.

[0.3.0] - 2026-03-30

THIS RELEASE DOES NOT WORK - this release is only to capture rename and TUI updates. Wait for a future one to do any work.

BREAKING CHANGE - due to rename, recommend deleting any external projects and starting over.

  • Terminology rename — “recipe” is now “unit” and “package” is now “artifact” throughout the codebase. The Starlark package() function is now unit(), the image field packages is now artifacts, and the recipes/ directory in layers is now units/. The recipes-core layer is now units-core. The Go internal/packaging package is now internal/artifact.
  • yoe log — view build logs from the command line. Shows the most recent build log by default, or a specific unit’s log with yoe log <unit>. Use -e to open the log in $EDITOR.
  • yoe diagnose — launch Claude Code with the /diagnose skill to analyze a build failure. Uses the most recent build log by default, or a specific unit’s log with yoe diagnose <unit>.
  • TUI rewriteyoe with no args launches an interactive unit list with inline build status (cached/waiting/building/failed). Builds run in-process via build.BuildUnits() with real-time status events — dependencies show as yellow “waiting”, then flash green as they build. Features: background builds (b/B), edit unit in $EDITOR (e), view build log (l), diagnose with Claude (d), add unit with Claude (a), clean with confirmation (c/C), search/filter (/), and a split detail view showing executor output and build log tail. The yoe tui subcommand has been removed.
  • Build eventsbuild.Options.OnEvent callback notifies callers (e.g., the TUI) as each unit transitions through cached/building/done/failed states.

[0.2.10] - 2026-03-30

  • yoe container shell — interactive bash shell inside the build container with bwrap sandbox, sysroot mounts, and the same environment variables recipes see during builds. Useful for debugging build failures and sandbox issues.

[0.2.9] - 2026-03-30

  • Bash for build commands — switched build shell from busybox sh to bash. Avoids autoconf compatibility issues (e.g., AS_LINENO_PREPARE infinite loop) and matches what upstream build scripts expect. Removed per-recipe bash workaround from util-linux.
  • User account API — new classes/users.star provides user() and users_commands() functions for defining user accounts in Starlark. base-files is now a callable base_files() function that accepts a users parameter — image recipes can override it to add users (e.g., dev-image adds a user account with password password).

[0.2.8] - 2026-03-30

  • meson build system support — added samurai (ninja-compatible build tool), meson, and kmod recipes. Container updated to v11 with python3 and py3-setuptools for meson. Build environment now sets PYTHONPATH to the sysroot so Python packages installed by recipes are discoverable.
  • Container versioning note — CLAUDE.md now documents that both Dockerfile.build and internal/container.go must be bumped together.
  • gettext recipe — builds GNU gettext from source as a recipe instead of relying on the container. Provides autopoint needed by packages like xz that use gettext macros in their autotools build.
  • Sysroot binaries on PATH/build/sysroot/usr/bin is now prepended to PATH during builds, so executables from dependency recipes are discoverable.
  • Autotools class respects explicit build steps — no longer prepends default autoreconf/configure when a recipe provides its own build commands.
  • Claude Code plugin — added .claude/ plugin with AI skills for recipe development: diagnose (iterative build failure analysis), new-recipe (generate recipes from URLs/descriptions), update-recipe (version bumps), audit-recipe (review against best practices and other distros).
  • --clean build flag — deletes source and destdir before rebuilding. --force now only skips the cache check without cleaning.
  • --force/--clean scoped to requested recipes — dependency recipes still use the cache, only explicitly named recipes are force-rebuilt.
  • Fixed YOE_CACHE help text — was ~/.cache/yoe-ng, actually defaults to cache/ in the project directory.

[0.2.7] - 2026-03-27

  • Per-recipe build logs — build output written to build/<recipe>/build.log. Console is quiet by default; on error the log path is printed. Use --verbose / -v to stream build output to the console.
  • Fixed QEMU machine templates — removed UEFI firmware (ovmf/aavmf/ opensbi) incompatible with MBR+syslinux boot, fixed root device vda2vda1.

[0.2.6] - 2026-03-27

  • base-files recipe — provides filesystem skeleton: /etc/passwd (root with blank password), /etc/inittab (busybox init + getty), /boot/extlinux/ (boot config), and essential mount point dirs (/proc, /sys, /dev, etc.). Moved from hardcoded Go to a recipe so users can customize via overlays.
  • Serial console uses getty for proper login prompt.

[0.2.5] - 2026-03-27

Added

  • musl libc recipe — copies the musl dynamic linker from the build container into the image so dynamically linked packages work at runtime.
  • Automatic package dep resolution — image assembly now resolves transitive build and runtime deps from recipe metadata. e.g., openssh automatically pulls in openssl and zlib without listing them in the image recipe.
  • Recipes without source — recipes with no source field (e.g., musl) skip source preparation instead of erroring.

Fixed

  • Disable ext4 features (64bit, metadata_csum, extent) incompatible with syslinux 6.03 so bootloader can load kernel from any partition size.
  • Image package dep resolution walks both deps and runtime_deps so shared libraries are included.
  • OpenSSL recipe uses --libdir=lib so libraries install to /usr/lib instead of /usr/lib64 — fixes “Error loading shared library libcrypto.so.3”.
  • Inittab no longer tries to mount /dev (already mounted by kernel via devtmpfs.mount=1).
  • Skip TestBuildRecipes_WithDeps in CI — GitHub Actions runners don’t support user namespaces inside Docker.
  • Most stuff in dev-image now works.

[0.2.4] - 2026-03-27

  • update BL config

[0.2.3] - 2026-03-27

Changed

  • Container as build workeryoe CLI always runs on the host. The container is now a stateless build worker invoked only for commands that need container tools (gcc, bwrap, mkfs, etc.). Eliminates container startup overhead for read-only commands (config, desc, refs, graph, clean).
  • File ownership — build output uses --user uid:gid so files created by the container are owned by the host user, not root.
  • QEMU host-firstyoe run tries host qemu-system-* first, falls back to the container if not found.
  • --force scoped to requested recipes--force and --clean only force-rebuild the explicitly requested recipes; dependencies still use the cache for incremental builds.
  • Busybox init — images use busybox /sbin/init with a minimal /etc/inittab instead of init=/bin/sh. Shell respawns on exit, clean shutdown via poweroff.

Fixed

  • Shell quoting in bwrap sandbox commands — semicolons in env exports no longer split the command at the outer shell level.
  • Package installation in image assembly — always extracts .apk files via tar instead of gating on apk binary availability.
  • Rootfs mount points (/proc, /sys, /dev, /tmp, /run) now included in disk images via .keep placeholder files.
  • devtmpfs.mount=1 added to kernel cmdline so /dev is populated before init.

Removed

  • YOE_IN_CONTAINER environment variable — no longer needed.
  • ExecInContainer / InContainer / HasBwrap APIs — replaced by RunInContainer.
  • Container re-exec pattern — the yoe binary is no longer bind-mounted into the container.

[0.2.2] - 2026-03-27

Added

  • Layer path field — layers can live in a subdirectory of a repo via path = "layers/recipes-core". Layer name derived from path’s last component.
  • Project-local cache — source and layer caches default to cache/ in the project directory instead of ~/.cache/yoe-ng/
  • .gitignore in yoe init — new projects get a .gitignore with /build and /cache
  • Autotools autoreconf — autotools class auto-runs autoreconf -fi when ./configure is missing (common with git sources)
  • SSH URL support for source fetching (git@host:user/repo.git)
  • Design: per-recipe tasks and containers — planned support for named task() build steps with optional per-task Docker container images. Container resolves: task → package → bwrap. See docs/superpowers/plans/per-recipe-containers.md.

Changed

  • Default layer in yoe init uses SSH URL (git@github.com:YoeDistro/yoe-ng.git) with path = "layers/recipes-core"
  • Container no longer mounts a separate cache volume — cache/ is accessible through the project mount
  • Container runs with --privileged (needed for losetup/mount during disk image creation and /dev/kvm for QEMU)

[0.2.1] - 2026-03-27

Added

  • Dev-image with 10+ packages — new dev-image builds end-to-end with sysroot, including essential libraries (openssl, ncurses, readline, libffi, expat, xz), networking (curl, openssh), and debug tools (strace, vim)
  • Remote layer fetchingyoe layer sync clones/fetches layers from Git
  • Sysroot + image deps in DAG — build sysroot and image dependencies resolved as part of the dependency graph
  • yoe_sloc — source lines of code counter using scc

Fixed

  • Correct partition size for losetup, ensure sysroot dir exists
  • Recipe fixes for end-to-end dev-image builds

Changed

  • Moved design docs into docs/ directory
  • Expanded build-environment and comparisons documentation

[0.2.0] - 2026-03-26

Added

  • Bootable QEMU x86_64 image — end-to-end flow from recipes to a partitioned disk image that boots to a Linux kernel with busybox
  • Starlark load() support — class imports and @layer//path label-based references across layers, // resolves to layer root when inside a layer
  • Recursive recipe discoveryrecipes/**/*.star directory traversal
  • recipes-core layer — autotools/cmake/go/image classes, busybox/zlib/ syslinux/linux recipes, base-image, qemu-x86_64 machine
  • APKINDEX generationAPKINDEX.tar.gz for apk dependency resolution
  • Bootstrap frameworkyoe bootstrap stage0/stage1/status
  • Container auto-enter — host yoe binary bind-mounted into container, Dockerfile embedded in binary, versioned image tags

Fixed

  • Build busybox as static binary (no shared lib dependency on rootfs)
  • APKINDEX uses SHA1 base64 as required by apk
  • Handle git sources in workspace (tag upstream without re-init)
  • bwrap sandbox inside Docker with --security-opt seccomp=unconfined
  • Mount git root for layer resolution

Changed

  • Prefer git sources with shallow clone over tarballs
  • Move build commands to envsetup.sh (yoe_build, yoe_test)

[0.1.0] - 2026-03-26

Initial release of yoe-ng — a next-generation embedded Linux distribution builder.

Added

  • CLI foundationyoe init, yoe config show, yoe clean, yoe layer commands with stdlib switch/case dispatch (no framework)
  • Starlark evaluation engine — recipe and configuration evaluation using go.starlark.net with built-in functions (project(), machine(), package(), image(), layer_info(), etc.)
  • Dependency resolution — DAG construction, Kahn’s algorithm topological sort with cycle detection, yoe desc, yoe refs, yoe graph
  • Content-addressed hashing — SHA256 cache keys from recipe + source + patches + dep hashes + architecture
  • Source managementyoe source fetch/list/verify/clean with content-addressed cache and patch application
  • Build executionyoe build with bubblewrap per-recipe sandboxing, automatic container isolation via Docker/Podman
  • Package creation — APK package creation, yoe repo commands, local repository management
  • Image assembly — rootfs construction, overlay application, disk image generation with syslinux MBR + extlinux
  • Device interactionyoe flash with safety checks, yoe run for QEMU with KVM
  • Interactive TUI — Bubble Tea interface for browsing recipes and machines
  • Developer workflowyoe dev extract/diff/status for source modification
  • Custom commands — extensible CLI via commands/*.star
  • Patch support — per-recipe patch files applied as git commits