osi-tools/osi-mk

292 lines
8.9 KiB
Plaintext
Raw Normal View History

2018-07-25 19:23:05 +00:00
#!/usr/bin/env bash
# 2018 Luke Shumaker
2018-08-06 01:56:44 +00:00
declare -r NAME=osi-mk
2018-08-12 20:35:56 +00:00
declare -r VERSION=20180812
2018-07-25 19:23:05 +00:00
# Why is this different than mkosi[1]?
#
# - mkosi claims to be "legacy-free"--but they call everything that's
2018-08-06 00:09:42 +00:00
# not systemd "legacy"; that clearly won't do for creating OpenRC
# images.
2018-07-25 19:23:05 +00:00
#
# - 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
2018-08-12 21:53:08 +00:00
set -euE -o pipefail
source ./lib/osi.sh
2018-08-06 01:49:45 +00:00
loaded_modules=()
2018-08-06 04:02:37 +00:00
load_module() {
2018-08-06 01:49:45 +00:00
local module
2018-08-06 04:02:37 +00:00
if ! [[ -f $1 ]]; then
error 2 'Module does not exist: %s' "$1"
fi
2018-08-06 01:49:45 +00:00
module="$(realpath -- "$1")"
if in_array "$module" "${loaded_modules[@]}"; then
2018-08-06 04:02:37 +00:00
return 0
2018-08-06 01:49:45 +00:00
fi
2018-08-06 04:02:37 +00:00
loaded_modules+=("$module")
# shellcheck disable=SC1090
2018-08-06 04:02:37 +00:00
source "$1"
2018-08-06 01:49:45 +00:00
}
2018-08-12 21:53:08 +00:00
osi-mk:genfstab() {
local arg_mountpoint=$1
genfstab -t uuid "$arg_mountpoint" > "${arg_mountpoint}/etc/fstab"
}
2018-08-06 01:56:44 +00:00
osi-mk:directories() {
2018-08-06 01:49:45 +00:00
local arg_mountpoint=$1
local spec outside inside
2018-08-06 04:02:37 +00:00
for spec in "${arg_directories[@]}"; do
2018-08-06 01:49:45 +00:00
outside="${spec%:*}"
inside="${spec#"${outside}:"}"
2018-08-13 01:03:56 +00:00
# TODO: maybe rsync would be better?
if [[ -d "${arg_mountpoint}/${inside}" ]]; then
2018-08-13 05:16:36 +00:00
print 'Removing existing %q:%q' "$arg_file" "$inside"
rm -rf -- "${arg_mountpoint:?}/${inside}"
2018-08-13 01:03:56 +00:00
fi
2018-08-06 04:02:37 +00:00
mkdir -p -- "$(dirname -- "${arg_mountpoint}/${inside}")"
2018-08-13 05:16:36 +00:00
print 'Copying %q to %q:%q' "$outside" "$arg_file" "$inside"
2018-08-13 01:03:56 +00:00
cp -aT -- "$outside" "${arg_mountpoint}/${inside}"
2018-08-06 01:49:45 +00:00
done
}
2018-08-12 21:53:08 +00:00
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
# shellcheck disable=SC2016
2018-08-12 21:53:08 +00:00
arch-chroot -- "$arg_mountpoint" sh -c \
'grub-install "$(awk '\''$2 == "/" { print $1 }'\'' </proc/mounts)"'
}
osi-mk:grub-mkconfig() {
local arg_mountpoint=$1
2018-08-12 21:53:08 +00:00
arch-chroot -- "$arg_mountpoint" grub-mkconfig -o /boot/grub/grub.cfg
}
2018-07-25 19:23:05 +00:00
main() {
local arg_orig=("$@")
2018-07-25 19:23:05 +00:00
local arg_mode=outside
local arg_mountpoint=
local arg_size=
2018-08-12 02:35:52 +00:00
local arg_edit=false
local arg_edit_base=
2018-08-06 01:49:45 +00:00
local arg_directories=()
2018-08-06 00:09:42 +00:00
local arg_modules=()
2018-08-06 01:49:45 +00:00
local arg_packages=()
2018-07-25 19:23:05 +00:00
local args
if ! args="$(getopt -n "${0##*/}" -o "s:e::d:m:p:hV" -l "inside:,size:,edit::,directory:,module:,package:,help,version" -- "$@")"; then
2018-07-25 19:23:05 +00:00
arg_mode=error
else
eval "set -- $args"
2018-07-25 19:23:05 +00:00
while true; do
case "$1" in
--inside) shift; arg_mode=inside; arg_mountpoint=$1; shift;;
2018-08-05 02:31:38 +00:00
-s|--size) shift; arg_size=$1; shift;;
-e|--edit) shift; arg_edit=true; arg_edit_base=$1; shift;;
2018-08-06 01:49:45 +00:00
2018-08-06 04:02:37 +00:00
-d|--directory) shift; arg_directories+=("$1"); shift;;
2018-08-06 00:09:42 +00:00
-m|--module) shift; arg_modules+=("$1"); shift;;
2018-08-06 04:02:37 +00:00
-p|--package) shift; arg_packages+=("$1"); shift;;
2018-08-06 01:49:45 +00:00
-V|--version) shift; arg_mode=version;;
-h|--help) shift; arg_mode=usage;;
2018-07-25 19:23:05 +00:00
--) shift; break;;
2018-08-05 02:31:38 +00:00
*) error 1 'Internal error. The programmer writing this tool screwed up.';;
2018-07-25 19:23:05 +00:00
esac
done
2018-08-06 00:09:42 +00:00
case "$arg_mode" in
outside|inside)
if (( $# != 1 )); then
if (( $# == 0 )); then
error 0 "Expected 1 positional argument, got none"
else
2018-08-14 20:17:31 +00:00
error 0 "Expected 1 positional argument, got %d: %s" "$#" "${*@Q}"
2018-08-06 00:09:42 +00:00
fi
arg_mode=error
else
arg_file=$1
fi
2018-08-12 22:15:43 +00:00
for module in "${arg_modules[@]}"; do
if ! [[ -f $module ]]; then
error 0 'Module does not exist: %s' "$module"
arg_mode=error
fi
done
for dirspec in "${arg_directories[@]}"; do
if ! [[ -d "${dirspec%:*}" ]]; then
error 0 'Directory does not exist: %s' "${dirspec%:*}"
arg_mode=error
fi
done
if [[ $arg_mode = outside ]]; then
if [[ ( $arg_edit = false || -n $arg_edit_base ) && -e $arg_file ]]; then
error 2 'Image file already exists, refusing to overwrite: %s' "$arg_file"
fi
if $arg_edit; then
if ! [[ -f ${arg_edit_base:-$arg_file} ]]; then
error 2 'Image must already exist to --edit: %s' "${arg_edit_base:-$arg_file}"
fi
else
if [[ -z $arg_size ]]; then
error 2 'Must specify --size when creating a new image'
fi
fi
fi
2018-08-06 00:09:42 +00:00
;;
esac
2018-07-25 19:23:05 +00:00
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)
2018-08-12 20:35:56 +00:00
print 'Usage: %s [OPTIONS] FILENAME.img' "${0##*/}"
print 'Operating System Image: Make'
2018-07-25 19:23:05 +00:00
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'
2018-08-06 01:49:45 +00:00
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)'
2018-07-25 19:23:05 +00:00
echo
print ' -h, --help display this help'
print ' -V, --version output version information'
return 0
;;
# main code starts here
outside)
2018-08-12 02:35:52 +00:00
if $arg_edit; then
if [[ -n $arg_edit_base ]]; then
# shellcheck disable=SC2059
printf -v prefix "$(gettext -- '%s [format]')" "$NAME"
{
cp -T -- "$arg_edit_base" "$arg_file"
btrfstune -fu "$arg_file"
} |& sed "s|^|${prefix} |"
2018-08-12 02:35:52 +00:00
fi
2018-08-16 05:19:05 +00:00
if [[ -n $arg_size ]]; then
# Calculate sizes in exact bytes now, to avoid any discrepancies
# between truncate(1) and btrfs-filesystem(8).
tmpfile=$(mktemp -t -- "${0##*/}.XXXXXXXXXX")
trap "rm -- ${tmpfile@Q}" EXIT
truncate --reference="$arg_file" --size="$arg_size" -- "$tmpfile"
old_size=$(stat --format='%s' -- "$arg_file")
new_size=$(stat --format='%s' -- "$tmpfile")
rm -- "$tmpfile"
trap - EXIT
# Do the resize
arg_mountpoint=$(mktemp -dt -- "${0##*/}.XXXXXXXXXX")
trap "rmdir -- ${arg_mountpoint@Q}" EXIT
if (( new_size > old_size )); then
truncate --size="$new_size" -- "$arg_file"
sudo -- ./osi-mount --root -- "$arg_file" "$arg_mountpoint" btrfs filesystem resize max "$arg_mountpoint"
elif (( new_size < old_size )); then
sudo -- ./osi-mount --root -- "$arg_file" "$arg_mountpoint" btrfs filesystem resize "$new_size" "$arg_mountpoint"
truncate --size="$new_size" -- "$arg_file"
fi
rmdir -- "$arg_mountpoint" EXIT
trap - EXIT
fi
2018-08-12 02:35:52 +00:00
else
# shellcheck disable=SC2059
2018-08-12 22:05:29 +00:00
printf -v prefix "$(gettext -- '%s [format]')" "$NAME"
2018-08-12 21:53:08 +00:00
{
truncate --size="$arg_size" -- "$arg_file"
mkfs.btrfs -- "$arg_file"
2018-08-12 22:05:29 +00:00
} |& sed "s|^|${prefix} |"
2018-07-25 19:23:05 +00:00
fi
arg_mountpoint=$(mktemp -dt -- "${0##*/}.XXXXXXXXXX")
trap "rmdir -- ${arg_mountpoint@Q}" EXIT
sudo -- ./osi-mount --root -- "$arg_file" "$arg_mountpoint" "${BASH_SOURCE[0]}" --inside="$arg_mountpoint" "${arg_orig[@]}"
2018-07-25 19:23:05 +00:00
;;
inside) # just keep reading...
2018-08-11 18:57:24 +00:00
needs_sudo
2018-07-26 20:35:32 +00:00
2018-08-06 01:49:45 +00:00
### Load modules ###
2018-08-12 22:20:59 +00:00
packages=("${arg_packages[@]}")
cache_packages=()
2018-08-12 21:53:08 +00:00
post_install=(
00:osi-mk:genfstab
2018-08-12 21:53:08 +00:00
50:osi-mk:directories
99:osi-mk:grub-mkconfig
2018-08-12 21:53:08 +00:00
)
2018-08-12 22:20:59 +00:00
if ! $arg_edit; then
packages+=(grub btrfs-progs)
post_install+=(
98:osi-mk:grub-install
2018-08-12 22:20:59 +00:00
)
fi
2018-08-06 00:09:42 +00:00
for module in "${arg_modules[@]}"; do
2018-08-06 04:02:37 +00:00
load_module "$module"
2018-08-06 00:09:42 +00:00
done
cache_packages+=("${packages[@]}")
2018-08-06 01:49:45 +00:00
2018-08-12 22:20:59 +00:00
#### install ###
# shellcheck disable=SC2059
2018-08-12 22:05:29 +00:00
printf -v prefix "$(gettext -- '%s [install]')" "$NAME"
2018-08-12 21:53:08 +00:00
{
2018-08-12 22:25:10 +00:00
# Pre-fill the package cache
2018-08-12 22:20:59 +00:00
if (( ${#cache_packages[@]} > 0 )); then
mkdir -p -- "$arg_mountpoint"/var/{cache/pacman/pkg,lib/pacman,log}
2018-08-12 22:25:10 +00:00
# Download anything the host doesn't already have cached
2018-08-12 22:20:59 +00:00
pacman -r "$arg_mountpoint" --config=/usr/share/pacman/defaults/pacman.conf.x86_64 \
-Syw --noconfirm -- "${cache_packages[@]}"
2018-08-12 22:25:10 +00:00
# Copy needed packages from host cache to image cache
2018-08-12 22:20:59 +00:00
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
2018-08-12 22:05:29 +00:00
} |& sed "s|^|${prefix} |"
2018-07-26 20:35:32 +00:00
2018-08-06 01:49:45 +00:00
### post_install ###
while IFS=: read -r n fn; do
# shellcheck disable=SC2059
2018-08-12 22:05:29 +00:00
printf -v prefix "$(gettext -- '%s [post_install:%s:%s]')" "$NAME" "$n" "$fn"
2018-08-12 21:53:08 +00:00
{
2018-08-12 22:05:29 +00:00
print Begin
2018-08-12 21:53:08 +00:00
"$fn" "$arg_mountpoint"
2018-08-12 22:05:29 +00:00
print End
2018-08-12 21:53:08 +00:00
} |& sed "s|^|${prefix} |"
2018-08-06 01:49:45 +00:00
done < <(printf '%s\n' "${post_install[@]}" | sort)
2018-08-06 00:09:42 +00:00
2018-08-12 21:53:08 +00:00
### End ###
print '%s Done' "$NAME"
2018-07-25 19:23:05 +00:00
;;
2018-08-05 02:31:38 +00:00
*) error 1 'Internal error. The programmer writing this tool screwed up.';;
2018-07-25 19:23:05 +00:00
esac
}
main "$@"