Skip to content

os/v2: the appliance on an Ubuntu base, updated with apt - #604

Merged
bong-water-water-bong merged 4 commits into
mainfrom
1bit/os-v2-apt
Oct 26, 2026
Merged

bong-water-water-bong merged 4 commits into
mainfrom
1bit/os-v2-apt

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

os/v2: the appliance on an Ubuntu base, updated with apt

Design: ~/briefs/TOSAPT-DESIGN.md. os/mini (the BusyBox image that ships today) keeps shipping until v2
passes the same checks on hardware; nothing here retires it.

This adds os/v2/ beside os/mini/, a v2 section in os/README.md, and the tools/copyright.py patterns
the packaged files need (dpkg control/conffiles, the console banner, *.mount/*.preset/*.network, the
snapshot pin, the power profile).

What is in the tree

file what it does
pin.txt the Ubuntu snapshot every build resolves against (resolute, 20261024T000000Z)
mkdebs.sh seven packages from what a release already produced — no binary is rebuilt. KERNEL_STAGE=<dir> adds the three a box needs; ENGINE_BUILD=<dir> localises the engine's libraries
mkrepo.sh the signed apt repository (Release, Release.gpg, InRelease)
mkrootfs.sh mmdebstrap --variant=minbase + apt install 1bit-appliance from that repository; MIRROR= overrides the snapshot for an outage
mklive.sh the live medium: squashfs on PARTLABEL=1BIT-LIVE, tmpfs overlay via one initramfs-tools script, ESP with shim + our MOK-signed grub + kernel + grub.cfg, and the stick's 1BIT-DATA
install.sh 1bit-install: the same refusal matrix as #539, three partitions (ESP, 1BIT-ROOT, 1BIT-DATA)
vmtest-v2.sh the appliance, booted in QEMU under OVMF, judged from the transcript the guest writes to /data
vmtest-install-v2.sh builds the medium with a test-only unit in it, runs the refusals, installs, then boots the installed disk alone
proto.sh, diag-v2.sh the whole prototype in one command; and a debugging boot that judges nothing

Packages: 1bit-engine (38 MB) · 1bit-lemonade (6.8 MB) · 1bit-kernel-image-<kver> (11.9 MB) ·
1bit-kernel-modules-<kver> (74.8 MB) · 1bit-ryzenadj (122 KB) · 1bit-appliance · 1bit-archive-keyring.

1bit-kernel-image-7.3.0-rc4-1bit-perfopt is the release kernel packaged as it sits on the build machine
(vmlinuz, System.map, config), not rebuilt: sbverify --cert MOK-Kernel.pem → Signature
verification OK
. Its postinst runs update-initramfs, which took inside the mmdebstrap chroot (the initrd
is in the built rootfs). The kver is in the package name, so an upgrade keeps the previous kernel installed —
that is the rollback. 1bit-kernel-modules carries all 3819 modules plus the signed ec_su_axb35.ko
(vermagic-checked, signer strixhalo Secure Boot MOK) and its source under /usr/src/1bit-ec-2026.43.
1bit-ryzenadj is built from the pinned v0.19.0 source, which ships beside the binary (LGPL-3.0).

Two gates mkdebs.sh enforces, because both failures are silent on a box: an EC module that is unsigned or
built for another kernel fails the build, and a vmlinuz with no signature fails the build. And a third:
the engine's own libraries must resolve from the package — scripts/pack-elf.sh's pack_localize runs over
the package tree, so libllama-server-impl.so, libhrx, libloomc, libomp travel inside it and the
RUNPATH is $ORIGIN (#549). A build-machine path in a package is a package that does not load; that is
exactly what the first v2 VM run found.

Evidence

Two runs, both judged from what the guest wrote onto its own disk (no port forwarding, no host-side guess).
Transcripts: ~/wt/osapt-proto/v2-final.log, ~/wt/osapt-proto/live-install.log.

VMTEST-V2 PASS — the appliance (rootfs from the seven debs, OVMF boot)

row result
packages all seven installed at 2026.43-1
banner 1bit OS, Lemonade Server appliance + Lemonade Server by AMD, running on the 1bit engine
/data ext4 on its own disk (/dev/vdb, label 1BIT-DATA)
engine 1bit --help: ok; llama-server's own libraries all resolve from the package
kernel package /boot/vmlinuz-7.3.0-rc4-1bit-perfopt and /boot/initrd.img-7.3.0-rc4-1bit-perfopt
1bit-selftest reports a mode
first boot /etc/1bit/installed written, SSH host keys generated on the box
sshd listening on 22 (socket activation, see below)
apt the repository answers (apt-cache policy 1bit-engine → Installed 2026.43-1); the shipped source is https://1bit.gg/apt with signed-by=/usr/share/keyrings/1bit-archive-keyring.gpg
Lemonade API key generated, 401 without it, lists Qwen3-0.6B-Q4_K_M, one chat answered (Paris.)
1bit-release-check every row passes or skips except sealed (a development stage carries no sealed NPU sets)

VMTEST-V2-INSTALL PASS — the medium and an install

row result
the medium boots squashfs + overlay (cmdline: live (squashfs + overlay))
refusals no --yes · a partition · the disk the live boot runs from · a disk in use · no GPT tool — each with its own REFUSED: line. The removable-device refusal cannot be exercised in QEMU (its usb-storage reports removable=0); the harness prints what the VM reports instead of claiming the row
install 1bit OS is installed on /dev/vdb; three partitions (EFI/1BIT-OS, 1BIT-ROOT, 1BIT-DATA)
the installed root the ESP carries the firmware loader; /boot has the kernel; fstab mounts by label; no live marker; our kernel package installed
the installed disk boots alone mode : INSTALLED, /etc/1bit/installed present, own SSH host keys, /data on /dev/vda3
Lemonade 401 without the key, lists the GGUF, chat: Paris.
1bit-release-check kernel : 7.3.0-rc4-1bit-perfopt with its modules, lemonade PASS, gguf PASS, chat PASS, bench PASS, licences PASS — only sealed fails

Bugs this found and fixes (all in this PR)

  1. The engine package could not load its own libraries. A dev-built stage carried the build tree's
    RUNPATH and never shipped libhrx.so.0, libloomc.so.0, libomp.so; they resolved on the build machine
    and nowhere else (libllama-server-impl.so: cannot open shared object file). Fixed with
    pack_localize, plus a loud fallback when no build tree is given.
  2. 1bit needed libgomp.so.1, which minbase does not have → the engine package now
    Depends: libgomp1, libpcre2-8-0 (its two non-base NEEDEDs).
  3. lemond exited in a loop: HOME is not set; cannot resolve Lemonade cache directory — a systemd unit
    has no HOME. Environment=HOME=/data/lemonade.
  4. sshd never started. ssh.service was enabled while ssh.socket was masked, and on Ubuntu 26.04 the
    socket unit carries RequiredBy=ssh.service: Failed to start ssh.service: Unit ssh.socket is masked.
    The package now enables the socket (what listens on 22, as on any Ubuntu box), masks nothing, and does
    not start the service.
  5. minbase has no sfdisk (it lives in Ubuntu's fdisk) — the installer would have refused on its own
    tool check. Added to the rootfs include list.
  6. The live root= must be the squashfs partition. With root= on the data partition, data.mount
    fought the initramfs for the same device and failed. root=/dev/disk/by-partlabel=1BIT-LIVE rootfstype=squashfs, and the overlay's lower layer is that mount.
  7. 1bit-appliance never shipped /usr/share/licenses/1bit-os, which 1bit-release-check's licences
    row reads. It does now (LICENSE, NOTICE, LICENSE-BINARY, NOTICE-BINARY, LICENSES.md).

Two initramfs-tools rules are worth knowing before touching mklive.sh: scripts/local-bottom only runs if
a local-top/local-premount script ran (so the overlay lives in scripts/init-bottom/0-1bit-live), and a
script there cannot return a variable by assignment — neither rootmnt= nor an export survives, because
ORDER runs each script as a command. /conf/param.conf is the channel. Both are comments in mklive.sh.

os/mini/release-check.sh needed no change after #597: it reads the key from the running lemond's
environment, which on v2 is the systemd EnvironmentFile=/etc/1bit/lemonade.env.

Known gaps

  • The ROCm userspace is not packaged. llama-server and ds4 link libamdhip64, rocBLAS, hipBLASLt,
    the HSA runtime and libamd_comgr from the SDK the build machine had. Ubuntu 26.04 has packages for them
    (libamdhip64-7, libhipblaslt1, librocblas*), but gfx1201 support against the distro's ROCm is a
    hardware question, so the engine's Depends do not name them yet. vmtest-v2 asserts the package's own
    libraries resolve and lets only that set be missing — which is the check that caught bug 1.
  • A development stage has no sealed NPU sets, so sealed fails by construction in these runs (the VM
    has no NPU either). A release stage always carries them.
  • The rootfs in these runs came from ca.archive.ubuntu.com, not the pinned snapshot: snapshot.ubuntu.com
    answered 503 to everything for about an hour on 25 Oct. mkrootfs.sh now retries
    (Acquire::Retries) and takes MIRROR=, and pin.txt is unchanged. The pinned path itself built twice
    earlier in the day (rootfs2 940 MB, rootfs3 1.2 GB).
  • No hardware run yet: the v2 image has not been booted on the box. os/mini keeps shipping until it has,
    and no tag, release or publish is part of this PR.

🤖 Generated with Claude Code

`os/mini` is the BusyBox image that ships today: everything in RAM, a reflash to update. `os/v2` is the
same appliance - Lemonade Server by AMD on the 1bit engine, our kernel, Secure Boot through the enrolled
MOK - on a minimal Ubuntu 26.04 root filesystem with systemd, where every piece we ship is a `.deb` from
our own signed repository and `apt update && apt upgrade` is the update path. `os/mini` keeps shipping
until v2 passes the same checks on hardware.

Seven packages, built from what a release already produces (no binary is rebuilt): `1bit-engine`,
`1bit-lemonade`, `1bit-kernel-image-<kver>`, `1bit-kernel-modules-<kver>`, `1bit-ryzenadj`,
`1bit-appliance` and `1bit-archive-keyring`. The kernel version is in the package name, so an upgrade keeps
the previous kernel installed - that is the rollback. `mkdebs.sh` refuses a stage whose EC module is
unsigned or built for another kernel, and a kernel image with no signature; it localises the engine's
libraries with `scripts/pack-elf.sh` (the same `pack_localize` a release tarball goes through), because a
package that carries the build machine's RUNPATH is a package that does not load (#549).

The live medium is the root filesystem as a squashfs with a tmpfs overlay: one initramfs-tools script
(`scripts/init-bottom/0-1bit-live`) builds the overlay and hands `rootmnt` back through `/conf/param.conf`,
so userspace has nothing custom at boot and a live boot cannot write the medium. `install.sh` is
`1bit-install`, with the same refusal matrix as #539 and three partitions (ESP, `1BIT-ROOT`, `1BIT-DATA`)
instead of a RAM image; it unpacks the squashfs, writes `fstab` by label, one grub entry per installed
kernel, and leaves `/etc/1bit/live` off the installed disk so `1bit-first-boot.service` writes
`/etc/1bit/installed` after making that machine's own SSH keys.

Two VM tests, both judged from what the guest writes to its own disk rather than from QEMU:
`vmtest-v2.sh` (the appliance, booted through OVMF so the secureboot row answers instead of failing for a
QEMU choice) and `vmtest-install-v2.sh` (builds the medium with a test-only unit inside it, boots it with a
target disk and a USB stick attached, runs the refusals, installs, then boots the installed disk alone).

Bugs this found in the v2 path, all fixed here: the engine package could not load its own libraries
(a dev-built stage carried the build tree's RUNPATH and never shipped libhrx/libloomc/libomp); `1bit`
needed `libgomp1`/`libpcre2-8-0`, which minbase has not; lemond exits at once without `HOME` in a systemd
unit; `ssh.service` enabled while `ssh.socket` was masked starts nothing at all on Ubuntu 26.04 (the socket
is what listens, and it carries RequiredBy=ssh.service) - so the socket is what the package enables; minbase
has no `sfdisk` (Ubuntu's lives in `fdisk`); `root=` in the live entry has to be the squashfs partition, or
`data.mount` fights the initramfs for the data partition; and `1bit-appliance` never shipped the aggregate
`/usr/share/licenses/1bit-os` that release-check reads.

Known gap, named in `os/v2/README.md` rather than papered over: the ROCm userspace (HIP, rocBLAS,
hipBLASLt, the HSA runtime) is the machine's, not the engine package's, and the engine's `Depends` do not
name Ubuntu's packages for it yet - gfx1201 support against the distro's ROCm is a hardware question a VM
cannot settle. `vmtest-v2` therefore asserts the package's own libraries resolve and lets only that set be
missing, which is exactly the check that caught the first bug above.

Evidence: `VMTEST-V2 PASS` (seven packages installed, banner, /data, 1bit --help, our kernel's vmlinuz and
initrd in /boot, sshd on 22, apt from the repository, Lemonade 401 + GGUF + chat, release-check with no
FAIL row but `sealed`, which needs a stage that carries the sealed NPU sets) and `VMTEST-V2-INSTALL PASS`
(six refusals, install, installed disk boots alone: INSTALLED mode, first-boot marker and own SSH keys,
/data on its own partition, Lemonade 401 + GGUF + chat, selftest INSTALLED). Transcripts:
`~/wt/osapt-proto/v2-final.log` and `~/wt/osapt-proto/live-install.log`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit 0899ea5)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

539 - Partially compliant

Compliant requirements:

  • The installer is implemented in os/v2/install.sh with the refusal matrix
  • The disk layout is implemented with GPT partitions as described
  • The installed system boots from the disk itself with the correct signed boot chain
  • The installed system carries the stick's data and has correct marker and banner
  • The installed system can run 1bit-selftest and 1bit-release-check

Non-compliant requirements:

  • The VM test script vmtest-install-v2.sh is not fully reviewed for correctness of all assertions

Requires further human verification:

  • The actual hardware verification of the installation process
  • The VM test assertions in vmtest-install-v2.sh need to be manually verified

549 - Partially compliant

Compliant requirements:

  • The mkdebs.sh script implements path mapping with -ffile-prefix-map
  • The mkdebs.sh script enforces artifact hygiene with pack_artifact_hygiene()
  • The mkdebs.sh script uses patchelf --set-rpath '$ORIGIN' to set relative RUNPATHs

Non-compliant requirements:

  • The script does not explicitly verify that the gate on the next package run will catch any issues

Requires further human verification:

  • Verification that the artifact hygiene checks are correctly enforced
  • Verification that the relative RUNPATHs are correctly set for all binaries

597 - Partially compliant

Compliant requirements:

  • The release-check.sh script is updated to source the key from the environment of the process listening on the Lemonade port

Non-compliant requirements:

  • The script does not explicitly verify that the key is correctly sourced from the environment

Requires further human verification:

  • Verification that the key is correctly sourced from the environment in all scenarios
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Incorrect partition naming logic

The new_part function in install.sh uses a heuristic to determine whether to append 'p' to the partition name, but this logic may not be robust for all device types. Specifically, it checks if the device name ends with a digit, but this approach could fail for devices like /dev/nvme0n1p1 where the device name itself ends with '1' but the partition number is appended with 'p'. This could lead to incorrect partition names being generated, potentially causing installation failures or incorrect disk layout.

new_part() {   # <disk> <1|2|3> -> the kernel's device name for that partition
    case "${1##*/}" in
        *[0-9]) echo "$1p$2" ;;   # nvme0n1, mmcblk0: a digit glued to a number needs the p
        *)      echo "$1$2" ;;    # sda, vda, hda
    esac
}
Potential library loading issues

