#!/usr/bin/env bash # 2018 Luke Shumaker declare -r NAME=mkosi declare -r VERSION=20180725 # Why is this different than mkosi[1]? # # - mkosi claims to be "legacy-free"--but they call everything that's # not systemd "legacy"; that clearly won't do for creating OpenRC or # GNU Shepherd images. # # - mkosi claims to be "legacy-free", only supporting GPT # disk-labels--but btrfs can be booted directly, without a separate # disk-label, or a separate ESP partition. To a btrfs disk, GPT/ESP # is legacy. # # - Using a raw btrfs disk means that it can easily be mounted without # first disecting the disk-label. # # [1]: https://github.com/systemd/mkosi print() { # shellcheck disable=SC2059 printf "$(gettext -- "$1")\\n" "${@:2}" } error() { local msg msg="$(print "${@:2}")" printf '%s: %s\n' "${0##*/}" "$msg" >&2 if (( $1 != 0 )); then exit "$1" fi } main() { set -euE local arg_mode=outside local arg_mountpoint= local arg_size=1G local arg_file local args if ! args="$(getopt -n "${0##*/}" -o "Vhs" -l "inside:,version,help,size" -- "$@")"; then arg_mode=error else eval "args=($args)" set -- "${args[@]}" while true; do case "$1" in --inside) shift; arg_mode=inside; arg_mountpoint=$1; shift;; -V|--version) shift; arg_mode=version;; -h|--help) shift; arg_mode=usage;; -s|--size) shift; arg_size=$1;; --) shift; break;; esac done if (( $# != 1 )); then if (( $# == 0 )); then error 0 "Expected 1 positional argument, got none" else error 0 "Expected 1 positional argument, got %d: %s" "$#" "${@@Q}" fi arg_mode=error else arg_file=$1 fi fi case "$arg_mode" in error) print "Try '%q --help' for more information" "${0##*/}" >&2; return 2;; version) print "%s (notsystemd) %s" "$NAME" "$VERSION" return 0 ;; usage) print 'Usage: %s [OPTIONS]' "${0##*/} FILENAME.IMG" print 'Make Operating System Image' echo print 'Create a mountable, bootable OS image.' echo print 'OPTIONS:' # --inside is internal-only; undocumented print ' -s SIZE, --size=SIZE set the size of the image (default: 1G)' echo print ' -h, --help display this help' print ' -V, --version output version information' return 0 ;; # main code starts here outside) if [[ -e "$arg_file" ]]; then error 1 'Image file already exists, refusing to overwrite: %s' "$arg_file" fi truncate --size="$arg_size" -- "$arg_file" mkfs.btrfs -- "$arg_file" arg_mountpoint=$(mktemp -dt -- "${0##*/}.XXXXXXXXXX") # shellcheck disable=SC2064 trap "rmdir -- ${arg_mountpoint@Q}" EXIT sudo -- unshare --mount -- "${BASH_SOURCE[0]}" --inside="$arg_mountpoint" "${args[@]}" ;; inside) # just keep reading... if (( UID != 0 )); then error 4 "Must be invoked as root." fi mount --make-rslave / mount "$arg_file" "$arg_mountpoint" # Base install # # The --hookdir won't work with pacstrap>18, because git after v18 tightens # up option parsing. Fortunately, it's only there to work around the fact # that pacstrap=18 hasn't yet migrated from --root to --sysroot. packages=( # Base bootable system base grub btrfs-progs # Integration tests dhclient net-tools strace ) pacstrap -c -M -C /usr/share/pacman/defaults/pacman.conf.x86_64 -- "$arg_mountpoint" \ --hookdir="$arg_mountpoint/etc/pacman.d/hooks" "${packages[@]}" genfstab -t uuid "$arg_mountpoint" > "${arg_mountpoint}/etc/fstab" # Bootloader cat <<-'EOT' >> "$arg_mountpoint/etc/default/grub" GRUB_TIMEOUT=0 GRUB_DEFAULT=1 GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0 console=tty0" GRUB_TERMINAL_OUTPUT="serial console" EOT arch-chroot -- "$arg_mountpoint" sh -c \ 'grub-install "$(awk '\''$2 == "/" { print $1 }'\'' "${arg_mountpoint}/etc/udev/rules.d/81-dhcpcd.rules" ACTION=="add", SUBSYSTEM=="net", ENV{SYSTEMD_WANTS}="dhcpcd@$name.service" EOT mkdir "${arg_mountpoint}/etc/systemd/system/dhcpcd@.service.d" cat <<-'EOT' > "${arg_mountpoint}/etc/systemd/system/dhcpcd@.service.d/wait-online.conf" [Unit] Before=network-online.target EOT mkdir "${arg_mountpoint}/etc/systemd/system/network-online.target.d" cat <<-'EOT' > "${arg_mountpoint}/etc/systemd/system/network-online.target.d/udev-settle.conf" [Unit] Wants=systemd-udev-settle.service After=systemd-udev-settle.service EOT # Autologin mkdir "${arg_mountpoint}/etc/systemd/system/serial-getty@ttyS0.service.d" cat <<-EOT >"${arg_mountpoint}/etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf" [Service] ExecStart= $(sed -n '/^ExecStart=/s/ / --autologin root /p' < "${arg_mountpoint}/usr/lib/systemd/system/serial-getty@.service") EOT # End ;; *) error 1 'Internal error. The programmer writing this tool screwed up.'; exit 1;; esac } main "$@"