2018-07-25 19:23:05 +00:00
|
|
|
#!/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}")"
|
2018-07-26 20:35:32 +00:00
|
|
|
printf '%s: %s\n' "${0##*/}" "$msg" >&2
|
2018-07-25 19:23:05 +00:00
|
|
|
if (( $1 != 0 )); then
|
|
|
|
exit "$1"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
main() {
|
|
|
|
set -euE
|
|
|
|
|
|
|
|
local arg_mode=outside
|
|
|
|
local arg_mountpoint=
|
2018-07-26 22:26:45 +00:00
|
|
|
local arg_size=1G
|
2018-07-25 19:23:05 +00:00
|
|
|
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)
|
2018-07-25 21:28:42 +00:00
|
|
|
if [[ -e "$arg_file" ]]; then
|
|
|
|
error 1 'Image file already exists, refusing to overwrite: %s' "$arg_file"
|
2018-07-25 19:23:05 +00:00
|
|
|
fi
|
2018-07-25 21:28:42 +00:00
|
|
|
truncate --size="$arg_size" -- "$arg_file"
|
2018-07-26 20:35:32 +00:00
|
|
|
mkfs.btrfs -- "$arg_file"
|
2018-07-25 19:23:05 +00:00
|
|
|
arg_mountpoint=$(mktemp -dt -- "${0##*/}.XXXXXXXXXX")
|
|
|
|
# shellcheck disable=SC2064
|
|
|
|
trap "rmdir -- ${arg_mountpoint@Q}" EXIT
|
2018-07-25 21:28:42 +00:00
|
|
|
sudo -- unshare --mount -- "${BASH_SOURCE[0]}" --inside="$arg_mountpoint" "${args[@]}"
|
2018-07-25 19:23:05 +00:00
|
|
|
;;
|
|
|
|
inside) # just keep reading...
|
2018-07-25 21:28:42 +00:00
|
|
|
if (( UID != 0 )); then
|
|
|
|
error 4 "Must be invoked as root."
|
|
|
|
fi
|
2018-07-25 19:23:05 +00:00
|
|
|
mount --make-rslave /
|
|
|
|
mount "$arg_file" "$arg_mountpoint"
|
2018-07-26 20:35:32 +00:00
|
|
|
|
|
|
|
# Base install
|
|
|
|
#
|
2018-07-25 21:28:42 +00:00
|
|
|
# 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.
|
2018-07-26 22:26:45 +00:00
|
|
|
packages=(
|
|
|
|
# Base bootable system
|
|
|
|
base grub btrfs-progs
|
|
|
|
# Integration tests
|
|
|
|
dhclient net-tools strace
|
|
|
|
)
|
2018-07-25 21:28:42 +00:00
|
|
|
pacstrap -c -M -C /usr/share/pacman/defaults/pacman.conf.x86_64 -- "$arg_mountpoint" \
|
2018-07-26 22:26:45 +00:00
|
|
|
--hookdir="$arg_mountpoint/etc/pacman.d/hooks" "${packages[@]}"
|
2018-07-26 20:35:32 +00:00
|
|
|
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
|
2018-07-25 21:28:42 +00:00
|
|
|
arch-chroot -- "$arg_mountpoint" sh -c \
|
|
|
|
'grub-install "$(awk '\''$2 == "/" { print $1 }'\'' </proc/mounts)"'
|
|
|
|
arch-chroot -- "$arg_mountpoint" grub-mkconfig -o /boot/grub/grub.cfg
|
2018-07-26 20:35:32 +00:00
|
|
|
|
|
|
|
# Networking
|
|
|
|
cat <<-'EOT' > "${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
|
2018-07-25 19:23:05 +00:00
|
|
|
;;
|
|
|
|
|
2018-07-26 20:35:32 +00:00
|
|
|
*) error 1 'Internal error. The programmer writing this tool screwed up.'; exit 1;;
|
2018-07-25 19:23:05 +00:00
|
|
|
esac
|
|
|
|
}
|
|
|
|
|
|
|
|
main "$@"
|