Repository navigation
os/v3: the appliance announces 1bit-os.local, and a blank-key check stops asking for a key - #953
Conversation
… healthy server is not reported dead (#945) `#934` split the lemonade row into a blank branch (the shipped cycle-20 default) and a keyed branch. The keyed branch assigned `MODELS`; the blank branch assigned only `BODY` and `WITHOUT`, and `MODELS` is the ONE input every row below reads (`models`, `have_model`, `have_model_ci`, `pick`, and therefore `chat`/`chat-hrx`/`chat-npu`/`bench`). On the appliance that made a live server report `FAIL Lemonade did not answer` and skip every chat row: the c20 board was 19/1 with `u4-no-hw-skips` downstream of it. Root cause is the split itself, not the missing line: a mode can set the probe body and forget the list. So the list is now chosen in ONE place, by a pure selector, after the mode branch: MODELS=$(models_list "${MODELS:-}" "$BODY") `models_list` takes the keyed body when there is one and the keyless body otherwise - which IS the list in blank mode (`/api/v1/models` answers the same records keyless when a key is not configured). Before / after, driving the script's real lemonade block with the network stubbed (healthy server, cycle-20 blank mode): before PASS lemonade answers ... without a key FAIL models Lemonade did not answer, so the model list cannot be checked MODELS=[] after PASS lemonade answers ... without a key MODELS=[{"data":[{"id":"user.Qwen3-0.6B-NPU-1bit",...}]}] models row: proceeds with 61 bytes of model list and keyed mode still takes the authenticated list (not the keyless one). Self-test: three new arms in `--self-test` (`models_list` blank / keyed / nothing answered) - the selector is pure for the same reason `lemonade_verdict_blank` is. Negative control: deleting `models_list` fails all three arms (`command not found`, rc 1); `bash -n` and the pre-existing 24 arms are clean on both sides (`release-check self-test PASS`). `tools/copyright.py --check` rc 0, no new files. Closes #945. Not claimed: the appliance-side board. The A/B on the frozen c20 image is in the issue; this fixes the branch that produced it and is proven device-free above.
…tops asking for a key Item 10 (owner 2026-11-04 ~01:3x: clickable links via ryzen) plus item 9a's other half and 9b from the c20 board (TOS3-C18-items.md), stacked on #951 for the model-list half of 9a. 10 mDNS: the built rootfs carries libavahi-client/common and no /usr/sbin/avahi-daemon, and nothing in the image answers an mDNS query, so http://1bit-os.local:13305 could never work. Adds the avahi-daemon + libnss-mdns package rows, /etc/init.d/18onboard-mdns (starts the D-Bus system bus first - PID 1 is not systemd, and avahi exits without a bus), the mdns4_minimal nsswitch line, and container-build.sh refusals for both halves. vm-check gains a 'U2 mdns' row. 9a the header half #951 does not cover: blank mode (the shipped default) sent 'Authorization: Bearer none' on the chat, bench and NPU requests. Blank mode's contract is stock Lemonade - no header at all, which is the shape the board measured (header-less /models 200, NPU long then short, 0 TDR). auth_get/auth_status/auth_post build the header only when the machine has a key, and `lemonade bench` gets LEMONADE_API_KEY unset rather than a placeholder. 9b prepare-stick says what the medium can answer: a stick with the NPU model and no GGUF has nothing for chat-hrx to ask, which is how the c20 stick shipped. vm-check's 'U4 release-check' row now fails on the RESULT line the board reads instead of recording it as INFO - that row is what let 9a through the VM gate. closes #952
PR Reviewer Guide 🔍(Review updated until commit 34702f8)Here are some key observations to aid the review process:
|
…s any script that names the bus
Measured on the c20 image itself (its puppy sfs, extracted): /etc/rc.d/rc.services does
grep -qF dbus-daemon $service_script || $service_script start
so a script whose TEXT contains that string is skipped outright - and comments count, because it greps the file.
18onboard-mdns started the D-Bus system bus itself, so it named the binary, so it would have been skipped and
1bit-os.local would have been silently unannounced on every boot. The trap's shape is the bad one: nothing fails.
The premise was wrong too, which is what makes the fix smaller rather than bigger: rc.sysinit already starts the
bus (`--system`, line ~695) BEFORE it runs rc.services (line ~727), and the board's own messages.log proves a live
bus (it was trying to activate org.freedesktop.login1). So the service now only WAITS for /run/dbus/system_bus_socket
(bounded, 10 s) and starts the responder; the dead bus-starting branch and the now-unreachable fallbacks are gone.
The gate is in the build, not in a comment: container-build.sh reads the PACKED etc/init.d/18onboard-mdns and refuses
the image if it is missing or if rc.services' own rule would skip it. My first attempt at this fix still tripped it
(one comment line spelled the path), which is the assertion doing its job.
Checked in a sandbox (unprivileged unshare, no device, host /run,/var,/usr untouched), three arms: no socket -> says
so and starts nothing; socket + avahi ok -> "avahi-daemon up" (announced); socket + avahi failing -> logs it, boot
continues.
|
Persistent review updated to latest commit c49946f |
cfb7dfe to
c49946f
Compare
|
Preparing review... |
|
Persistent review updated to latest commit c49946f |
|
#953 ready for review at c49946f. Stacked on #951 (100867d) - merge that first, then this rebases to 5 files. Item 9a's model-list half is #951's (I dropped mine rather than duplicate it); this carries the header half it misses, plus 9b and item 10 (closes #952). |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY on content at c49946f96 — but #953 contains #951's whole diff, so they are not both mergeable as they stand.
Merge order, first
#961's fix aside, the os/mini/release-check.sh hunks in this PR are the same change as #951 (models_list, the three self-test arms, MODELS=$(models_list "${MODELS:-}" "$BODY")). #951 is a strict subset: this PR has all of it plus the auth_get/auth_status/auth_post header change, the mDNS init script, prepare-stick item 9b, container-build.sh and vm-check.sh.
So: merge #953 and close #951 as superseded, or merge #951 first and rebase #953 onto it (git will drop the duplicate hunk). Landing both as they are means the second one conflicts over text that is already identical.
What I verified here
bash -nclean on every shell file it touches; the new18onboard-mdnsinit script included.- The
models_listhalf I already verified on #951 by running it: blank mode gives the keyless list, keyed mode is unchanged, and two negative controls (definition removed; keyed precedence dropped) both turn the self-test red with rc 1. - The blank-mode auth change is the one with behavioural risk, and the reasoning is right: cycle 20 sent
Authorization: Bearer none, and the board's own evidence is that a header-less request is what answers 200 and keeps the NPU TDR-free. Keeping the keyed and blank shapes in three small helpers rather than at every call site is the correct way to stop them drifting. prepare-stickitem 9b isokin both branches, deliberately: an NPU-only medium is legitimate, so this is a statement about what the stick can answer, not a pass/fail.
Not run
The mDNS announcement and the container-build/vm-check rows need a real image build and a boot, which is the appliance lane's gate, not something I can do on this box. packages-extra only adds the avahi/mDNS package lines and container-build.sh gains the checks around them; both are read, not executed.
…R for phones, and the mDNS name Owner, 2026-11-04 01:3x: "due to being headless, can you make the links clickable, because i'm on kvm and i want use it through ryzen", plus a QR code of the web UI URL and the mDNS name on the CONNECT block. The TUI already made no tty1 assumption, but three things assumed the VGA console, so using it from a terminal emulator was strictly worse than being at the box: the URLs were text nobody can click, the only way to reach the web UI from a phone was to retype 27 characters off a screen, and `q` was refused even where a prompt is harmless. Each of the three now depends on the terminal it actually finds itself on, decided in one place and reported in the footer: - OSC 8 hyperlinks on every URL in CONNECT when TERM is a terminal emulator (xterm-256color, tmux, kitty, ...), none when it is `linux`/`dumb` (VGA console, JetKVM, serial - there the escapes would be printed). ratatui has no hyperlink style, so the links are written AFTER the frame at the cells `ui::draw` reports: the drawn text is the link target, which `every_hyperlink_covers_the_url_that_is_drawn_there` asserts off the rendered frame at 80x25 and 240x67. A URL that wrapping split is not a link - half a URL opens the wrong thing. The link is re-stated every frame and styled with the row's own white+bold, so it keeps the pixel it had plus the underline (an `\x1b[4m` alone would inherit whatever colour the last cell left). - A QR code of the web UI URL beside the CONNECT rows, two modules per cell (17 rows for the appliance's 27-character URL: version 2, ECC L), drawn BLACK ON WHITE because a scanner wants dark-on-light and a terminal's dark background is the inversion many phone scanners refuse. Decoded end to end, not reasoned about: zxing-cpp on the PNG built from the 240x67 render's own cells returns `http://192.168.50.110:13305`. - `http://1bit-os.local:13305` on the CONNECT block, with `(mDNS)` when the box's `avahi-daemon` is up and `(mDNS - avahi not running)` when it is not, so the row never promises a name that will not answer. The responder itself is pi-os3's half (engine #953, still open) - this PR reads it, it does not install it. - `q` quits when started from an ssh session (sshd sets SSH_CONNECTION/SSH_TTY; a tty1 respawn sets neither) and still says "q stays" on tty1, which keeps the promise that console never drops to a bare prompt. The footer says which one you are looking at, from the same fact the key uses. Not done on purpose: the QR is NOT at 80x25. The code needs 33 columns and 17 rows; at 80x25 the eight CONNECT rows, the ten MENU rows, the three log rows and the footer already account for all 25 console rows, so there is nothing to put it in without deleting one of the things the owner asked for. It is drawn wherever it fits (tty1 is 240x67; an ssh window is normally wider than 104 columns), and at 80x25 the URL stays plain text - which is also the size the QEMU gate reads, so `U2 menu connect` is unaffected. Measured (pty, the musl release binary from build-static.sh, d225399adbf7a119, 1550384 bytes): - TERM=xterm-256color at 80x25: 3 distinct OSC 8 targets written per frame - web UI, api base, mDNS name - each at the cell its text occupies; TERM=linux: 0 bytes of OSC 8, frames byte-identical to the previous build at the URL cells (checked, not assumed). - `q` with SSH_CONNECTION set: exit 0. `q` without it (env -u SSH_CONNECTION -u SSH_TTY): the screen stays and the footer says "q stays: no bare prompt here (7 shell, 9 reboot)". - 240x67: the panel is 33 columns at x=206, rows 3-19, all nine menu rows and 40 log rows still below it. - cargo test 42/42 (10 new: the links cover their own URL, a split URL is not a link, the QR is drawn only where it fits, the QR block is square 33x17 with a quiet zone, the mDNS row in both states at both sizes, the footer in both worlds, the ssh/hyperlink predicates), clippy --all-targets clean, fmt clean, tools/copyright.py --check rc 0. New dependency: `qrcodegen` 1.8 (Nayuki's reference encoder, MIT, no transitive dependencies, ~700 lines). It is compiled into the static binary on the build host; the image build still only *installs* the binary (CONSOLE_TUI_BIN), so the appliance gains no toolchain and no runtime dependency.
|
Persistent review updated to latest commit 6de2eee |
|
Preparing review... |
…R for phones, and the mDNS name Owner, 2026-11-04 01:3x: "due to being headless, can you make the links clickable, because i'm on kvm and i want use it through ryzen", plus a QR code of the web UI URL and the mDNS name on the CONNECT block. The TUI already made no tty1 assumption, but three things assumed the VGA console, so using it from a terminal emulator was strictly worse than being at the box: the URLs were text nobody can click, the only way to reach the web UI from a phone was to retype 27 characters off a screen, and `q` was refused even where a prompt is harmless. Each of the three now depends on the terminal it actually finds itself on, decided in one place and reported in the footer: - OSC 8 hyperlinks on every URL in CONNECT when TERM is a terminal emulator (xterm-256color, tmux, kitty, ...), none when it is `linux`/`dumb` (VGA console, JetKVM, serial - there the escapes would be printed). ratatui has no hyperlink style, so the links are written AFTER the frame at the cells `ui::draw` reports: the drawn text is the link target, which `every_hyperlink_covers_the_url_that_is_drawn_there` asserts off the rendered frame at 80x25 and 240x67. A URL that wrapping split is not a link - half a URL opens the wrong thing. The link is re-stated every frame and styled with the row's own white+bold, so it keeps the pixel it had plus the underline (an `\x1b[4m` alone would inherit whatever colour the last cell left). - A QR code of the web UI URL beside the CONNECT rows, two modules per cell (17 rows for the appliance's 27-character URL: version 2, ECC L), drawn BLACK ON WHITE because a scanner wants dark-on-light and a terminal's dark background is the inversion many phone scanners refuse. Decoded end to end, not reasoned about: zxing-cpp on the PNG built from the 240x67 render's own cells returns `http://192.168.50.110:13305`. - `http://1bit-os.local:13305` on the CONNECT block, with `(mDNS)` when the box's `avahi-daemon` is up and `(mDNS - avahi not running)` when it is not, so the row never promises a name that will not answer. The responder itself is pi-os3's half (engine #953, still open) - this PR reads it, it does not install it. - `q` quits when started from an ssh session (sshd sets SSH_CONNECTION/SSH_TTY; a tty1 respawn sets neither) and still says "q stays" on tty1, which keeps the promise that console never drops to a bare prompt. The footer says which one you are looking at, from the same fact the key uses. Not done on purpose: the QR is NOT at 80x25. The code needs 33 columns and 17 rows; at 80x25 the eight CONNECT rows, the ten MENU rows, the three log rows and the footer already account for all 25 console rows, so there is nothing to put it in without deleting one of the things the owner asked for. It is drawn wherever it fits (tty1 is 240x67; an ssh window is normally wider than 104 columns), and at 80x25 the URL stays plain text - which is also the size the QEMU gate reads, so `U2 menu connect` is unaffected. Measured (pty, the musl release binary from build-static.sh, d225399adbf7a119, 1550384 bytes): - TERM=xterm-256color at 80x25: 3 distinct OSC 8 targets written per frame - web UI, api base, mDNS name - each at the cell its text occupies; TERM=linux: 0 bytes of OSC 8, frames byte-identical to the previous build at the URL cells (checked, not assumed). - `q` with SSH_CONNECTION set: exit 0. `q` without it (env -u SSH_CONNECTION -u SSH_TTY): the screen stays and the footer says "q stays: no bare prompt here (7 shell, 9 reboot)". - 240x67: the panel is 33 columns at x=206, rows 3-19, all nine menu rows and 40 log rows still below it. - cargo test 42/42 (10 new: the links cover their own URL, a split URL is not a link, the QR is drawn only where it fits, the QR block is square 33x17 with a quiet zone, the mDNS row in both states at both sizes, the footer in both worlds, the ssh/hyperlink predicates), clippy --all-targets clean, fmt clean, tools/copyright.py --check rc 0. New dependency: `qrcodegen` 1.8 (Nayuki's reference encoder, MIT, no transitive dependencies, ~700 lines). It is compiled into the static binary on the build host; the image build still only *installs* the binary (CONSOLE_TUI_BIN), so the appliance gains no toolchain and no runtime dependency.
|
Persistent review updated to latest commit 5aae34c |
|
Persistent review updated to latest commit 3db4549 |
|
Persistent review updated to latest commit 07ffab9 |
|
Persistent review updated to latest commit 407f224 |
|
Persistent review updated to latest commit 5c5994c |
|
Persistent review updated to latest commit 34702f8 |
…R for phones, and the mDNS name (#962) * os/v3/puppy: the console TUI is usable over ssh - clickable URLs, a QR for phones, and the mDNS name Owner, 2026-11-04 01:3x: "due to being headless, can you make the links clickable, because i'm on kvm and i want use it through ryzen", plus a QR code of the web UI URL and the mDNS name on the CONNECT block. The TUI already made no tty1 assumption, but three things assumed the VGA console, so using it from a terminal emulator was strictly worse than being at the box: the URLs were text nobody can click, the only way to reach the web UI from a phone was to retype 27 characters off a screen, and `q` was refused even where a prompt is harmless. Each of the three now depends on the terminal it actually finds itself on, decided in one place and reported in the footer: - OSC 8 hyperlinks on every URL in CONNECT when TERM is a terminal emulator (xterm-256color, tmux, kitty, ...), none when it is `linux`/`dumb` (VGA console, JetKVM, serial - there the escapes would be printed). ratatui has no hyperlink style, so the links are written AFTER the frame at the cells `ui::draw` reports: the drawn text is the link target, which `every_hyperlink_covers_the_url_that_is_drawn_there` asserts off the rendered frame at 80x25 and 240x67. A URL that wrapping split is not a link - half a URL opens the wrong thing. The link is re-stated every frame and styled with the row's own white+bold, so it keeps the pixel it had plus the underline (an `\x1b[4m` alone would inherit whatever colour the last cell left). - A QR code of the web UI URL beside the CONNECT rows, two modules per cell (17 rows for the appliance's 27-character URL: version 2, ECC L), drawn BLACK ON WHITE because a scanner wants dark-on-light and a terminal's dark background is the inversion many phone scanners refuse. Decoded end to end, not reasoned about: zxing-cpp on the PNG built from the 240x67 render's own cells returns `http://192.168.50.110:13305`. - `http://1bit-os.local:13305` on the CONNECT block, with `(mDNS)` when the box's `avahi-daemon` is up and `(mDNS - avahi not running)` when it is not, so the row never promises a name that will not answer. The responder itself is pi-os3's half (engine #953, still open) - this PR reads it, it does not install it. - `q` quits when started from an ssh session (sshd sets SSH_CONNECTION/SSH_TTY; a tty1 respawn sets neither) and still says "q stays" on tty1, which keeps the promise that console never drops to a bare prompt. The footer says which one you are looking at, from the same fact the key uses. Not done on purpose: the QR is NOT at 80x25. The code needs 33 columns and 17 rows; at 80x25 the eight CONNECT rows, the ten MENU rows, the three log rows and the footer already account for all 25 console rows, so there is nothing to put it in without deleting one of the things the owner asked for. It is drawn wherever it fits (tty1 is 240x67; an ssh window is normally wider than 104 columns), and at 80x25 the URL stays plain text - which is also the size the QEMU gate reads, so `U2 menu connect` is unaffected. Measured (pty, the musl release binary from build-static.sh, d225399adbf7a119, 1550384 bytes): - TERM=xterm-256color at 80x25: 3 distinct OSC 8 targets written per frame - web UI, api base, mDNS name - each at the cell its text occupies; TERM=linux: 0 bytes of OSC 8, frames byte-identical to the previous build at the URL cells (checked, not assumed). - `q` with SSH_CONNECTION set: exit 0. `q` without it (env -u SSH_CONNECTION -u SSH_TTY): the screen stays and the footer says "q stays: no bare prompt here (7 shell, 9 reboot)". - 240x67: the panel is 33 columns at x=206, rows 3-19, all nine menu rows and 40 log rows still below it. - cargo test 42/42 (10 new: the links cover their own URL, a split URL is not a link, the QR is drawn only where it fits, the QR block is square 33x17 with a quiet zone, the mDNS row in both states at both sizes, the footer in both worlds, the ssh/hyperlink predicates), clippy --all-targets clean, fmt clean, tools/copyright.py --check rc 0. New dependency: `qrcodegen` 1.8 (Nayuki's reference encoder, MIT, no transitive dependencies, ~700 lines). It is compiled into the static binary on the build host; the image build still only *installs* the binary (CONSOLE_TUI_BIN), so the appliance gains no toolchain and no runtime dependency. * os/v3/puppy: pin the QR's rows as a known answer (pi-prs' nits on #962) Review: "the one sentence in the module that would make a reader trust the drawing is the one thing the suite does not do" (nit 1), and nit 2's "worth keeping the comment that says the matrix is the encoder's and not ours". Nit 1 was right about the phrase, not quite about the coverage: `blocks()` did have a known-answer test (a hand-drawn 2x2), but its name (`two_module_rows_become_one_text_row`) did not say so and the doc comment pointed at "a known-answer test" without naming it - so the reader was asked to take it on faith. Both halves are fixed: - `two_module_rows_become_one_text_row` -> `blocks_draws_two_module_rows_per_text_row` (the drawing rule now reads in the name). - New `a_known_url_renders_to_these_exact_rows`: `code("http://192.168.50.110:13305")` against the 17 rows of 33 cells written out literally. That is the known answer rather than the fit check (`the_appliances_own_url_fits_the_block_the_layout_reserves`, kept - it is about the layout's box, not the drawing). A change to the encoder, the ECC level (L), the version (2) or the quiet zone now fails the build instead of shipping a code nobody can read; the literal block also makes the quiet zone and the half-block rule visible in the test itself. Nit 2, kept and made explicit: the module doc now says the matrix is the encoder's, that the drawing is what this module owns, that a scan is not asserted by any test - and how it WAS measured: the 240x67 frame rendered to a PNG and read back with zxing-cpp returned `http://192.168.50.110:13305` (2026-11-04), with "re-run that by hand if the encoder/ECC/quiet zone changes". The README says the same next to the feature. Measured on this head: cargo test 43/43 (1 new), clippy --all-targets -D warnings clean, fmt --check clean, tools/copyright.py --check rc 0, build-static.sh ok (static musl binary fc71d2c720f97213, 1550360 B). The two snapshots are untouched by this commit - no drawn cell changes. --------- Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
The page told a reader 'mDNS is not claimed' and to read the address off the box. Cycle 21 item 10 (#953, merged b8a07df) ships the responder, asserts it at build time and prints it in the console's CONNECT block, so that sentence is now false in the direction that costs a reader a step. Said what the sources say, with the two limits kept: the announced name is $(hostname).local (so it follows a rename), the CONNECT row says when avahi is not running, and a peer resolving it over multicast is still not claimed on this page.
… announced but never answers (#1016) * os/v3/puppy: the firewall drops every mDNS query, so 1bit-os.local is announced but never answers The c22 board (4 Nov 12:5x AST) logged `avahi-daemon up: 1bit-os.local is announced on this network` and a raw mDNS query from ryzen got no answer. #952/#953 put the responder in the image; what was never opened is the path for a *query*, and the cause is the same `rc.firewall` that `zz-onboard-ports` already works around for tcp. Read out of the packed c22 puppy SFS, `etc/init.d/rc.firewall`: -P INPUT DROP -A INPUT -p ALL -d 224.0.0.1 -j DROP -A INPUT -p UDP -j udp_inbound (drops 137/138, accepts DHCP 67->68, then -j RETURN) -A INPUT -m pkttype --pkt-type broadcast -j DROP -A OUTPUT -p ALL -j ACCEPT so every NEW inbound UDP packet falls through to the DROP policy, and a query on 5353 is exactly that; the appliance's own announcements leave fine, which is why avahi reported success while nothing answered. This file already opens tcp/13305 and tcp/22 for the same reason - mDNS is the UDP half. LAN-only, the same ranges hosts.allow already names below plus link-local for avahi's no-lease fallback, ahead of udp_inbound and the broadcast drop, idempotent via -C, and in `zz-onboard-ports` rather than `18onboard-mdns`, whose name sorts before `rc.firewall` and would be wiped by it. Measured in a booted c22 image (`~/wt/os3i/c23-mdns-probe6.sh`, one controlled announcement burst per arm, the rules' own packet counters): arm policy DROP the fix's INPUT rule shipped firewall 5 -> 12 (+7/2095) absent + one `-I INPUT 1 ... dport 5353` 12 (unchanged) 0 -> 7 packets / 2095 bytes rule removed again 12 -> 19 (+7/4963) absent the same seven packets that were dropped are the ones the rule accepts, byte for byte. Two caveats stated rather than implied: those packets are avahi's own multicast announcements looped back (IP_MULTICAST_LOOP), not a query from a peer - same packet class, same chain, and a peer's query is what the board row must still prove; and QEMU user networking cannot carry inbound UDP at all (a `hostfwd=udp` delivers nothing even with the firewall flushed), which is why this is measured on the appliance's own interface. Gate: a `U3 mdns rule` row in vm-check.sh that fails an image without the rule. closes #1011 * os/v3/puppy: a self-check for the inbound rules, so the mDNS block cannot regress silently `mdns-ports-selftest.sh` runs the shipped `zz-onboard-ports` against a stub `iptables` (a rule table in a file, `-C` answered from it): no root, no ruleset, no device. It asserts four `udp/5353` inserts with the four intended sources, that a second run inserts nothing, and that the two TCP ports this file already opened are still opened. Measured both ways before committing: shipped script run 1: 6 inserts, 4 of them udp/5353 run 2: 0 inserts, 4 -C -> RESULT: PASS origin/main's run 1: 2 inserts, 0 of them udp/5353 -> RESULT: FAIL `sh -n`, `shellcheck -S warning` and `tools/copyright.py --check` are clean. --------- Co-authored-by: pi-os3 <pi-os3@1bit>
…rget (#859) * docs: 1bit OS is the Puppy Linux appliance, and os/mini is a CI/VM target 1bit OS is Puppy Linux (ResolutePup64, woof-CE on the Ubuntu 26.04 resolute base) with Lemonade Server and the engine embedded, our MOK-signed 7.3 perfopt kernel, headless by default with a 1bit OS (desktop) boot entry, bash/sudo/apt, data and settings on 1BIT-DATA, Secure Boot on. The BusyBox os/mini image is the CI/VM target and is no longer called 1bit OS anywhere. No release is claimed: the next release carries the appliance. Files: README.md (get-started steps + a milestone row), os/README.md (the 1bit OS page is the appliance; os/mini, os/v2 and os/v3/puppy laid out), os/v2/README.md (title and 'ships today' line), os/LICENSES.md (which image carries what), docs/releases.md (top note, artifact row, known issues, os/mini build note), docs/os-v3.md (Puppy is v3, with the board result; the Ubuntu plan is the fallback), docs/os-v3-live-puppy-eval.md (dated update: the four blockers are closed and it booted on the board), docs/kernel.md (the kernel reaches 1bit OS as woof-CE's kernel package), docs/PORTING.md (same, one clause). * docs: install 1bit OS (stick, disk, USB) and connect a client to Lemonade docs/install.md covers the options the owner asked for, each with who it is for, the steps and what is erased: run from the stick, install to an internal disk or another USB drive with 1bit-install (the seven steps, the refusals, the firmware entry), keep the existing OS (no dual-boot wizard in this release), the engine on an existing Linux (advanced, no PPA route yet), Secure Boot and the MOK, and the Bosgame front-port note (#823). docs/clients.md covers the address and the key, curl, the web app, Lemonade Mobile on iPhone and Android, the OpenAI SDKs and any OpenAI-compatible app, the lemonade CLI from another machine, and the security/rotation rules. Every example was run against the cycle-13 appliance image booted in QEMU with Secure Boot on (~/wt/rlk-vm/verify.sh, results.txt): the key is enforced (401/401/200, /api/v1 and /v1), the web app is served at :13305, the curl and OpenAI-SDK chat examples returned 200 with content, the CLI flags and the LAN beacon work, and the firewall opens only 13305 and 22. The install ran end to end on a bare disk. Three defects it found are filed as #865 (an installed disk does not boot: init resolves pupsfs=<label> but not pupsfs=UUID=...), #866 (step 7's BootNext lands on the BootManagerMenuApp) and #867 (a dd-written stick never gets an API key minted, so Lemonade serves the LAN unauthenticated); the pages state each where it matters, with the measured label A/B that isolates #865. README: step 4 of Get started points at both pages. * docs/install: no unmeasured install duration * docs: the 1bit OS image ships from Hugging Face, not a GitHub release asset The Puppy image is 9,128,902,656 B written / 2,212,389,580 B as 1bit-os-puppy.img.xz, and a GitHub release asset is capped at 2 GiB, so the image lives at https://huggingface.co/1bit-MONSTER/1bit-OS with its SHA256SUMS. README, docs/install.md and docs/releases.md now say so and give the same two write lines everywhere (sha256sum -c SHA256SUMS --ignore-missing, then xz -dc ... | dd; balenaEtcher and Rufus DD mode take the .img.xz directly). The GitHub releases page keeps the lemonade-onebit package, the release notes and the release SHA256SUMS, and links to the image. History rows are untouched: the c18-c20 candidates really did ship the BusyBox os/mini image as 1bit-os-<tag>.img.zst, and those sizes and shas stay as recorded. * docs: the Hugging Face image repository is private until v2026.44 is published Every place a reader acts on the download (README quickstart, the install page's image row and write block, the release table's image row) now says the repository is private until v2026.44 is published, so the link is the download from publication day and an earlier release carries only the BusyBox os/mini image, the CI/VM target. Addresses the CHANGES on #859: the URLs are correct and the upload landed, but they answer 401 while the repository is private. * docs: the appliance boots as 1bit-os, and installer #866 is fixed Two claims in this branch were true of the cycle-13 image and are not true of the image that ships next: - docs/clients.md said the hostname is Puppy's per-machine puppypc<MAC>; the image sets /etc/hostname to 1bit-os (os/v3/puppy/embed/etc/hostname, and the c16 board boot read back 1bit-os). Keep the cycle-13 observation as history and tell the reader the name, not a guessed .local. - docs/install.md said installer step 7 and the pupsfs=UUID boot are both open. #866 is fixed on main by #889 (merged 2026-11-03); #865's fix is #890, open. * docs/clients: say puppypc<digits>, not <MAC digits> - the MAC derivation is unverified * docs/install: both installed-disk fixes are in main, and a dd-only stick does get a key now Three claims went stale since this branch opened, all measured against origin/main today rather than against the cycle-13 artifact: - the installed-disk blockquote said "until both are in an image". #890 (#865's init fix) merged 2026-11-03T12:45Z on top of #889 (#866), and its own probe boots a disk installed by `1bit-install` as the only disk in QEMU; an image built from main boots the installed disk as written. - the measurement table's #865 row still said "fix open as #890". - the dd-only stick paragraph said a stick with no key serves every endpoint unauthenticated and that #867 is an open defect of this image. On main it is not: 05onboard-data no longer pre-creates an empty lemond.conf and 10lemonade mints on "is there a key" rather than "is there a file" (#828). The cycle-13 measured behaviour is kept as history, with the fix pointer and how to read the minted key (console 1 -> Show API key). * docs: both MOKs are needed, the real menu order, the save layer, #860 not in tree, the shipped artifact name Six Copilot review findings, each checked against the sources rather than the prose: - README and install.md §6 said Secure Boot "can stay on" with "shim, grub, the kernel and its modules all signed with our MOK". The shim is Microsoft-signed (os/v3/puppy/mkimage.sh:75-80 pulls shimx64.efi.signed), and on a machine that has never trusted our key the firmware refuses the kernel and the certificates are not on the stick — so a fresh machine has to enroll first or turn Secure Boot off. Both places now say that and link to the enrollment steps. - install.md step 1 named the third menu entry as the serial shell; the recipe orders headless, desktop, install-to-a-disk, serial shell (os/v3/puppy/mkimage.sh:152-166), so the documented ordinal opened the installer. All four are now named with their flags. - the RAM-only paragraph contradicted the PUPMODE=12 save layer above it. It now separates the two measured cases: no save folder + `poweroff -f` (marker gone, the cycle-13 observation) versus a save folder + normal shutdown, where G7 measured an /etc marker surviving (os/v3/puppy/g7-probe.sh:28-33,93-99), with 05onboard-data's shutdown sync/unmount named. - install.md advertised `scripts/mk-plugin-package.sh` as an existing route; it is on PR #860, not in this tree. Marked as such. - the enrollment row imported only `MOK-Kernel.der`; kernel.md's table has a second key, `MOK.der`, for every module we build, so a machine that enrolled only the kernel key still refuses the NPU and `ec` modules. The row now imports both, and §6 states the split. - releases.md's artifact row listed `1bit-os-<tag>.efi` as the appliance file; the Puppy recipe builds a disk image (`1bit-os-puppy.img.xz` + SHA256SUMS) and there is no standalone `.efi` for it. The `.efi` and `.img.zst` names stay with the earlier BusyBox os/mini candidates, which the same page's gated-candidate table already lists. The Hugging Face privacy clause is unchanged. copyright.py --check rc 0; site builds 53 pages, one <h1> per touched page. * docs: the console is a menu that prints how to connect, not a login prompt The cycle-18 image puts a CONNECT block (address, web UI, API, ssh) above the console menu, and 1bit-console replaces plogin on a headless boot, so README's "console login" and the bare "a console with no X" no longer describe what comes up. Also names the desktop entry by its grub.cfg label, `1bit OS (desktop, JWM)` (os/v3/puppy/mkimage.sh:156). * docs: the appliance ships with no API key (cycle 20), and the console's item 1 is the toggle #934 made blank the shipped mode: 10lemonade starts with LEMONADE_API_KEY unset and binds 0.0.0.0:13305 anyway, so every request works with no header until a person turns a key on from console item 1 (which mints 48 hex into /etc/1bit/lemond.conf, 0600, on /data, restarts the service and prints it), or prepare-stick --mint writes one offline. install.md and clients.md still described the #867/#828 behaviour - 'the appliance mints one on its first boot', 'read the key with show API key', 'every request needs the API key', '401 without the key', 'Lemonade Mobile: on 1bit OS it is required'. All of those are now false of the shipped default and wrong in the unsafe direction (they imply the box is closed when it is open). Rewritten against the sources, with file:line: 10lemonade:43-62,78-81; 1bit-console-menu:238,242,300-370; console-tui/src/ui.rs:203-215, 280-292; prepare-stick.sh:346-410; vm-check.sh:710-730; 1bit-webapp-proxy:37-44. The cycle-13 measured rows stay as history, marked as that image's behaviour, and the os/mini media difference (it still mints) is stated per docs/os-v3.md. * docs/clients: a non-1bit-os hostname is a person's rename, not a fault (cycle-16 u2-hostname) The cycle-16 board booted as `strix` while the frozen image bytes still read `1bit-os`: a person renamed it at the console, and the release lane's `u2-hostname` red was that reading, not an image defect. Section 1 told a reader `hostname` prints `1bit-os` with no way to tell a rename from the `puppypc<digits>` regression it is contrasted with, so a reader on a renamed box would go looking for a fault. Name the console path (3 Network -> 3 Set the hostname, `1bit-console-menu:437,463-466,632`) and say to check the address instead of the name. * docs/os-v3: where v3 stands, as of cycle 20 — 33-row board runs and a blank key by default The standing paragraph was written on 2026-11-02 and had gone wrong in two ways in the same breath. It quoted '20 of puppy-check's 25 rows PASS' as the appliance's current state (the checker has 33 rows now; cycle 19 came back 33 rows, 0 failed, 1 skipped, and cycle 20's two reds were the acceptance script's own defect, engine#945, fix open as #951), and it said 'Lemonade answers on :13305 with its key' — false and unsafe since engine#934 made blank mode the shipped default (10lemonade:43-62 binds 0.0.0.0:13305 with LEMONADE_API_KEY unset; console item 1 turns a key on). Restate it against the c19/c20 board verdicts and the #934 sources, and add one sentence to the eval page's dated 2026-11-02 update so the 'with its key' reading there is marked as that cycle's behaviour rather than today's. * docs/os-v3: #945's fix landed as #951, so the sentence stops calling it open * docs/clients: the appliance announces 1bit-os.local from cycle 21 on The page told a reader 'mDNS is not claimed' and to read the address off the box. Cycle 21 item 10 (#953, merged b8a07df) ships the responder, asserts it at build time and prints it in the console's CONNECT block, so that sentence is now false in the direction that costs a reader a step. Said what the sources say, with the two limits kept: the announced name is $(hostname).local (so it follows a rename), the CONNECT row says when avahi is not running, and a peer resolving it over multicast is still not claimed on this page. * docs/clients: two citations point at the lines that carry the claim container-build.sh gained 12 lines in #976, so the mdns pack-checks it cites moved from 496/505 to 508/517; and the webapp proxy's foreign-Origin refusal is at 45-49 (the rule and ALLOWED_ORIGINS) and 74-77 (the code), not 37-44 - which is the config block and the sentence that introduces the key attachment. Both numbers read back out of the merged tree this pass, not carried over. * docs/clients: the 8080 chat page is not served on the c19/c20 boards Both boards log `OSError: [Errno 98] Address already in use` for 127.0.0.1:8080 (c19 finding, unchanged on c20, and #959's own body says so), so the proxy's chat page is not reachable there while Lemonade's own page on :13305 is. A reader sent to :8080 chases a dead port; the sentence names it and points at :13305, with the init script's own two lines cited. * docs: re-point two citations at the lines that now hold them (CMakeLists 322, serve.cpp 202-213, ui.rs 218-222) * docs/os-v3-live-puppy-eval: a bare `</dev/null` code span truncated the rendered page at that table row python-markdown reads the angle-bracket close tag as raw HTML and drops everything after it: 46,922 B of markdown rendered to 21,897 B, cutting the page mid-table in front of the whole cycle-13/19/20 acceptance evidence. Reworded to plain text (same meaning); the page now renders 61,087 B with the cycle-13 rows, and the site build is 56 pages, rc 0. * docs/clients: re-point the two vm-check.sh citations after #1001/#1007 moved them (616-621, 740-754) * docs/clients: the c22 board announced the name and nothing answered it - rc.firewall drops inbound mDNS Cycle 23 (#1016) opened udp/5353 in zz-onboard-ports for the LAN ranges and added the `U3 mdns rule` gate row. Section 1 said only the responder and the LAN could leave you reading an address, which reads as "works on a c22 stick" when the measured c22 board shows the opposite. Stated what was measured, with the fix's citations, and kept the one hop nobody has proved: the packets the rule accepts were avahi's own loopback announcements, not a peer's query. Also re-points the blank-mode U3 citation (:740-754 -> :749-762) after #1016. * docs: the image's kernel is 7.4.0-rc1-1bit since cycle 21, and the cycle-23 board is green os-v3.md said the appliance ships `7.3.0-rc4-1bit-perfopt`. Measured in the frozen images rather than taken from a note: the c20 image carries `7.3.0-rc4-1bit-perfopt+`, and the c21, c22 and c23 images carry `7.4.0-rc1-1bit` (string present in each), so the page now says which cycle moved it. The same paragraph's newest board was cycle 20; cycle 23 (4 Nov) is in, with its row counts and the two facts the c23 verdict records - the single red is a bug in the check's own u7-mdns row, not the image. clients.md: three citations re-pointed at the lines that now carry them after #1020 moved vm-check.sh and container-build.sh by 13 and 1 lines. * docs/clients: re-point the two vm-check.sh citations #989 moved again #989 added the U4 kernel rows above them, so the U3 mdns rule row is at 711-714 (was 689-692) and the blank/keyed U3 network block at 784-797 (was 762-775). The citations now also name the row, because this file has moved under them three times tonight. * docs/os-v3-live-puppy-eval: CMakeLists.txt:322 -> 331-332, moved by #1021 #1021 added nine lines to CMakeLists.txt above the HRX block; 331-332 is still target_compile_definitions(... ONEBIT_HRX_LIBHSA=...). * docs/clients: the mDNS fix now states the outbound leg too, and two citations #1025 moved #1025 (c24 prep) adds the outbound 5353 rule to open_mdns - intent, not a fix: the packed c23 image accepts all output, and the c23 board's "out: NONE" was the row demanding a rule the image correctly lacks. The page now says which half each cycle opened and why the second one exists, and the gate has a row per half. Citations: open_mdns 55-66 -> 56-74, the blank/keyed U3 block 784-797 -> 789-802. * docs/clients: re-point the three vm-check.sh citations after the file grew ~190 lines The gate script gained rows since these numbers were written, so the three citations in clients.md pointed at unrelated rows (616-621 landed in the service-up/U3-key rows, 711-714 in the driver-signature row, 789-802 in the engine-sha row). Re-read them against the merged tree: U3 mdns rule 849-852, U3 mdns out 884-886, U3 auth (blank vs keyed) 670-676, getent hosts 722-723. * docs/clients: the CONNECT name rows are ui.rs:236-238, not 213-216 Caught by re-reading every citation in the page with sed instead of trusting the string: 213-216 is the 'web UI: waiting for network' line. The name/mDNS rows the paragraph quotes are at 236-238 on main (#1041/#1059 moved them). * docs: the 1bit OS kernel is 7.4.0-rc1-1bit, and the boot entry label says so The appliance images from cycle 22 on carry 7.4.0-rc1-1bit (c22/c24/c26 hand-offs, and the c24 release verdict), but four current-state lines in this PR still named 7.3.0-rc4-1bit-perfopt: README's 1bit OS paragraph, os/README.md's kernel row, and install.md's boot step plus its Secure Boot chain. install.md's boot step is the sharpest: mkimage.sh builds the menu title from the vmlinuz version (`menuentry "1bit OS (headless console, ${kver})"`), so the label a reader sees is the kernel's own string. The dated cycle-13 measurement rows keep 7.3.0-rc4-1bit-perfopt+ - they say what that image carried, and install.md:277 and clients.md:254 say so. Also os/README.md names the second boot entry as mkimage.sh words it, `1bit OS (desktop, JWM)`. * docs/clients, docs/install: four citations point at the rows the prose names Re-read every file:line in both pages out of origin/main (fadcc1e) instead of trusting the earlier pass - the pattern that has failed in this lane before. Three had rotted under the cycle-25 console/logging work: - clients.md:78-81 -> 104-108 for "blank mode is LAN-reachable on purpose"; 78-81 is now the cycle-25 log_level comment, the LAN comment moved down. - clients.md:632 -> 645 for the console's top-level Network row; 632 is the closing brace of the CLI dispatcher. - install.md:218-222 -> 240-243 for the TUI's api row; 218-222 is the "web UI: waiting for network" block, the api/keyed rows are 240-244. And one was under-cited: clients.md:88 cited 10lemonade:43-62 for the 0.0.0.0:13305 bind, which is line 107-108. Now 43-62,107-108. Checked mechanically: /tmp citcheck over both pages, every ref printed with its first line, all twelve in clients.md and eight in install.md name what the sentence says. * docs/os-v3-live-puppy-eval: the update box's numbers are cycle 12's, and one citation went stale Two claims on this page had drifted, both found by checking them against today's tree rather than re-reading the prose: 1. The update box led with '20 of `puppy-check`'s 25 rows PASS' as if it were the appliance's acceptance state. That was cycle 12, the first hardware boot; cycle 19 came back 33 rows / 0 failed and cycle 23 41 rows / 40 PASS / 1 FAIL (the FAIL being the checker's own `u7-mdns` row). Within this same PR, docs/os-v3.md already carries those. The numbers stay, labelled as cycle 12's - they are what decided the plan - and the page now points at os-v3.md as the page of record instead of growing a second, diverging copy. 2. `ONEBIT_HRX_LIBHSA` is a CMake compile definition, cited at `CMakeLists.txt:331-332`; those lines are the npu_sources block now. It is at `CMakeLists.txt:356` (verified against origin/main). The other nine file:line citations in this branch were checked the same way and are correct (ui.rs:236-238 and 240-243, container-build.sh:508 and 518-522, vm-check.sh:670-676 / 722-723 / 849-852, prepare-stick.sh:346-410, g7-probe.sh:28-33, app/serve.cpp:202-213). * docs: the .img.xz size is approximate, because the exact bytes change with every cut pi-prs flagged 2,212,389,580 B as the one figure on the download pages a reader can falsify with `ls -l`: the only .xz on this box is ~/wt/hf-1bitos/1bit-os-puppy.img.xz at 2,212,388,768 B (3 Nov, an earlier cut), and the Hugging Face repository is private, so the published file cannot be read to check. Every re-cut changes it (c26 and c27 are both 9,128,902,656 B written but their images differ), so an exact compressed byte count is a number this page cannot keep true. Dropped it in favour of "about 2.2 GB compressed" (README, install.md, releases.md x2) and kept the figure that is stable across cuts, the 9,128,902,656 B written, which install.md:41 now pairs with the rule that matters: the size to check is the one `ls -l` prints after the download. No sha or exact size now depends on a cut this page cannot see. --------- Co-authored-by: pi-rlk <pi-rlk@1bit.gg> Co-authored-by: agent <agent@local> Co-authored-by: pi-rlk <rlk@1bit.local> Co-authored-by: bong-water-water-bong <bong-water-water-bong@users.noreply.github.com> Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: pi-os3 <pi-os3@1bit.local>
Cycle 21 items 10, 9a's header half and 9b (c20 board,
TOS3-C18-items.md) plus pi-release'sTOS3-NOTE-c20-prepstick.md.Stacked on #951 (
100867df), which owns item 9a's model-list half (#945) with its own self-tests — merge #951 first; after it lands this rebases to 5 files.closes #952
Root causes, measured
Item 10 — nothing in the image can answer an mDNS query. Measured in the built resolute64 rootfs:
libavahi-client/libavahi-commonare present,/usr/sbin/avahi-daemonis not, andnsswitch.confhas nomdnsline. Sohttp://1bit-os.local:13305(owner: "clickable links via ryzen") could never resolve. Two halves: the responder (package row +/etc/init.d/18onboard-mdns) and the client half (libnss-mdns+mdns4_minimal).container-build.shrefuses an image missing either.The responder's own trap, found by extracting the c20 image's puppy sfs (
c49946f):/etc/rc.d/rc.servicesruns each/etc/init.dscript withstartonly aftergrep -qF dbus-daemon "$script"— a script whose text names the bus binary is skipped outright, comments included. The first version of this service started the bus itself, so it named the binary, so it would have been silently skipped on every boot and1bit-os.localwould never have been announced. The premise was wrong too:rc.sysinitalready starts the bus (--system, line ~695) before runningrc.services(line ~727), and the board's ownmessages.logproves a live bus. So the service now only waits (bounded, 10 s) for/run/dbus/system_bus_socketand starts the responder.container-build.shapplies rc.services' own rule to the packed script and refuses the image if it would be skipped — my first attempt at the fix still tripped it (one comment line spelled the path), which is the assertion doing its job. Sandbox check, three arms: no socket → says so, starts nothing; socket + avahi ok → "avahi-daemon up"; socket + avahi failing → logged, boot continues.Item 9a, header half — blank mode asked for a key it does not have. The shipped default is blank key (owner, c20: "blank key"), whose contract is stock Lemonade: no header at all. Cycle 20 sent
Authorization: Bearer noneon the chat, bench and NPU requests to keep one code path. #951 fixes the model list but not this.auth_get/auth_status/auth_postbuild the header only when the machine has a key;lemonade benchgetsLEMONADE_API_KEYunset, not a placeholder.Item 9b — the medium said nothing about what it cannot answer. The c20 stick carried the NPU model and no GGUF, so
chat-hrxhad nothing to ask.prepare-stick --checknow prints that as its own line with the flag that fixes it (no GGUF in models/ - chat-hrx has nothing to ask; --model <file.gguf> stages one).Why the VM gate let 9a through.
vm-check'sU4 release-checkrow wasINFO: it printedFAIL modelsand passed. It now assertsRESULT: PASS— the same line the board reads.Before / after, on the request log
~/wt/os3i/rc-blank-repro.shruns the realrelease-check.shagainst a stub lemond in an unprivileged sandbox (CPU-only, no device, no/datatouched) and records every request's Authorization header plus the bench row's environment.origin/main)FAIL models,SKIP gguf,SKIP chat,SKIP benchGET /models— no POST at allPASS models,PASS chat (1BIT OK @42 tok/s),PASS bench,SKIP gguf(no GPU in the sandbox)POST /chat/completions auth=<none>GET/POST … auth=Bearer 70f057…Blank mode sends no header and still answers; keyed mode still sends the real key.
release-check.sh --self-test: PASS (includes #951's threemodels_listcases).Gates
bash -n/sh -nclean,tools/copyright.py --checkrc=0, no new shellcheck warning per file vs the base (release-check.shstays at its 5 pre-existing). The new fileos/v3/puppy/embed/etc/init.d/18onboard-mdnscarries the full Apache notice.Not claimed: that avahi actually starts under Puppy's init in a booted image, and that a peer on the LAN resolves
1bit-os.local— the first is theU2 mdnsvm-check row, the second needs multicast from outside the guest, which QEMU user networking does not carry. Both go to the board.