Repository navigation
os: a usable console - kernel messages off the shell, a visible prompt - #582
Merged
Merged
Conversation
First hardware boot (candidate 5, 26 Oct): the cmdline's ignore_loglevel kept every kernel line on tty1 after the banner (USB connect/disconnect from the KVM, amdgpu notes), on top of the shell, whose prompt was BusyBox's bare "# ". The owner could not use the console. init.sh now turns ignore_loglevel off and sets the console level to emergencies only once the system is up (both are runtime-writable; dmesg keeps everything, the serial log already has the boot), and the console, serial and ssh shells get PS1="1bit-os:<dir> # ". The gate's prompt matchers accept the new prompt (they look for a trailing "# "). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
bong-water-water-bong
pushed a commit
that referenced
this pull request
Oct 26, 2026
…gs, install, shell)
First hardware boot of candidate 5 left the console unusable: kernel lines over a bare "# " prompt
and nothing to do with it. This is what the owner asked for instead - a dialog-style full-screen
menu - as one POSIX sh script, os/mini/1bit-menu, with no new binary in the image (there is no
dialog, whiptail or ncurses on the stick, and terminfo is not guaranteed on a text console, so it
draws with ANSI escapes in the 16-colour set).
tty1 only. init.sh starts the menu in place of the bare `sh -i` loop; ttyS0 (the serial line the
QEMU gate drives) and ssh keep exactly the plain shell they have today, so no prompt matcher on the
gate changes, and the gate builds nothing new: the same init.sh reaches the same serial shell.
The menu shows the banner facts it was previously printed as text (edition, LIVE/INSTALLED, address,
Lemonade URL, where the API key is) and drives only commands the appliance already has - it starts
neither the engine nor Lemonade, which are up by the time init gets here:
Chat pick a model from the Local models `lemonade list` reports (the onebit ones marked) and
run `lemonade chat <id>` in the terminal; back on exit
Models `lemonade list` and the GGUFs on /data/models, paged by a built-in pager
Status the tail of /data/selftest.log and the PASS/FAIL/SKIP rows of the LATEST
/data/release-check.log run (each run is delimited), refreshing once a second; r runs
`1bit-release-check` again and shows its output
Logs tail -40 of /tmp/lemond.log, /data/boot.log and /tmp/fans.log, any key refreshes
Install the internal NVMe with its size, model and partition table, plus 1bit-install's own
refusal text (run without --yes, so the warning cannot drift from the tool); typing
INSTALL is required, then `1bit-install /dev/nvme0n1 --yes`, then an offer to reboot.
When the machine is installed it shows what it runs from instead
Shell `sh -i` with PS1 "1bit-os:\w # "; exit comes back to the menu
Power reboot or power off, confirmed with y
Keys: up/down or j/k, 1-7 jump, Enter chooses, q quits to the plain shell. `1bit-menu --shell`, or
holding q as it starts, skips the menu entirely.
The console can never be swallowed. The menu restores the terminal on EXIT, INT, TERM and HUP, and
init runs `stty sane` on tty1 before every restart (an addition asked for after review: a crash must
not leave raw mode behind). Any exit other than 0 restarts the menu; q means "give me the shell" and
drops to it. Two details found while proving this:
* init has to run that loop in the foreground, not as `prog &`: POSIX has an asynchronous list
ignore SIGINT and SIGQUIT, and the child inherits it - so a menu started with & could not be
interrupted, and neither could the lemonade chat or the shell it runs. init has nothing else to
do once the appliance is up, and both loops never return.
* key reads wait in 0.5 s slices instead of blocking, so a menu killed with SIGKILL cannot leave a
dd blocked in a read; a straggler holds tty1's session and then nothing can take the tty again.
Rebased onto baf5996 (#583): main's #582 (ignore_loglevel/printk off before the console, and
PS1="1bit-os:\w # " on tty1, ttyS0 and ssh through /etc/profile), #580 (ONEBIT_HRX_LLAMA_SERVER,
the libhsa preference), #583 (explicit HSA staging) and #579 (the onebit manifest) are all kept. The
menu replaces only the tty1 shell loop; the plain-shell fallback below it keeps #582's PS1, and the
menu's Shell entry sets the same one. The printk lines now pay off twice: a boot line painted over the
frame is a mess the menu can only clean up on the next keypress.
Proof, on ryzen, no GPU and no strixhalo reboot: bash -n / sh -n / dash -n clean; run in an 80x25
tmux pane with TERM=linux and a stub `lemonade` (plus stub 1bit-install and 1bit-release-check) on
PATH - it draws, navigates, and runs Chat, Models, Status (including re-running the check), Logs,
Install and Shell, and comes back from each; q and Ctrl-C both leave the terminal with icanon and
echo back. The init loop itself was driven on a pty nothing else owns: a SIGKILLed menu leaves raw
mode behind, the loop's `stty sane` restores it and the menu comes back, Ctrl-C is restarted by the
loop, and q falls through to the plain shell.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bong-water-water-bong
added a commit
that referenced
this pull request
Oct 26, 2026
…gs, install, shell) (#584) First hardware boot of candidate 5 left the console unusable: kernel lines over a bare "# " prompt and nothing to do with it. This is what the owner asked for instead - a dialog-style full-screen menu - as one POSIX sh script, os/mini/1bit-menu, with no new binary in the image (there is no dialog, whiptail or ncurses on the stick, and terminfo is not guaranteed on a text console, so it draws with ANSI escapes in the 16-colour set). tty1 only. init.sh starts the menu in place of the bare `sh -i` loop; ttyS0 (the serial line the QEMU gate drives) and ssh keep exactly the plain shell they have today, so no prompt matcher on the gate changes, and the gate builds nothing new: the same init.sh reaches the same serial shell. The menu shows the banner facts it was previously printed as text (edition, LIVE/INSTALLED, address, Lemonade URL, where the API key is) and drives only commands the appliance already has - it starts neither the engine nor Lemonade, which are up by the time init gets here: Chat pick a model from the Local models `lemonade list` reports (the onebit ones marked) and run `lemonade chat <id>` in the terminal; back on exit Models `lemonade list` and the GGUFs on /data/models, paged by a built-in pager Status the tail of /data/selftest.log and the PASS/FAIL/SKIP rows of the LATEST /data/release-check.log run (each run is delimited), refreshing once a second; r runs `1bit-release-check` again and shows its output Logs tail -40 of /tmp/lemond.log, /data/boot.log and /tmp/fans.log, any key refreshes Install the internal NVMe with its size, model and partition table, plus 1bit-install's own refusal text (run without --yes, so the warning cannot drift from the tool); typing INSTALL is required, then `1bit-install /dev/nvme0n1 --yes`, then an offer to reboot. When the machine is installed it shows what it runs from instead Shell `sh -i` with PS1 "1bit-os:\w # "; exit comes back to the menu Power reboot or power off, confirmed with y Keys: up/down or j/k, 1-7 jump, Enter chooses, q quits to the plain shell. `1bit-menu --shell`, or holding q as it starts, skips the menu entirely. The console can never be swallowed. The menu restores the terminal on EXIT, INT, TERM and HUP, and init runs `stty sane` on tty1 before every restart (an addition asked for after review: a crash must not leave raw mode behind). Any exit other than 0 restarts the menu; q means "give me the shell" and drops to it. Two details found while proving this: * init has to run that loop in the foreground, not as `prog &`: POSIX has an asynchronous list ignore SIGINT and SIGQUIT, and the child inherits it - so a menu started with & could not be interrupted, and neither could the lemonade chat or the shell it runs. init has nothing else to do once the appliance is up, and both loops never return. * key reads wait in 0.5 s slices instead of blocking, so a menu killed with SIGKILL cannot leave a dd blocked in a read; a straggler holds tty1's session and then nothing can take the tty again. Rebased onto baf5996 (#583): main's #582 (ignore_loglevel/printk off before the console, and PS1="1bit-os:\w # " on tty1, ttyS0 and ssh through /etc/profile), #580 (ONEBIT_HRX_LLAMA_SERVER, the libhsa preference), #583 (explicit HSA staging) and #579 (the onebit manifest) are all kept. The menu replaces only the tty1 shell loop; the plain-shell fallback below it keeps #582's PS1, and the menu's Shell entry sets the same one. The printk lines now pay off twice: a boot line painted over the frame is a mess the menu can only clean up on the next keypress. Proof, on ryzen, no GPU and no strixhalo reboot: bash -n / sh -n / dash -n clean; run in an 80x25 tmux pane with TERM=linux and a stub `lemonade` (plus stub 1bit-install and 1bit-release-check) on PATH - it draws, navigates, and runs Chat, Models, Status (including re-running the check), Logs, Install and Shell, and comes back from each; q and Ctrl-C both leave the terminal with icanon and echo back. The init loop itself was driven on a pty nothing else owns: a SIGKILLed menu leaves raw mode behind, the loop's `stty sane` restores it and the menu comes back, Ctrl-C is restarted by the loop, and q falls through to the plain shell. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
bong-water-water-bong
added a commit
that referenced
this pull request
Oct 26, 2026
…image (#590) Second hardware boot (candidate 8, 26 Oct): the full-screen menu drew on tty1 and waited for a key, and the owner could not type. /dev/input held only "mice", dmesg showed no USB keyboard, and the initramfs carried hid.ko (for amd_sfh) but neither usbhid nor hid_generic: CONFIG_USB_HID and CONFIG_HID_GENERIC are modules in this kernel and the image's module list never named them. The KVM's keyboard and any USB keyboard are HID devices, so no keystroke ever reached the console - on candidate 5 the same gap hid behind the kernel-message flood that #582 fixed. mkimage.sh adds usbhid, hid_generic and evdev to the module list (with their dependencies, as the list already does); init.sh loads them with the other USB drivers before the console starts. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
bong-water-water-bong
pushed a commit
that referenced
this pull request
Oct 26, 2026
…R code to scan The owner, looking at c10's menu over JetKVM: "you need to make the link for the url clickable also". A video console cannot be clicked, so this does both halves (addendum to the TTUI brief): 1. OSC 8 hyperlinks. The Address in the header is now wrapped in `\033]8;;<url>\033\\<url>\033]8;;\033\\`, which any modern terminal (ssh, a serial console in a terminal app) renders as a clickable link. It is guarded on TERM: the VT reports TERM=linux and never sees the escape, so the raw console shows the URL as plain text instead of stray characters. 2. A QR code on the physical console. `u`, or menu entry 8 "Phone", draws the web app URL with `qrencode -t ANSIUTF8` (half blocks: this URL's 29x29 modules with margin come to 17 text rows and fit an 80x25 screen), the URL underneath (still a link where the terminal supports it), and `a` switches to `-t ASCII` for a console font without the block glyphs - with a note when that form needs a taller screen than the console has. The header carries the hint: "On a phone press u". qrencode ships with the image: libqrencode 4.1.1 (LGPL-2.1-or-later). It rides the path Dropbear and mtools already use - mkimage.sh unpacks the Ubuntu qrencode and libqrencode4 .debs into ~/.cache/1bit-os/tools/x on first use, copies /usr/bin/qrencode to /bin/qrencode and resolves what it links with ldd under that tree (libqrencode.so.4 from that package, plus libpng16/libz/libm/libc, which the image already carries or takes from Ubuntu). Licences: os/licenses/qrencode/LICENSE (the LGPL-2.1 text) and SOURCE (project, release, packaging route, why), copied into /usr/share/licenses/1bit-os/qrencode/ with ryzenadj's; each package's own copyright file goes to /usr/share/doc/<pkg>/copyright, exactly as Dropbear's does (they are unpacked, not dpkg-installed, so packages.txt cannot name them); os/LICENSES.md lists them. Proved on ryzen (no GPU, no box): * `bash -n`, `sh -n`, `busybox sh -n`, `dash -n` clean on the menu and mkimage.sh. * In an 80x25 tmux pane with TERM=xterm-256color and the *staged* qrencode (its own libqrencode.so.4, nothing installed on the host): the Address line carries the OSC 8 sequence (`\033]8;;http://192.168.50.100:13305\033\\...`), `u` draws the code, entry 8 does the same, `a` toggles ASCII, and any key returns. With TERM=linux the same pane has **zero** OSC 8 sequences and the URL as plain text. * The console-drawn code is the URL: the half-block rows as captured from the pane, turned back into a module matrix, are identical to `qrencode -t ASCII`'s matrix for the same URL (0 differing modules of 725), and `zbarimg` on that matrix rendered as an image reads `http://192.168.50.100:13305`. * The mkimage staging snippet was run against the unpacked .debs: /bin/qrencode lands with libqrencode.so.4, libpng16, libz, libm, libc, and that copy runs with LD_LIBRARY_PATH pointed at the staged tree only. * init.sh's restart loop is untouched; its pty proof (13 checks: crash/stty sane, Ctrl-C, q, and #582's PS1) still passes with the new frames. Also fixed while proving: the QR screen's hint line passed three arguments to a two-`%s` printf, which a POSIX printf repeats - it printed a second, garbled line under the code. Caught on the pane before it ever reached hardware. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bong-water-water-bong
added a commit
that referenced
this pull request
Oct 26, 2026
…R code to scan (#600) The owner, looking at c10's menu over JetKVM: "you need to make the link for the url clickable also". A video console cannot be clicked, so this does both halves (addendum to the TTUI brief): 1. OSC 8 hyperlinks. The Address in the header is now wrapped in `\033]8;;<url>\033\\<url>\033]8;;\033\\`, which any modern terminal (ssh, a serial console in a terminal app) renders as a clickable link. It is guarded on TERM: the VT reports TERM=linux and never sees the escape, so the raw console shows the URL as plain text instead of stray characters. 2. A QR code on the physical console. `u`, or menu entry 8 "Phone", draws the web app URL with `qrencode -t ANSIUTF8` (half blocks: this URL's 29x29 modules with margin come to 17 text rows and fit an 80x25 screen), the URL underneath (still a link where the terminal supports it), and `a` switches to `-t ASCII` for a console font without the block glyphs - with a note when that form needs a taller screen than the console has. The header carries the hint: "On a phone press u". qrencode ships with the image: libqrencode 4.1.1 (LGPL-2.1-or-later). It rides the path Dropbear and mtools already use - mkimage.sh unpacks the Ubuntu qrencode and libqrencode4 .debs into ~/.cache/1bit-os/tools/x on first use, copies /usr/bin/qrencode to /bin/qrencode and resolves what it links with ldd under that tree (libqrencode.so.4 from that package, plus libpng16/libz/libm/libc, which the image already carries or takes from Ubuntu). Licences: os/licenses/qrencode/LICENSE (the LGPL-2.1 text) and SOURCE (project, release, packaging route, why), copied into /usr/share/licenses/1bit-os/qrencode/ with ryzenadj's; each package's own copyright file goes to /usr/share/doc/<pkg>/copyright, exactly as Dropbear's does (they are unpacked, not dpkg-installed, so packages.txt cannot name them); os/LICENSES.md lists them. Proved on ryzen (no GPU, no box): * `bash -n`, `sh -n`, `busybox sh -n`, `dash -n` clean on the menu and mkimage.sh. * In an 80x25 tmux pane with TERM=xterm-256color and the *staged* qrencode (its own libqrencode.so.4, nothing installed on the host): the Address line carries the OSC 8 sequence (`\033]8;;http://192.168.50.100:13305\033\\...`), `u` draws the code, entry 8 does the same, `a` toggles ASCII, and any key returns. With TERM=linux the same pane has **zero** OSC 8 sequences and the URL as plain text. * The console-drawn code is the URL: the half-block rows as captured from the pane, turned back into a module matrix, are identical to `qrencode -t ASCII`'s matrix for the same URL (0 differing modules of 725), and `zbarimg` on that matrix rendered as an image reads `http://192.168.50.100:13305`. * The mkimage staging snippet was run against the unpacked .debs: /bin/qrencode lands with libqrencode.so.4, libpng16, libz, libm, libc, and that copy runs with LD_LIBRARY_PATH pointed at the staged tree only. * init.sh's restart loop is untouched; its pty proof (13 checks: crash/stty sane, Ctrl-C, q, and #582's PS1) still passes with the new frames. Also fixed while proving: the QR screen's hint line passed three arguments to a two-`%s` printf, which a POSIX printf repeats - it printed a second, garbled line under the code. Caught on the pane before it ever reached hardware. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Owner, first hardware boot of candidate 5 (26 Oct): "I couldn't do anything" on the console. The screenshot shows why: after the banner, every kernel line (USB connect/disconnect from the KVM, amdgpu notes) kept printing over the shell, because the cmdline boots with
ignore_loglevel loglevel=7so the boot is fully visible; and the shell's prompt was BusyBox's bare#, invisible under that stream.os/mini/init.sh:ignore_logleveloff and console level to emergencies only (/sys/module/printk/parameters/ignore_loglevel,/proc/sys/kernel/printk=1 4 1 7). Both are runtime-writable;dmesgkeeps everything and the serial log already carries the boot, which the VM gate reads./etc/profile) getPS1='1bit-os:\w # '.The gate's prompt matchers look for a trailing
#(wait_prompt:/ #,# $; the run() prompt regex ends in#), so the new prompt is accepted.Goes into the next package of the candidate 6 engine (package-only is correct for an init.sh change on a freshly built engine; the acceptance checks of build-candidate6.sh still apply).
🤖 Generated with Claude Code