The mkdebs.sh script uses ldd to check for missing libraries in the llama-server binary, but it only checks for libraries that are not found. It does not check for libraries that are found but are not in the expected location, which could lead to runtime issues if the libraries are not properly linked or if they are not available in the package's runtime environment.

    miss=$(ldd "$p/opt/1bit/hrx/llama/bin/llama-server" 2>/dev/null | awk '/not found/ {print $1}' | tr '\n' ' ')
    [ -z "$miss" ] || { echo "mkdebs: llama-server misses libraries: $miss"; exit 1; }
fi
Potential race condition in loop device setup

In mklive.sh, the script waits for loop devices to be available with a 20-second timeout, but this could be insufficient on slower systems or under heavy load. If the loop device does not become available within this time, the script will fail, potentially leading to an incomplete or failed image build.

i=0
while [ ! -b "${loop}p3" ] && [ "$i" -lt 20 ]; do sleep 1; i=$((i + 1)); done

⚠️ Review coverage: The following files were not included in this review because of the token budget:

  • os/v2/diag-v2.sh
  • os/v2/proto.sh
  • os/v2/debian/1bit-appliance/files/usr/lib/1bit/ensure-env.sh
  • os/v2/debian/1bit-appliance/files/usr/lib/1bit/first-boot.sh
  • os/v2/debian/1bit-appliance/files/etc/profile.d/1bit.sh
  • tools/copyright.py
  • os/v2/README.md
  • os/v2/debian/1bit-appliance/postinst
  • os/v2/debian/1bit-lemonade/1bit-lemonade.service
  • os/README.md
  • os/v2/debian/1bit-appliance/files/usr/lib/systemd/system/data.mount
  • os/v2/debian/1bit-appliance/files/usr/lib/systemd/system/1bit-board.service
  • os/v2/debian/1bit-appliance/files/usr/lib/systemd/system/1bit-boost.service
  • os/v2/debian/1bit-appliance/files/usr/lib/systemd/system/1bit-first-boot.service
  • os/v2/debian/1bit-appliance/files/usr/lib/tmpfiles.d/1bit.conf
  • os/v2/debian/1bit-appliance/files/etc/1bit/power
  • os/v2/pin.txt
  • os/v2/debian/1bit-kernel-image/postinst
  • os/v2/debian/1bit-engine/control
  • os/v2/debian/1bit-kernel-image/postrm
  • os/v2/debian/1bit-kernel-modules/postinst
  • os/v2/debian/1bit-lemonade/control
  • os/v2/debian/1bit-appliance/files/etc/systemd/system/getty@tty1.service.d/1bit-autologin.conf
  • os/v2/debian/1bit-appliance/files/etc/systemd/system/ssh.service.d/1bit-first-boot.conf
  • os/v2/debian/1bit-appliance/files/usr/lib/systemd/system-preset/90-1bit.preset
  • os/v2/debian/1bit-kernel-image/control
  • os/v2/debian/1bit-appliance/files/etc/systemd/network/10-1bit.network
  • os/v2/debian/1bit-ryzenadj/control
  • os/v2/debian/1bit-appliance/files/etc/motd
  • os/v2/debian/1bit-appliance/control
  • os/v2/debian/1bit-kernel-modules/control
  • os/v2/debian/1bit-appliance/files/etc/issue
  • os/v2/debian/1bit-archive-keyring/control
  • os/v2/debian/1bit-appliance/conffiles

@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit e4e1abd

@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit fdcfbb5

@github-actions

Copy link
Copy Markdown

Persistent review updated to latest commit 0899ea5

@bong-water-water-bong
bong-water-water-bong merged commit f11f8d5 into main Oct 26, 2026
8 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the 1bit/os-v2-apt branch October 26, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant