diff --git a/.config b/.config index da5fdd4..23b3116 100644 --- a/.config +++ b/.config @@ -1,6 +1,6 @@ # # Automatically generated file; DO NOT EDIT. -# Buildroot -g430ecb4-dirty Configuration +# Buildroot -g0d30096-dirty Configuration # BR2_HAVE_DOT_CONFIG=y BR2_HOST_GCC_AT_LEAST_4_9=y @@ -914,7 +914,17 @@ BR2_PACKAGE_MAKE=y # BR2_PACKAGE_DAVFS2 is not set # BR2_PACKAGE_DOSFSTOOLS is not set # BR2_PACKAGE_DUST is not set -# BR2_PACKAGE_E2FSPROGS is not set +BR2_PACKAGE_E2FSPROGS=y +# BR2_PACKAGE_E2FSPROGS_DEBUGFS is not set +# BR2_PACKAGE_E2FSPROGS_E2IMAGE is not set + +# +# e2scrub needs bash, coreutils, lvm2, and util-linux +# +# BR2_PACKAGE_E2FSPROGS_E4DEFRAG is not set +BR2_PACKAGE_E2FSPROGS_FSCK=y +# BR2_PACKAGE_E2FSPROGS_FUSE2FS is not set +# BR2_PACKAGE_E2FSPROGS_RESIZE2FS is not set # BR2_PACKAGE_E2TOOLS is not set # BR2_PACKAGE_ECRYPTFS_UTILS is not set # BR2_PACKAGE_EROFS_UTILS is not set @@ -4565,7 +4575,7 @@ BR2_PACKAGE_SYSTEMD_BOOTCHART_ARCH_SUPPORTS=y # BR2_PACKAGE_TPM2_TOTP is not set # BR2_PACKAGE_UNSCD is not set BR2_PACKAGE_UTIL_LINUX=y -# BR2_PACKAGE_UTIL_LINUX_LIBBLKID is not set +BR2_PACKAGE_UTIL_LINUX_LIBBLKID=y # BR2_PACKAGE_UTIL_LINUX_LIBFDISK is not set # BR2_PACKAGE_UTIL_LINUX_LIBMOUNT is not set # BR2_PACKAGE_UTIL_LINUX_LIBSMARTCOLS is not set @@ -4713,7 +4723,7 @@ BR2_TARGET_GRUB2_I386_PC=y BR2_TARGET_GRUB2_BOOT_PARTITION="hd0,msdos1" BR2_TARGET_GRUB2_BUILTIN_MODULES_PC="boot linux ext2 fat squash4 part_msdos part_gpt normal biosdisk iso9660 search search_fs_file" BR2_TARGET_GRUB2_BUILTIN_CONFIG_PC="board/mentalnet/grub-embedded.cfg" -# BR2_TARGET_GRUB2_INSTALL_TOOLS is not set +BR2_TARGET_GRUB2_INSTALL_TOOLS=y BR2_PACKAGE_SHIM_ARCH_SUPPORTS=y # BR2_TARGET_SHIM is not set # BR2_TARGET_SYSLINUX is not set diff --git a/board/mentalnet/INSTALL-GUIDE.md b/board/mentalnet/INSTALL-GUIDE.md new file mode 100644 index 0000000..b254557 --- /dev/null +++ b/board/mentalnet/INSTALL-GUIDE.md @@ -0,0 +1,265 @@ +# Mentalnet GNU/Linux — Install & Test Guide + +Mentalnet GNU/Linux is a small, TTY-only distribution for 90s Intel +Pentium-class PCs, built with Buildroot 2025.02.17. The ISO is a live +CD: the entire system runs from the CD with a read-only root, and the +same CD carries a built-in hard disk installer (`mentalnet-install`). + +- Kernel: `6.12.104-mentalnet-intel32` (slimmed i386 config) +- Login: `root` / `mnlinux` +- Hostname: `mentalnet` +- Live CD size: ~100 MB + +--- + +## 1. System requirements + +### Running the live CD + +| Component | Requirement | +|-------------|----------------------------------------------------------| +| CPU | i586 (generic Pentium) or any newer x86 | +| RAM | 128 MB minimum (64 MB usually works) | +| CD drive | IDE/ATAPI CD-ROM (PIIX-era ATA controller) | +| Display | VGA text console, PS/2 keyboard/mouse | +| USB | USB keyboards, mice and mass storage (UHCI/OHCI/EHCI) | +| Serial | 8250 UART (kernel log is mirrored to COM1) | +| NICs (opt.) | NE2000-PCI clones (RTL8029 etc.), Intel e100/e1000/e1000e, Realtek 8139, 3Com 3c59x | + +### Installing to a hard disk + +- IDE/ATA disk with at least **400 MB** free (the whole disk is wiped) +- The machine must be able to boot from CD (El Torito, BIOS boot) + +--- + +## 2. What's on the media + +The live system mounts its root filesystem **read-only** from the CD. +Everything writable lives in RAM: + +- `/tmp`, `/run`, `/dev/shm` are tmpfs +- `/var/log`, `/var/run`, `/var/cache`, `/var/spool`, `/var/tmp` + are symlinks into those tmpfs areas + +Services started at boot: + +| Service | Purpose | +|------------|------------------------------------------------| +| DHCP | `eth0` configured via udhcpc | +| syslogd | kernel + system logging (to `/tmp/log`) | +| lighttpd | web server, port 80 | +| dropbear | SSH server, port 22 | +| chronyd | NTP time sync | +| crond | cron daemon | +| iptables | firewall rules | + +DNS from DHCP works on the live CD too: `/etc/resolv.conf` is a +symlink into tmpfs that `udhcpc` writes through. + +--- + +## 3. Building from source + +The build tree is a standard Buildroot 2025.02.17 checkout with the +Mentalnet configuration applied. Install the usual Buildroot host +dependencies (see `docs/manual/prerequisite.txt` in the Buildroot +manual), then: + +``` +make +``` + +Build outputs land in `output/images/`: + +| File | Purpose | +|-------------------|--------------------------------------------------| +| `rootfs.iso9660` | the bootable live CD / installer ISO | +| `bzImage` | the kernel (also inside the ISO and ext2 image) | +| `rootfs.ext2` | 2 GB raw disk image variant (for QEMU testing) | +| `grub-eltorito.img` / `grub.img` | GRUB core images (used by the ISO) | + +A full build takes a while; rebuilds after config changes are much +faster. After editing kernel fragments or the overlay, plain `make` +picks the changes up. + +--- + +## 4. Testing in QEMU + +All commands are run from the Buildroot tree root. Exit a +`-nographic` session with `Ctrl-A x`. + +### 4.1 Live CD, graphical window + +``` +qemu-system-i386 -m 128 -cdrom output/images/rootfs.iso9660 +``` + +Expect the GRUB menu ("Mentalnet GNU/Linux", 10s timeout), a kernel +boot and a login prompt on the VGA console. + +### 4.2 Headless (serial console) + +``` +qemu-system-i386 -m 128 -nographic \ + -kernel output/images/bzImage \ + -append "console=ttyS0 root=/dev/sr0 ro" \ + -cdrom output/images/rootfs.iso9660 +``` + +This bypasses GRUB and shows the whole boot on the terminal. + +### 4.3 Low-memory margin test + +Same as 4.2 but with `-m 64`. The live system should still come up. + +### 4.4 NE2000 network test + +``` +qemu-system-i386 -m 128 -nographic -nic model=ne2k_pci \ + -kernel output/images/bzImage \ + -append "console=ttyS0 root=/dev/sr0 ro" \ + -cdrom output/images/rootfs.iso9660 +``` + +After login, `ip link` should show a configured `eth0`. + +### 4.5 Disk image variant + +``` +qemu-system-i386 -m 128 -nographic \ + -kernel output/images/bzImage \ + -append "console=ttyS0 root=/dev/sda1 rootwait rw" \ + -drive file=output/images/rootfs.ext2,format=raw,if=ide +``` + +(`rootfs.ext2` also contains `/boot/bzImage` and a GRUB config for +disk boot.) + +### 4.6 Full installer end-to-end test + +``` +# 1. create a throwaway disk +qemu-img create -f qcow2 /tmp/mn-test.qcow2 512M + +# 2. boot the live CD with the test disk attached +qemu-system-i386 -m 128 -nographic \ + -kernel output/images/bzImage \ + -append "console=ttyS0 root=/dev/sr0 ro" \ + -cdrom output/images/rootfs.iso9660 \ + -drive file=/tmp/mn-test.qcow2,format=qcow2,if=ide +``` + +Log in and run `mentalnet-install`: + +``` +Disk to install to (e.g. sda): sda +Type YES to continue: YES +Create a swap partition? [y/N]: y +``` + +When it prints `Installation complete!`: + +``` +poweroff +``` + +Then boot **only** the test disk (no `-cdrom`, no `-kernel`) — this +exercises the installed GRUB in the MBR: + +``` +qemu-system-i386 -m 128 -nographic \ + -drive file=/tmp/mn-test.qcow2,format=qcow2,if=ide +``` + +The kernel log (mirrored to the serial console by the installed +config) should show `EXT4-fs (sda1): mounted ... r/w` and +`Run /sbin/init as init process`. + +--- + +## 5. Installing to a hard disk (real hardware) + +> **WARNING: the installer destroys ALL data on the target disk +> without further notice.** Double-check the disk name before +> confirming. + +1. Boot the machine from the CD (El Torito boot). +2. Log in as `root` (password `mnlinux`). +3. Run: + + ``` + mentalnet-install + ``` + +4. Answer the prompts: + - **Disk to install to** — e.g. `sda` (the installer lists + detected disks with their sizes first) + - **Type YES** — destroys everything on that disk + - **Create a swap partition? [y/N]** — recommended on machines + with little RAM; the swap is sized at 2x RAM, capped at 512 MB + +5. The installer then: + - writes a fresh MBR partition table (root partition starts at + sector 2048, leaving room for the GRUB core image) + - creates an ext4 filesystem labelled `rootfs` (+ swap if chosen) + - copies the whole system from the CD + - generates `/etc/fstab` + - installs GRUB to the MBR and writes the boot menu + +6. When it prints `Installation complete!`: + + ``` + poweroff + ``` + + Remove the CD and power on — the machine now boots from its own + disk. + +The installed GRUB menu has a 5s timeout and boots automatically. + +--- + +## 6. Post-install notes + +- The installed root filesystem is **read-write**, so all services + behave normally: chrony keeps its drift file, dropbear generates + SSH host keys on first boot, logs persist under `/var/log`. +- DHCP DNS works the same way as on the live CD (`resolv.conf` + symlink into tmpfs). +- Kernel boot messages are mirrored to `COM1` (ttyS0) — handy for + debugging headless or semi-broken machines. +- Login is `root` / `mnlinux` (same as the live system). + +--- + +## 7. Troubleshooting + +| Symptom | Likely cause / fix | +|---------|--------------------| +| 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. | +| `Remounting root filesystem read-write ... failed` on live boot | Cosmetic. The CD root is read-only by design; all writes go to tmpfs. | +| Installer: `/dev/sdX1 did not appear after partitioning` | The kernel was still re-reading the partition table. Simply run the installer again. | +| Installer: `disk is too small` | At least 400 MB is needed. | +| Forgot the password | It is `mnlinux` (set at build time in the Buildroot config). | +| No network | Check the NIC against the supported list in section 1. QEMU: use `-nic model=ne2k_pci` or the default e1000. | + +--- + +## 8. Maintainer file map + +Everything Mentalnet-specific lives here: + +| File | Purpose | +|------|---------| +| `board/mentalnet/grub-embedded.cfg` | config embedded into the GRUB core image: searches for the medium containing `/boot/bzImage` instead of hardcoding a device | +| `board/mentalnet/linux-slim.config` | kernel config fragment: keeps the classic PCI NICs, ATA/ATAPI, USB HID/storage, serial and VGA console; drops wireless, sound, DRM, RAID, PCMCIA, debug | +| `board/mentalnet/overlay/usr/sbin/mentalnet-install` | the hard disk installer | +| `board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg` | boot menu template written to installed systems (`@ROOTDEV@` is replaced) | +| `board/mentalnet/overlay/etc/resolv.conf` | symlink so DHCP DNS works on the read-only CD | +| `board/mentalnet/overlay/etc/lighttpd/` | web server configuration | +| `fs/iso9660/grub.cfg` | live CD boot menu (`root=/dev/sr0 ro`) | +| `localversion.config` | kernel version suffix (`-mentalnet-intel32`) | +| `.config` | Buildroot configuration: GRUB2 embedded config path and module list, kernel fragment list, e2fsprogs/grub install tools | diff --git a/board/mentalnet/overlay/usr/sbin/mentalnet-install b/board/mentalnet/overlay/usr/sbin/mentalnet-install new file mode 100755 index 0000000..7438d12 --- /dev/null +++ b/board/mentalnet/overlay/usr/sbin/mentalnet-install @@ -0,0 +1,212 @@ +#!/bin/sh +# +# mentalnet-install - install Mentalnet GNU/Linux from the live CD onto a hard disk +# +# Run as root on the live system: mentalnet-install +# +# The installed system boots from its own MBR (GRUB) with the root +# filesystem on a single ext4 partition (plus optional swap). + +set -u + +MIN_DISK_SECTORS=819200 # 400 MiB minimum target disk +BOOT_START_SECTORS=2048 # first sector of root: leaves the MBR gap + # free for the GRUB core.img embedding +MNT=/mnt/hd + +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 +cleanup() { + grep -q " $MNT " /proc/mounts 2>/dev/null && umount "$MNT" 2>/dev/null + [ -n "${TMNT:-}" ] && umount /mnt 2>/dev/null + return 0 +} +trap cleanup EXIT + +[ "$(id -u)" -eq 0 ] || die "please run as root" + +for t in fdisk mkfs.ext4 mkswap grub-install; do + command -v "$t" >/dev/null 2>&1 || die "required tool '$t' not found" +done + +banner() { + clear 2>/dev/null + msg "" + msg " ============================================================" + msg " Mentalnet GNU/Linux - hard disk installer" + msg " ============================================================" + msg "" + msg " This will copy the live system from the CD onto a local" + msg " disk, install the GRUB bootloader, and make the disk" + msg " bootable on its own." + msg "" +} + +list_disks() { + for d in /sys/block/*; do + dev=${d##*/} + case "$dev" in + sr*|loop*|ram*|zram*|md*|dm-*|fd*|nbd*) continue ;; + esac + size=$(cat "$d/size" 2>/dev/null) || continue + mib=$((size / 2048)) + model=$(cat "$d/device/model" 2>/dev/null | tr -d ' ') + printf ' /dev/%-6s %6s MiB %s\n' "$dev" "$mib" "${model:-}" + done +} + +banner +msg " Detected disks:" +list_disks +msg "" + +printf ' Disk to install to (e.g. sda): ' +read -r DISK +[ -n "${DISK:-}" ] || die "no disk given" +case "$DISK" in + sr*|loop*|ram*|zram*|md*|dm-*|fd*|nbd*) die "refusing to use $DISK" ;; + *[!/a-z0-9]*) die "invalid disk name" ;; +esac +DEV="/dev/$DISK" +SYS="/sys/block/$DISK" +[ -d "$SYS" ] && [ -b "$DEV" ] || die "$DEV is not a block device" + +SIZE=$(cat "$SYS/size") +[ "$SIZE" -ge "$MIN_DISK_SECTORS" ] \ + || die "$DEV is too small (need $((MIN_DISK_SECTORS / 2048)) MiB minimum)" + +if grep -q "^$DEV" /proc/mounts; then + die "$DEV (or a partition on it) is currently mounted" +fi + +msg "" +warn "ALL DATA on $DEV will be DESTROYED without further notice." +printf ' Type YES to continue: ' +read -r ANSWER +[ "${ANSWER:-}" = "YES" ] || { msg "Aborted."; exit 0; } + +# --- swap? --------------------------------------------------------------- +printf ' Create a swap partition? [y/N]: ' +read -r SWAPANS +SWAP=0 +case "${SWAPANS:-}" in + y|Y|yes|Yes|YES) SWAP=1 ;; +esac + +SWAP_SECTORS=0 +if [ "$SWAP" -eq 1 ]; then + MEM_KB=$(awk '/^MemTotal:/{print $2}' /proc/meminfo) + SWAP_MIB=$((MEM_KB / 1024 * 2)) + [ "$SWAP_MIB" -gt 512 ] && SWAP_MIB=512 + [ "$SWAP_MIB" -lt 32 ] && SWAP_MIB=32 + SWAP_SECTORS=$((SWAP_MIB * 2048)) + msg " Swap partition: ${SWAP_MIB} MiB" +fi + +# --- partition ----------------------------------------------------------- +msg " Partitioning $DEV (MBR, root at sector $BOOT_START_SECTORS) ..." + +if [ "$SWAP" -eq 1 ]; then + P1_END=$((SIZE - SWAP_SECTORS - 1)) + P2_START=$((SIZE - SWAP_SECTORS)) + printf 'o\nn\np\n1\n%d\n%d\nn\np\n2\n%d\n%d\nt\n2\n82\na\n1\nw\n' \ + "$BOOT_START_SECTORS" "$P1_END" \ + "$P2_START" "$((SIZE - 1))" \ + | fdisk -u "$DEV" >/dev/null || die "fdisk failed" +else + printf 'o\nn\np\n1\n%d\n%d\na\nw\n' \ + "$BOOT_START_SECTORS" "$((SIZE - 1))" \ + | fdisk -u "$DEV" >/dev/null || die "fdisk failed" +fi + +ROOTPART="${DEV}1" +i=0 +while [ ! -b "$ROOTPART" ] && [ "$i" -lt 10 ]; do + i=$((i + 1)) + partprobe "$DEV" 2>/dev/null + sleep 1 +done +[ -b "$ROOTPART" ] || die "$ROOTPART did not appear after partitioning" + +# --- filesystems --------------------------------------------------------- +msg " Creating ext4 filesystem on $ROOTPART ..." +mkfs.ext4 -F -L rootfs "$ROOTPART" >/dev/null || die "mkfs.ext4 failed" + +if [ "$SWAP" -eq 1 ]; then + msg " Creating swap on ${DEV}2 ..." + mkswap "${DEV}2" >/dev/null || die "mkswap failed" +fi + +# --- copy the live system ------------------------------------------------ +# /mnt lives on the read-only CD, so put a writable tmpfs over it to +# get a usable mount point +TMNT= +if ! grep -q ' /mnt ' /proc/mounts; then + mount -t tmpfs -o mode=0755 tmpfs /mnt || die "cannot set up /mnt" + TMNT=1 +fi +mkdir -p "$MNT" +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. +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" + +# mount points that live in tmpfs on the CD +mkdir -p "$MNT/proc" "$MNT/sys" "$MNT/dev" "$MNT/tmp" "$MNT/run" "$MNT/mnt" + +# CD-only artifacts must not end up on the disk +rm -f "$MNT/boot/grub/grub-eltorito.img" + +# --- /etc/fstab ---------------------------------------------------------- +msg " Writing /etc/fstab ..." +cat > "$MNT/etc/fstab" <> "$MNT/etc/fstab" +fi + +# --- bootloader ---------------------------------------------------------- +msg " Writing bootloader configuration ..." +sed "s|@ROOTDEV@|$ROOTPART|" /usr/share/mentalnet/grub-disk.cfg \ + > "$MNT/boot/grub/grub.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 \ + || die "grub-install failed" + +sync +umount "$MNT" || umount -f "$MNT" || die "could not unmount $MNT" +[ -n "$TMNT" ] && umount /mnt + +msg "" +msg " ============================================================" +msg " Installation complete!" +msg " ============================================================" +msg "" +msg " Root partition : $ROOTPART (ext4)" +[ "$SWAP" -eq 1 ] && msg " Swap : ${DEV}2 (${SWAP_MIB} MiB)" +msg " Login : root / mnlinux" +msg "" +msg " Remove the CD and reboot: poweroff" +msg "" diff --git a/board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg b/board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg new file mode 100644 index 0000000..3edef39 --- /dev/null +++ b/board/mentalnet/overlay/usr/share/mentalnet/grub-disk.cfg @@ -0,0 +1,6 @@ +set default="0" +set timeout="5" + +menuentry "Mentalnet GNU/Linux" { + linux /boot/bzImage root=@ROOTDEV@ rootwait rw console=ttyS0,9600n8 console=tty0 +}