diff --git a/.config b/.config
index 9768a40..9c0a4c3 100644
--- a/.config
+++ b/.config
@@ -1,6 +1,6 @@
#
# Automatically generated file; DO NOT EDIT.
-# Buildroot -ga43e95b-dirty Configuration
+# Buildroot -g674592d-dirty Configuration
#
BR2_HAVE_DOT_CONFIG=y
BR2_HOST_GCC_AT_LEAST_4_9=y
@@ -461,7 +461,7 @@ BR2_TARGET_GENERIC_GETTY_BAUDRATE_KEEP=y
# BR2_TARGET_GENERIC_GETTY_BAUDRATE_57600 is not set
# BR2_TARGET_GENERIC_GETTY_BAUDRATE_115200 is not set
BR2_TARGET_GENERIC_GETTY_BAUDRATE="0"
-BR2_TARGET_GENERIC_GETTY_TERM="vt100"
+BR2_TARGET_GENERIC_GETTY_TERM="linux"
BR2_TARGET_GENERIC_GETTY_OPTIONS=""
BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW=y
BR2_SYSTEM_DHCP="eth0"
diff --git a/README-BUILDROOT-ORIGINAL b/README-BUILDROOT-ORIGINAL
new file mode 100644
index 0000000..143b476
--- /dev/null
+++ b/README-BUILDROOT-ORIGINAL
@@ -0,0 +1,26 @@
+Buildroot is a simple, efficient and easy-to-use tool to generate embedded
+Linux systems through cross-compilation.
+
+The documentation can be found in docs/manual. You can generate a text
+document with 'make manual-text' and read output/docs/manual/manual.text.
+Online documentation can be found at https://buildroot.org/docs.html
+
+To build and use the buildroot stuff, do the following:
+
+1) run 'make menuconfig'
+2) select the target architecture and the packages you wish to compile
+3) run 'make'
+4) wait while it compiles
+5) find the kernel, bootloader, root filesystem, etc. in output/images
+
+You do not need to be root to build or run buildroot. Have fun!
+
+Buildroot comes with a basic configuration for a number of boards. Run
+'make list-defconfigs' to view the list of provided configurations.
+
+Please feed suggestions, bug reports, insults, and bribes back to the
+buildroot mailing list: buildroot@buildroot.org
+You can also find us on #buildroot on OFTC IRC.
+
+If you would like to contribute patches, please read
+https://buildroot.org/manual.html#submitting-patches
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7969f3c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+# Mentalnet GNU/Linux
+
+A light, TTY-only GNU/Linux distribution for 90s Intel Pentium-class
+(i586) machines, built with [Buildroot](https://buildroot.org).
+
+
+
+
+
+The ISO is a live CD: the entire system runs from the CD with a
+read-only root filesystem, and the same CD carries a built-in hard
+disk installer (`mentalnet-install`).
+
+## Current release
+
+**R1 "FirstStorm"** — named after DECO*27's
+*Chūlán ~First Storm~* (初嵐~First Storm~).
+
+## Versioning
+
+- Major releases bump the `R` number (`R1`, `R2`, ...).
+- Minor releases keep the `R` base version and use the codename as
+ the minor version.
+- All codenames are taken from Vocaloid, Hatsune Miku or Kasane Teto
+ songs.
+
+| Release | Codename | Song | Notes |
+|---------|----------|------|-------|
+| R1 | Mesmerizer | *Mesmerizer* — 32ki (Hatsune Miku & Kasane Teto) | Initial release |
+| R1 | **FirstStorm** | *Chūlán ~First Storm~ (初嵐~First Storm~)* — DECO*27 | **Current** — swap-free installer, true i586 support, terminal/input hardening, interrupt-storm fixes |
+
+## Quick facts
+
+| | |
+|---|---|
+| Login | `root` / `mnlinux` |
+| Hostname | `mentalnet` |
+| Kernel | `6.12.104-mentalnet-intel32` |
+| Media | Live CD with built-in installer |
+| CPU | i586 (Pentium / Pentium MMX) or any newer x86 |
+| RAM | 128 MB minimum (64 MB usually works) |
+
+## Security after install
+
+The default root password is **`mnlinux`** — please change it after
+installing:
+
+```
+passwd
+```
+
+Then, for day-to-day use, create a regular user and give it sudo
+privileges (sudo is included in the build):
+
+```
+adduser myuser
+```
+
+You have two options to grant sudo:
+
+- Add the user to the `sudo` group, which the shipped sudoers file
+ already trusts:
+
+ ```
+ addgroup myuser sudo
+ ```
+
+- Or, if you prefer the classic `wheel` group, enable it with
+ `visudo` (uncomment the `%wheel` line) and add the user to it:
+
+ ```
+ visudo
+ addgroup myuser wheel
+ ```
+
+**Verify that sudo works for the new user before locking root** —
+log in as the user and run, for example, `sudo ls /`.
+
+Once sudo is confirmed working, lock the root account so it cannot
+be accessed (SSH included):
+
+```
+passwd -l root
+```
+
+## Documentation
+
+The full install and test guide — hardware requirements, QEMU
+testing, the install walkthrough and troubleshooting — lives in
+[`board/mentalnet/INSTALL-GUIDE.md`](board/mentalnet/INSTALL-GUIDE.md).
diff --git a/board/mentalnet/INSTALL-GUIDE.md b/board/mentalnet/INSTALL-GUIDE.md
index 4bd9a9b..3314df3 100644
--- a/board/mentalnet/INSTALL-GUIDE.md
+++ b/board/mentalnet/INSTALL-GUIDE.md
@@ -57,6 +57,25 @@ Services started at boot:
DNS from DHCP works on the live CD too: `/etc/resolv.conf` is a
symlink into tmpfs that `udhcpc` writes through.
+### Default kernel command line
+
+Both boot paths (live CD and installed system) ship with:
+
+```
+i8042.nomux i8042.noloop noapic acpi=off i8042.dumbkbd=1
+```
+
+| Option | Purpose |
+|--------|---------|
+| `i8042.nomux` / `i8042.noloop` | skip the two i8042 controller probe steps most known to confuse old or emulated keyboard controllers |
+| `noapic` | use the legacy 8259 PIC instead of the IO-APIC - avoids IRQ routing/storm issues on era boards (which mostly predate the IO-APIC anyway) |
+| `acpi=off` | era BIOSes (1994-1997) have broken or absent ACPI; skipping it avoids the table-parsing minefield. Power management is disabled (era-appropriate) |
+| `i8042.dumbkbd=1` | do not program keyboard LEDs or expect controller ACKs - the conservative mode for flaky 8042 clones (Caps Lock light will not toggle) |
+
+To troubleshoot a specific machine, edit the boot entry at the GRUB
+menu with `e`, change the `linux` line, and boot with `Ctrl-X` or
+`F10`.
+
---
## 3. Building from source
@@ -84,7 +103,9 @@ faster. After editing kernel fragments or the overlay, plain `make`
picks the changes up.
Every build also publishes the ISO under a unique name and prints its
-SHA256 at the end of the build log:
+SHA256 at the end of the build log. Release builds use the
+`mentalnet-gnulinux-intel32--.iso` convention (for
+example `mentalnet-gnulinux-intel32-R1-GhostRule.iso`):
```
Published: .../output/images/mentalnet-gnulinux-intel32-20260910-212811.iso
@@ -254,6 +275,8 @@ The installed GRUB menu has a 5s timeout and boots automatically.
| **How to tell which build a VM is actually running** | At the login prompt the build stamp is printed (`Mentalnet GNU/Linux build YYYYMMDD-HHMMSS`); inside the guest check `cat /etc/os-release` (`BUILD_ID=`) or `cat /proc/version`. Useful kernel build markers: `#3 ... 18:45:02` predates the rtl8139 fix, `#4 ... 20:24:17` is the first build with 8139cp. |
| **Booting the CD runs an older install instead of the live system** | Fixed: the live CD's GRUB core hardcodes `root=(cd)` (the El Torito boot CD itself), so the boot chain never touches any attached disk and an existing Mentalnet install can no longer hijack it. On builds older than this fix, the CD's GRUB searched for `/boot/bzImage` (or a marker file), and since BIOS enumerates disks before the CD, the search landed on the installed system first. |
| **Boots on Pentium Pro/i686 but hangs at kernel start on Pentium/Pentium MMX (i586)** | Fixed: the kernel is now built for `CONFIG_M586` with `CONFIG_X86_GENERIC` (no CMOV instructions). Builds before this fix used `i386_defconfig`'s `CONFIG_M686` default, which emits CMOV - instant invalid-opcode crash on real i586 CPUs. |
+| **Terminal garbled / typed text repeats after a full-screen app (htop, neofetch)** | Fixed: builds before this change set `TERM=vt100` on the VGA console, so full-screen apps restored the terminal with the wrong sequences. Recovery on any running system: run `reset`. |
+| **Phantom keystrokes / junk commands during or after the install** | Root cause: interrupt storms from APIC/ACPI quirks on era hardware (and emulated boards) starving the keyboard controller; the long silent copy phase also invited typed-ahead input that was replayed as shell commands. Fixed builds ship `noapic acpi=off i8042.dumbkbd=1` on both boot paths (see the kernel command line section) and show per-directory progress with an input drain before finishing. On older builds: let it finish, then run `reset`; avoid typing during the copy; `Ctrl-Q` if the console seems frozen. |
| **Proxmox: VM boots an older build despite uploading a new ISO** | Proxmox keeps every upload as a separate storage volume, and a VM's CD/DVD drive points at a specific **volume** — renaming or re-uploading a file never updates an existing drive (checksums of the new file do not help either). Delete the stale volume, upload the unique-named ISO from the build log, attach it in Hardware, and check Boot Order (an installed disk can also shadow the CD). |
| GRUB menu does not appear, or the machine reboots before booting, in QEMU | Try more RAM (`-m 256`). Memory pressure during development was the culprit more than once. |
| Kernel panic: `Unable to mount root fs` | The CD drive is on an unsupported controller. The kernel targets PIIX-era IDE/ATA; modern SATA-only setups are out of scope. |
diff --git a/board/mentalnet/overlay/usr/sbin/mentalnet-install b/board/mentalnet/overlay/usr/sbin/mentalnet-install
index 6891043..ddaedea 100755
--- a/board/mentalnet/overlay/usr/sbin/mentalnet-install
+++ b/board/mentalnet/overlay/usr/sbin/mentalnet-install
@@ -19,8 +19,10 @@ msg() { printf '%s\n' "$*"; }
warn() { printf 'WARNING: %s\n' "$*"; }
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
-# make sure a failed run never leaves the target disk mounted
+# make sure a failed run never leaves the target disk mounted and
+# always restores the terminal state
cleanup() {
+ stty sane 2>/dev/null
grep -q " $MNT " /proc/mounts 2>/dev/null && umount "$MNT" 2>/dev/null
[ -n "${TMNT:-}" ] && umount /mnt 2>/dev/null
return 0
@@ -33,6 +35,10 @@ for t in fdisk mkfs.ext4 grub-install; do
command -v "$t" >/dev/null 2>&1 || die "required tool '$t' not found"
done
+# keep the console usable even if the user hits Ctrl-S (XOFF) by
+# accident while the install is running
+stty -ixon 2>/dev/null
+
banner() {
clear 2>/dev/null
msg ""
@@ -134,15 +140,26 @@ mount -t ext4 "$ROOTPART" "$MNT" || die "could not mount $ROOTPART"
msg " Copying the system (this takes a while) ..."
-# NOTE: one single tar invocation per side. busybox tar stops reading at
-# the first end-of-archive marker, so piping several archives through
-# '-' would silently transfer only the first directory.
+# NOTE: one complete archive->extraction pair per directory. busybox tar
+# stops reading at the first end-of-archive marker, so a single archive
+# made of several directories would transfer only the first one; a pipe
+# of several archives would leave the reader hanging after the first
+# EOF. Per-directory pairs are safe and give progress feedback on slow
+# machines.
DIRS=
for d in bin boot etc home lib lib32 lib64 media opt root sbin srv usr var; do
[ -e "/$d" ] && DIRS="$DIRS $d"
done
-(cd / && tar -cf - $DIRS) | (cd "$MNT" && tar -xpf -) \
- || die "copying failed"
+
+# typing during the long copy used to be echoed and buffered, then
+# replayed as shell commands after the installer exited; mute it
+stty -echo 2>/dev/null
+for d in $DIRS; do
+ msg " Copying: /$d"
+ (cd / && tar -cf - "$d") | (cd "$MNT" && tar -xpf -) \
+ || { stty echo 2>/dev/null; die "copying /$d failed"; }
+done
+stty echo 2>/dev/null
# mount points that live in tmpfs on the CD
mkdir -p "$MNT/proc" "$MNT/sys" "$MNT/dev" "$MNT/tmp" "$MNT/run" "$MNT/mnt"
@@ -175,13 +192,24 @@ sed "s|@ROOTDEV@|$ROOTPART|" /usr/share/mentalnet/grub-disk.cfg \
|| die "could not write grub.cfg"
msg " Installing GRUB to the MBR of $DEV ..."
-grub-install --boot-directory="$MNT/boot" "$DEV" >/dev/null 2>&1 \
+grub-install --boot-directory="$MNT/boot" "$DEV" \
|| die "grub-install failed"
sync
umount "$MNT" || umount -f "$MNT" || die "could not unmount $MNT"
[ -n "$TMNT" ] && umount /mnt
+# discard anything the user typed ahead during the (long) copy: it was
+# buffered in the tty and would otherwise be replayed as shell commands
+# after this script exits
+stty -icanon min 0 time 0 2>/dev/null
+i=0
+while [ "$i" -lt 512 ] && read -r junk; do
+ i=$((i + 1))
+done
+stty sane 2>/dev/null
+
+clear 2>/dev/null
msg ""
msg " ============================================================"
msg " Installation complete!"
diff --git a/board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg b/board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg
index 3edef39..5245a66 100644
--- a/board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg
+++ b/board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg
@@ -2,5 +2,5 @@ set default="0"
set timeout="5"
menuentry "Mentalnet GNU/Linux" {
- linux /boot/bzImage root=@ROOTDEV@ rootwait rw console=ttyS0,9600n8 console=tty0
+ linux /boot/bzImage root=@ROOTDEV@ rootwait rw console=ttyS0,9600n8 console=tty0 i8042.nomux i8042.noloop noapic acpi=off i8042.dumbkbd=1
}
diff --git a/board/mentalnet/post-build.sh b/board/mentalnet/post-build.sh
index a148f38..1a13dbb 100755
--- a/board/mentalnet/post-build.sh
+++ b/board/mentalnet/post-build.sh
@@ -15,23 +15,26 @@ set -e
TARGET_DIR="${1}"
#STAMP="$(date +%Y%m%d-%H%M%S)"
-STAMP='R1 "RollingGirl-SE"'
+RELEASE="R1"
+CODENAME="FirstStorm"
+STAMP="${RELEASE} \"${CODENAME}\"" # pretty form: R1 "GhostRule"
+STAMP_SAFE="${RELEASE}-${CODENAME}" # sanitized: R1-GhostRule
IMAGES_DIR="$(dirname "${TARGET_DIR}")/images"
# record the stamp for post-image.sh
mkdir -p "${IMAGES_DIR}"
-echo "${STAMP}" > "${IMAGES_DIR}/.mn-build-stamp"
+echo "${STAMP_SAFE}" > "${IMAGES_DIR}/.mn-build-stamp"
# /etc/os-release (regular file lives at /usr/lib/os-release; /etc/os-release
# is a symlink to it) - Mentalnet branding, Buildroot defaults dropped
OS_RELEASE="${TARGET_DIR}/usr/lib/os-release"
cat > "${OS_RELEASE}" <