#!/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. set -u MIN_DISK_SECTORS=2097152 # 1 GiB minimum target disk (rootfs is ~150MB, # and old disks are cheap) 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 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 } trap cleanup EXIT [ "$(id -u)" -eq 0 ] || die "please run as root" 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 "" 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; } # --- partition ----------------------------------------------------------- msg " Partitioning $DEV (MBR, root at sector $BOOT_START_SECTORS) ..." printf 'o\nn\np\n1\n%d\n%d\na\n1\nw\n' \ "$BOOT_START_SECTORS" "$((SIZE - 1))" \ | fdisk -u "$DEV" >/dev/null || die "fdisk failed" 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" # --- show the result and get final confirmation -------------------------- msg "" msg " New partition table on $DEV:" fdisk -l "$DEV" msg "" printf ' Proceed with installation? [y/N]: ' read -r GO case "${GO:-}" in y|Y|yes|Yes|YES) ;; *) msg "Aborted."; exit 0 ;; esac # --- filesystems --------------------------------------------------------- msg " Creating ext4 filesystem on $ROOTPART ..." mkfs.ext4 -F -L rootfs "$ROOTPART" >/dev/null || die "mkfs.ext4 failed" # --- 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 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 # 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" # the live system's resolv.conf is a symlink into tmpfs (udhcpc writes # through it on the CD); on the disk udhcpc needs a real file rm -f "$MNT/etc/resolv.conf" : > "$MNT/etc/resolv.conf" # 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/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" \ || 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!" msg " ============================================================" msg "" msg " Root partition : $ROOTPART (ext4)" msg " Login : root / mnlinux" msg "" msg " Remove the CD and reboot: poweroff" msg ""