#!/usr/bin/env bash # 2018 Luke Shumaker declare -r NAME=osi-mk declare -r VERSION=20180812 # 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 # 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 set -euE -o pipefail source "${BASH_SOURCE[0]%/*}/lib/osi.sh" loaded_modules=() load_module() { local module if ! [[ -f $1 ]]; then error 2 'Module does not exist: %s' "$1" fi module="$(realpath -- "$1")" if in_array "$module" "${loaded_modules[@]}"; then return 0 fi loaded_modules+=("$module") source "$1" } osi-mk:genfstab() { local arg_mountpoint=$1 genfstab -t uuid "$arg_mountpoint" > "${arg_mountpoint}/etc/fstab" } osi-mk:directories() { local arg_mountpoint=$1 local spec outside inside for spec in "${arg_directories[@]}"; do outside="${spec%:*}" inside="${spec#"${outside}:"}" # TODO: maybe rsync would be better? if [[ -d "${arg_mountpoint}/${inside}" ]]; then print 'Removing existing %q:%q' "$arg_file" "$inside" rm -rf -- "${arg_mountpoint}/${inside}" fi mkdir -p -- "$(dirname -- "${arg_mountpoint}/${inside}")" print 'Copying %q to %q:%q' "$outside" "$arg_file" "$inside" cp -aT -- "$outside" "${arg_mountpoint}/${inside}" done } osi-mk:grub-install() { local arg_mountpoint=$1 cat <<-'EOT' >> "$arg_mountpoint/etc/default/grub" GRUB_TIMEOUT=0 GRUB_DEFAULT=1 # Use the fallback initramfs, to get all drivers EOT arch-chroot -- "$arg_mountpoint" sh -c \ 'grub-install "$(awk '\''$2 == "/" { print $1 }'\'' &2; return 2;; version) print "%s (notsystemd) %s" "$NAME" "$VERSION" return 0 ;; usage) print 'Usage: %s [OPTIONS] FILENAME.img' "${0##*/}" print 'Operating System Image: Make' 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)' print ' -e[BASE.img], --edit[=BASE.img] edit an existing image' echo print ' -d OUTSIDE:INSIDE, --directory=OUTSIDE:INSIDE include the given directory' print ' -m MOD.sh, --module=MOD.sh include the given module' print ' -p PACKAGE, --package=package include the given package (or group)' echo print ' -h, --help display this help' print ' -V, --version output version information' return 0 ;; # main code starts here outside) if [[ ( $arg_edit = false || -n $arg_edit_base ) && -e $arg_file ]]; then error 1 'Image file already exists, refusing to overwrite: %s' "$arg_file" fi if $arg_edit; then if ! [[ -f "${arg_edit_base:-$arg_file}" ]]; then error 1 'Image must already exist to --edit: %s' "${arg_edit_base:-$arg_file}" fi if [[ -n $arg_edit_base ]]; then printf -v prefix "$(gettext -- '%s [format]')" "$NAME" { cp -T -- "$arg_edit_base" "$arg_file" btrfstune -fu "$arg_file" } |& sed "s|^|${prefix} |" fi # TODO: resize according to $arg_size else printf -v prefix "$(gettext -- '%s [format]')" "$NAME" { truncate --size="$arg_size" -- "$arg_file" mkfs.btrfs -- "$arg_file" } |& sed "s|^|${prefix} |" fi arg_mountpoint=$(mktemp -dt -- "${0##*/}.XXXXXXXXXX") # shellcheck disable=SC2064 trap "rmdir -- ${arg_mountpoint@Q}" EXIT sudo -- ./osi-mount --root -- "$arg_file" "$arg_mountpoint" "${BASH_SOURCE[0]}" --inside="$arg_mountpoint" "${arg_orig[@]}" ;; inside) # just keep reading... needs_sudo ### Load modules ### packages=("${arg_packages[@]}") cache_packages=() post_install=( 00:osi-mk:genfstab 50:osi-mk:directories 99:osi-mk:grub-mkconfig ) if ! $arg_edit; then packages+=(grub btrfs-progs) post_install+=( 98:osi-mk:grub-install ) fi for module in "${arg_modules[@]}"; do load_module "$module" done cache_packages+=("${packages[@]}") #### install ### printf -v prefix "$(gettext -- '%s [install]')" "$NAME" { # Pre-fill the package cache if (( ${#cache_packages[@]} > 0 )); then mkdir -p -- "$arg_mountpoint"/var/{cache/pacman/pkg,lib/pacman,log} # Download anything the host doesn't already have cached pacman -r "$arg_mountpoint" --config=/usr/share/pacman/defaults/pacman.conf.x86_64 \ -Syw --noconfirm -- "${cache_packages[@]}" # Copy needed packages from host cache to image cache pacman -r "$arg_mountpoint" --config=/usr/share/pacman/defaults/pacman.conf.x86_64 \ -Sp --print-format='%l' -- "${cache_packages[@]}" \ | sed -n 's,^file://,,p' \ | xargs -d $'\n' -r cp -t "$arg_mountpoint/var/cache/pacman/pkg" -- fi if (( ${#packages[@]} > 0 )); then # The --hookdir bit is to hack around https://bugs.archlinux.org/task/49347 pacstrap -M -C /usr/share/pacman/defaults/pacman.conf.x86_64 -- "$arg_mountpoint" \ --hookdir="$arg_mountpoint/etc/pacman.d/hooks" \ --needed \ "${packages[@]}" fi } |& sed "s|^|${prefix} |" ### post_install ### while IFS=: read -r n fn; do printf -v prefix "$(gettext -- '%s [post_install:%s:%s]')" "$NAME" "$n" "$fn" { print Begin "$fn" "$arg_mountpoint" print End } |& sed "s|^|${prefix} |" done < <(printf '%s\n' "${post_install[@]}" | sort) ### End ### print '%s Done' "$NAME" ;; *) error 1 'Internal error. The programmer writing this tool screwed up.';; esac } main "$@"