#!/bin/sh -e # run-hurd.sh, a script to fetch and run Debian GNU (Hurd) using qemu, aspiring to be fool-proof # Copyright (C) 2026 alicia@ion.nu # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . bit64=false redownload=false fsck=false daemon=false expand='' sshport='127.0.0.1:2222' video='' ram='2G' while getopts "6rfde:s:v:m:" opt; do case "$opt" in 6) bit64=true;; r) redownload=true;; f) fsck=true;; d) daemon=true;; v) video="-display ${OPTARG}";; e) expand="$OPTARG" if ! echo "$expand" | grep -q '[0-9]\+[BKMGTP]'; then echo 'Invalid format for expansion. Expected number followed by unit (B for bytes, K for kilobytes, M for megabytes etc.)' exit 1 fi;; m) ram="$OPTARG" # TODO: Warn about >16GB issue? if ! echo "$ram" | grep -q '[0-9]\+[BKMGTP]'; then echo 'Invalid format for RAM. Expected number followed by unit (B for bytes, K for kilobytes, M for megabytes etc.)' exit 1 fi;; s) sshport="$OPTARG" if ! echo "$sshport" | grep -q '^[0-9.]*:[0-9]\+$'; then echo 'Invalid format for SSH host/port. Expected format "host:port" where host is optional. No host = remote accessible' exit 1 fi;; *) echo "Usage: ${0} [options]" echo 'Options include:' echo ' -6 = use the new 64bit build' echo ' -r = redownload instead of booting existing image' echo ' -f = run a filesystem check (requires losetup and fsck.ext2)' echo ' -d = daemonize, start qemu and return to the shell' echo ' -e = expand disk image to the given size' echo ' -s [host]: = set port for SSH forward (default: 127.0.0.1:2222)' echo ' -v = set the Qemu display option. e.g. curses or vnc=:0' echo ' curses is good for non-GUI environments and to translate' echo ' keypresses from host keyboard layout.' exit;; esac done # Some sanity checks before we begin depcheck() { local cmd="$1" local guixpkg="$2" local pacmanpkg="$3" local aptpkg="$4" local dnfpkg="$5" # Cascade package name for missing parameters if [ -z "$guixpkg" ]; then guixpkg="$cmd"; fi if [ -z "$pacmanpkg" ]; then pacmanpkg="$guixpkg"; fi if [ -z "$aptpkg" ]; then aptpkg="$pacmanpkg"; fi if [ -z "$dnfpkg" ]; then dnfpkg="$aptpkg"; fi if [ ! -f "`which "$cmd"`" ]; then # Using -f because some implementations of 'which' make simply return code or presence of output unreliable indicators echo "${cmd} not found. You can likely install it with one of the commands below:" echo "guix install ${guixpkg}" echo "sudo pacman -S ${pacmanpkg}" echo "sudo apt install ${aptpkg}" echo "sudo dnf install ${dnfpkg}" exit 1 fi } if [ ! -f "`which 'sudo'`" ]; then # No sudo = make our own using su sudo() { local cmd="$@" echo "su -c '${cmd}'" >&2 PATH="/sbin:/usr/sbin:${PATH}" su -c "$cmd" } fi depcheck qemu-system-x86_64 qemu qemu-system-x86 if [ ! -e debian-hurd.img ] || "$redownload"; then depcheck wget rm -f debian-hurd.img.tar.gz if "$bit64"; then wget 'https://cdimage.debian.org/cdimage/ports/latest/hurd-amd64/debian-hurd.img.tar.gz' else wget 'https://cdimage.debian.org/cdimage/ports/latest/hurd-i386/debian-hurd.img.tar.gz' fi image="`tar -xvzf debian-hurd.img.tar.gz`" mv "$image" debian-hurd.img fi if "$fsck"; then # TODO: Dynamically figure out which way (if any, maybe user is root) to run these as root # We set PATH here because some distros hide tools like fsck.ext2 in paths that are only in root's PATH by default, so without it we get an error here even though sudo finds it PATH="/sbin:/usr/sbin:${PATH}" depcheck fsck.ext2 e2fsprogs disk="`sudo losetup -fP --show debian-hurd.img`" if [ -z "$disk" ]; then echo 'Failed to losetup the disk image. Is losetup installed? (part of util-linux)'; exit 1; fi # TODO: Bundle these sudos together? (cuts down on password prompts, especially when falling back to su) if [ -e "${disk}p5" ]; then sudo fsck.ext2 -fy "${disk}p5" || true else sudo fsck.ext2 -fy "${disk}p2" || true # 32bit doesn't use the extended partition fi sudo losetup -d "$disk" fi if [ -n "$expand" ]; then PATH="/sbin:/usr/sbin:${PATH}" depcheck parted PATH="/sbin:/usr/sbin:${PATH}" depcheck fsck.ext2 e2fsprogs num="`echo "$expand" | sed -e 's/[A-Z]$//'`" unit="`echo "$expand" | sed -e 's/^[0-9]*//'`" dd if=/dev/zero of=debian-hurd.img bs="1${unit}" count=0 seek="$num" disk="`sudo losetup -fP --show debian-hurd.img`" # TODO: Unify the losetup calls? if [ -z "$disk" ]; then echo 'Failed to losetup the disk image. Is losetup installed?'; exit 1; fi # TODO: Bundle these sudos together? (cuts down on password prompts, especially when falling back to su) sudo parted "$disk" resizepart 2 '100%' # Resize the extended partition if [ -e "${disk}p5" ]; then sudo parted "$disk" resizepart 5 '100%' # Resize the filesystem's partition therein sudo fsck.ext2 -fy "${disk}p5" || true sudo resize2fs "${disk}p5" else sudo fsck.ext2 -fy "${disk}p2" || true sudo resize2fs "${disk}p2" # No extended partition for 32bit fi sudo losetup -d "$disk" fi runqemu() { kvm="`grep -q ' \(svm\|vmx\)' /proc/cpuinfo && echo '--enable-kvm' || true`" # I think 32bit code should work even if we run x86_64 qemu? # Something is broken on 64bit Hurd with 4+GB RAM but -M q35 evades this. But 32bit breaks with it so it needs to be conditional. >16GB remains broken if "$bit64"; then machine='-M q35'; else machine=''; fi qemu-system-x86_64 ${video} ${machine} ${kvm} -m "$ram" -drive cache=writeback,file=debian-hurd.img -net user,hostfwd="tcp:${sshport}-:22" -net nic,model=e1000 } if "$daemon"; then runqemu > /dev/null 2> /dev/null & else runqemu fi