Skip to content

os: a usable console - kernel messages off the shell, a visible prompt - #582

Merged
bong-water-water-bong merged 1 commit into
mainfrom
1bit/os-console-prompt
Oct 26, 2026
Merged

bong-water-water-bong merged 1 commit into
mainfrom
1bit/os-console-prompt

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

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=7 so the boot is fully visible; and the shell's prompt was BusyBox's bare # , invisible under that stream.

os/mini/init.sh:

  • once the system is up, right before the console shell starts: ignore_loglevel off and console level to emergencies only (/sys/module/printk/parameters/ignore_loglevel, /proc/sys/kernel/printk = 1 4 1 7). Both are runtime-writable; dmesg keeps everything and the serial log already carries the boot, which the VM gate reads.
  • the tty1 shell, the ttyS0 shell and ssh logins (/etc/profile) get PS1='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

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>
@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Prompt Visibility

The change introduces a new PS1 prompt for console and serial shells, but does not ensure that the prompt is properly escaped or handled in all terminal environments. This could lead to issues with prompt recognition or display in non-standard terminals or when using certain terminal emulators.

( while :; do setsid -c env PS1='1bit-os:\w # ' /bin/sh -i < /dev/tty1 > /dev/tty1 2>&1; sleep 2; done ) &
Kernel Log Level Configuration

The code disables ignore_loglevel and sets the console log level to emergencies only. While this addresses the issue of kernel messages overwriting the shell prompt, it assumes that these sysfs and procfs writes will succeed and that the system will behave as expected. If these writes fail, kernel messages may still overwrite the prompt, leading to an unusable console.

echo N > /sys/module/printk/parameters/ignore_loglevel 2>/dev/null
echo "1 4 1 7" > /proc/sys/kernel/printk 2>/dev/null

@bong-water-water-bong
bong-water-water-bong merged commit c248c18 into main Oct 26, 2026
8 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the 1bit/os-console-prompt branch October 26, 2026 02:45
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>
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