Kiosk Mode
How the KN-86 boots straight into the nOSh runtime with no login prompt, no display manager, and a read-only root filesystem — and how a developer drops out of that mode for debugging. Read this if you are configuring auto-login, changing the read-only-root layout, disabling a service, or debugging “why won’t my edit to /etc/... survive a reboot.”
boot-and-systemd.md— service ordering that this kiosk model assumes.system-image-build.md— thestage-kn86-baseandstage-kn86-runtimestages where these settings are baked in.update-system.md— how updates land on the read-only root.../hardware/build-specification.md§5 — Developer-mode vs Production-mode boot flag.../../adr/ADR-0011-device-firmware-update-system.md— partition layout, including the/home/sharedsave partition this doc references.
Auto-login
Section titled “Auto-login”There is no display manager (no LightDM, no GDM, no SDDM). The system boots to multi-user.target, then a getty override on tty1 auto-logs the kiosk user.
# /etc/systemd/system/getty@tty1.service.d/kn86-autologin.conf[Service]ExecStart=ExecStart=-/sbin/agetty --autologin nosh --noclear %I $TERMType=idleThe nosh user is created by stage-kn86-base of system-image-build.md:
useradd -m -s /bin/bash -G video,audio,input,dialout noshvideo for KMSDRM framebuffer access, audio (legacy — actual audio is via the Pico, not Linux), input for /dev/input/event*, dialout for /dev/serial0 to talk to the Pico. No sudo, no shell access in production mode (see “Recovery / dev-mode toggle” below).
The user’s ~/.profile chains directly into the nOSh runtime systemd unit:
# /home/nosh/.profile (snippet)if [[ "$(tty)" == "/dev/tty1" ]] && [[ -z "$KN86_BOOTED" ]]; then export KN86_BOOTED=1 exec systemctl --user start kn86-noshfiThe combination of auto-login + the ~/.profile chain delivers the user to nOSh in <8 s from kernel handoff. The multi-user.target is the right systemd target — graphical.target would pull in display-manager assumptions we explicitly don’t want.
Read-only root filesystem
Section titled “Read-only root filesystem”The rootfs (/dev/mmcblk0p4 or p5, depending on active slot per ../../adr/ADR-0011-device-firmware-update-system.md) is mounted read-only by /etc/fstab in the slot:
/dev/disk/by-label/rootfs-A / ext4 ro,noatime,errors=remount-ro 0 1/dev/disk/by-label/bootfs-A /boot vfat ro,noatime 0 2/dev/disk/by-label/share /home/shared ext4 rw,noatime 0 2tmpfs /tmp tmpfs rw,nosuid,nodev,size=64m 0 0tmpfs /var/log tmpfs rw,nosuid,nodev,size=32m 0 0Ephemeral writes (anything that systemd, journald, or nOSh wants to scribble during a session) land on tmpfs at /tmp and /var/log. They evaporate on reboot. The only writable persistent location is /home/shared (p6) — the Universal Deck State partition.
We use pi-gen’s built-in overlayroot support (an overlay-root overlay that mounts an overlayfs over the root, backed by tmpfs) for any case where a runtime tool insists on writing to a non-tmpfs path under /. This is enabled in stage-kn86-base of system-image-build.md and handled by adding init=/init-overlay to cmdline.txt for that slot. We prefer the explicit fstab ro mount above when possible (smaller surface, no overlayfs surprise behaviour); overlayroot is the fallback for code that won’t honour ro.
Save partition layout
Section titled “Save partition layout”/home/shared (p6 from ADR-0011) is the only persistent writable location the operator’s data ever touches. It carries:
/home/shared/├── deckstate.bin # Universal Deck State (operator handle, credits,│ # reputation, cartridge_history bitfield, phase chain)├── boot-success-token # 60-second sentinel for ADR-0011 tryboot commit├── kn86-mode.txt -> /boot/kn86-mode.txt # symlink for runtime read└── (cart-side per-cart save data lives on each cart's own SD; not here)The image-build pipeline never writes p6, the flasher never writes p6, the updater image never writes p6. Only nOSh (running as user nosh) writes p6. This isolation is the load-bearing reason ADR-0011 §Risks #5 holds — operator state survives every conceivable update / re-flash / slot swap.
Per-cartridge save data lives on the cart’s own SD per ../../adr/ADR-0019-cartridge-storage-and-form-factor.md — never on /home/shared.
Disabling unused services
Section titled “Disabling unused services”stage-kn86-base of system-image-build.md masks the following systemd units (the -base flavor of pi-gen pulls some in by default that we don’t want):
| Unit | Reason |
|---|---|
getty@tty2.service … getty@tty6.service | Production mode has only tty1 (autologin into nOSh). |
bluetooth.service | Bluetooth is unused; the BT module is also disabled at the device-tree level (device-tree-overlays.md disable-bt) to free UART0. |
avahi-daemon.service | mDNS exposes kn86.local on the network; in production mode we don’t want network discoverability. Re-enabled in dev mode. |
triggerhappy.service | Pi OS Lite ships a generic input-event-to-shell-command bridge; conflicts with nOSh’s exclusive ownership of evdev. |
keyboard-setup.service | Pi OS Lite tries to apply Linux console keymap; nOSh owns the keyboard, the Linux console is never user-visible. |
systemd-resolved.service | Production mode has no DNS use case post-boot; static /etc/resolv.conf (empty) is sufficient. |
wpa_supplicant.service | Wi-Fi is dev-mode-only; production has no network use case post-boot. Re-enabled in dev mode. |
ssh.service | Production has no SSH; the service is masked. Dev mode enables it. |
Mask command for reference (run in stage-kn86-base/run-chroot.sh):
systemctl mask bluetooth.service avahi-daemon.service triggerhappy.service \ keyboard-setup.service systemd-resolved.service \ wpa_supplicant.service ssh.servicefor tty in 2 3 4 5 6; do systemctl mask "getty@tty${tty}.service"doneRecovery / dev-mode toggle
Section titled “Recovery / dev-mode toggle”There is no in-device path to drop to a shell from production mode. By design — a kiosk user cannot click their way out of the kiosk, and an attacker with physical access can’t trick the device into a shell either.
Toggling mode
Section titled “Toggling mode”Mode is set by a single file read at boot: /boot/kn86-mode.txt on p1 (the common boot region). Contents:
mode=productionor
mode=developmentstage-kn86-runtime of system-image-build.md reads this file in kn86-display-init.service (early in boot) and exports KN86_MODE for the rest of the unit graph. nOSh re-reads it once at start.
Dev mode unlocks
Section titled “Dev mode unlocks”| Concern | Production | Development |
|---|---|---|
/etc/getty@tty1 autologin | nosh user, no shell exit | nosh user, but Ctrl+C drops to bash |
getty@tty2 … tty6 | masked | enabled (alt + arrow on a USB keyboard switches; the KN-86’s mech keeb does not have alt + arrow, so this is effectively bench-keyboard-only) |
ssh.service | masked | enabled on the Wi-Fi interface |
wpa_supplicant.service | masked | enabled |
avahi-daemon.service | masked | enabled (kn86.local resolvable) |
| journald | volatile (/run/log/journal) | persistent (/var/log/journal, capped 100 MB) |
| Rootfs | read-only | read-only by default; mount -o remount,rw / works since the operator is root in dev |
| nEmacs REPL filesystem access | read-only | read-write to /home/shared and mounted carts |
Switching modes
Section titled “Switching modes”- Dev → Prod: edit
/boot/kn86-mode.txt(mount p1 from any host with an SD reader, or via the updater MSC mount from ADR-0011), setmode=production, reboot. - Prod → Dev: physical SD access required. No in-device path. This is deliberate — production mode should not be re-enableable into dev mode without somebody opening the case and pulling the SD. The Pelican shell makes this a deliberate ritual, not a slip.
Bootloader-level recovery
Section titled “Bootloader-level recovery”If the SD itself is corrupt and the system won’t boot to the point of mounting p1, the only recovery path is to re-image the SD using a fresh .img from the system-image-build.md pipeline. There is no bench-side rescue partition baked into the SD layout — ADR-0011’s six-partition table does not reserve one. /home/shared (p6) survives any re-image of slots A and B (the flasher and provisioning script never touch p6 — see update-system.md), so operator state survives bootloader-level recovery.
For total-loss situations (corrupt p1 or a totally bad card), reseed the SD from .img and on first boot of the new image nOSh will see an empty /home/shared and bootstrap a fresh deck state.