From 5de41afa5a1976f42997347f4a213e031678ec34 Mon Sep 17 00:00:00 2001 From: Stefan Kreutz Date: Sat, 24 Jul 2021 19:06:25 +0200 Subject: Add initial implementation --- .gitignore | 7 ++ Makefile | 9 ++ README.md | 20 ++++ installiso.8 | 140 +++++++++++++++++++++++++++ installiso.ksh | 293 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 469 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 installiso.8 create mode 100755 installiso.ksh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0da69c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.iso +*.qcow2 +disklabel_template +install.conf +site +tmp +upgrade.conf diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bfc0afc --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +MAN= installiso.8 +BINDIR= /usr/local/bin +MANDIR= /usr/local/man/man + +beforeinstall: + ${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \ + ${.CURDIR}/installiso.ksh ${DESTDIR}${BINDIR}/installiso + +.include diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1d6211 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Installiso + +Installiso is a utility for OpenBSD that facilitates creating custom ISO 9660 +installation images for unattended installation. See +[this](https://www.skreutz.com/posts/first-release-of-installiso/) blog post +for an introduction, and refer to the man page for details. + +## Install + +Run `make install` as root to install the `installiso` utility and man page. + +## Release + +Create an annotated tag, and an archive as follows, replacing `0.1.0` with the +current version. + + $ git tag --annotate --message "Version 0.1.0" 0.1.0 + $ git archive --format=tar.gz --prefix=installiso-0.1.0/ \ + --output installiso-0.1.0.tar.gz 0.1.0 + diff --git a/installiso.8 b/installiso.8 new file mode 100644 index 0000000..c2348d5 --- /dev/null +++ b/installiso.8 @@ -0,0 +1,140 @@ +.\" Copyright (c) 2021 Stefan Kreutz +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.Dd $Mdocdate: July 24 2021 $ +.Dt INSTALLISO 8 +.Os +.Sh NAME +.Nm installiso +.Nd Customize OpenBSD installation images +.Sh SYNOPSIS +.Nm installiso +.Op Fl v +.Ar command +.Op Ar arg ... +.Sh DESCRIPTION +The +.Nm +utility facilitates creating custom ISO 9660 installation images for the +unattended installation of +.Ox . +.Pp +The common options are as follows: +.Bl -tag -width Ds +.It Fl v +Verbose mode. +Causes +.Nm +to print informative messages. +Multiple +.Fl v +options increase the verbosity. +The maximum is 2. +By default, +.Nm +is quiet. +.El +.Pp +The commands are as follows: +.Bl -tag -width Ds +.It Cm fetch Oo Fl f Oc Oo Fl m Ar mirror Oc Oo Fl r Ar release Oc Oo Fl p Ar key Oc Oo Fl o Ar output Oc +Download and verify an official ISO 9660 installation image. +.Bl -tag -width 15n +.It Fl f +Force overwriting existing files. +By default, +.Cm fetch +will not overwrite existing files. +.It Fl m Ar mirror +The HTTP(S) +.Ox +.Ar mirror +to use. +Defaults to the mirror specified by +.Xr installurl 5 +or else +.Lk https://cdn.openbsd.org/pub/OpenBSD/ . +.It Fl r Ar release +The +.Ox +.Ar release . +Defaults to the latest development snapshot of -current. +.It Fl p Ar key +The public +.Xr signify 1 +.Ar key +used to verify the downloaded installation image. +Defaults to the key +.Pa /etc/signify/openbsd-*-base.pub +corresponding to the downloaded release. +.El +.It Cm patch Oo Fl f Oc Oo Fl i Ar install_conf Oc Oo Fl u Ar upgrade_conf Oc Oo Fl s Ar site_dir Oc Ar input Ar output +Patch an ISO 9660 installation image. +.Bl -tag -width 15n +.It Fl f +Force overwriting existing files. +By default, +.Cm patch +will not overwrite existing files. +.It Fl i Ar install_conf +Insert an +.Xr autoinstall 8 +response file for unattended installation. +.It Fl u Ar upgrade_conf +Insert an +.Xr autoinstall 8 +response file for unattended upgrade. +.It Fl s Ar site +Package and insert the directory +.Ar site +as a site-specific file set. +.It Ar input +The +.Ar input +installation image file. +.It Ar output +The +.Ar output +installation image file. +.El +.El +.Sh EXIT STATUS +.Ex -std installiso +.Sh EXAMPLES +Fetch the latest development snapshot: +.Bd -literal -offset indent +$ installiso -v fetch +.Ed +.Pp +Fetch a specific release: +.Bd -literal -offset indent +$ installiso -v fetch -r 6.9 +.Ed +.Pp +Create a custom image for unattended installation: +.Bd -literal -offset indent +$ doas installiso -v patch -i install.conf install69.iso custom.iso +.Ed +.Sh SEE ALSO +.Xr autoinstall 8 , +.Xr mkhybrid 8 , +.Xr rdsetroot 8 , +.Xr sysupgrade 8 , +.Xr vnconfig 8 +.Sh AUTHORS +.An Stefan Kreutz Aq Mt mail@skreutz.com +.Sh BUGS +The +.Nm +utility is currently limited to the amd64 architecture because it wasn't tested +on any other architecture. diff --git a/installiso.ksh b/installiso.ksh new file mode 100755 index 0000000..f28a60e --- /dev/null +++ b/installiso.ksh @@ -0,0 +1,293 @@ +#! /bin/ksh + +# Copyright (c) 2021 Stefan Kreutz +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# shellcheck disable=SC2001 + +set -o errexit +set -o nounset +set -o pipefail + +verbosity=0 +tmp= +vnode= + +function main { + trap 'cleanup' EXIT ERR INT + + while getopts :v option; do + case "$option" in + v) verbosity=$((verbosity+1)) ;; + :) err_exit "missing argument for option -$OPTARG" ;; + ?) err_exit "illegal option: -$OPTARG" ;; + esac + done + shift $((OPTIND-1)) + + if [ $# -eq 0 ]; then + err_exit "missing command" + fi + local _cmd="$1" + shift 1 + + tmp="$( readlink -f "$( mktemp -d -t )" )" + debug "Created temporary directory $tmp" + + case "$_cmd" in + fetch|patch) "$_cmd" "$@" ;; + *) err_exit "undefined command: $_cmd" ;; + esac +} + +function fetch { + local _force=0 + local _mirror _arch _release _pubkey _output _url _ftp_options _rel _iso + + while getopts :fm:r:p:o: option; do + case "$option" in + f) _force=1 ;; + m) _mirror="$OPTARG" ;; + r) _release="$OPTARG" ;; + p) _pubkey="$OPTARG" ;; + o) _output="$OPTARG" ;; + :) err_exit "missing argument for option -$OPTARG" ;; + ?) err_exit "illegal option: -$OPTARG" ;; + esac + done + shift $((OPTIND-1)) + + if [ $# -ne 0 ]; then + err_exit "too many arguments: $*" + fi + + _arch="$( uname -p )" + if [ "$_arch" != amd64 ]; then + err_exit "unsupported architecture: $_arch" + fi + + _mirror="${_mirror:-"$( get_mirror )"}" + case "$_mirror" in + http://*|https://*) ;; + *) err_exit "unsupported mirror: $_mirror" ;; + esac + + _url="${_mirror%/}/${_release:-snapshots}/$_arch" + info "Fetching from $_url/" + + case $verbosity in + 0) _ftp_options="-VM" ;; + 1) _ftp_options="-V" ;; + *) _ftp_options="-v" ;; + esac + + debug "Downloading SHA256.sig ..." + ftp "$_ftp_options" -o "$tmp/SHA256.sig" "$_url/SHA256.sig" + debug "Downloaded SHA256.sig" + + _rel="$( sed -n 's/^SHA256 (install\([0-9][0-9]\).iso) = .*/\1/p' "$tmp/SHA256.sig" )" + _iso="install${_rel}.iso" + _output="${_output:-"$_iso"}" + + if [ -e "$_output" ] && [ $_force -eq 0 ]; then + err_exit "file already exists: $_output" + fi + + debug "Downloading $_iso ..." + ftp "$_ftp_options" -o "$tmp/$_iso" "$_url/$_iso" + debug "Downloaded $_iso" + + _pubkey="${_pubkey:-"/etc/signify/openbsd-${_rel}-base.pub"}" + debug "Verifying $_iso using $_pubkey ..." + ( cd "$tmp" && signify -C -p "$_pubkey" -x SHA256.sig -q -- "$_iso" ) + debug "Verified $_iso" + + mv "$tmp/$_iso" "$_output" + if [ "$_iso" = "$_output" ]; then + info "Fetched $_iso" + else + info "Fetched $_iso as $_output" + fi +} + +function patch { + local _force=0 + local _compressed=0 + local _install_conf _upgrade_conf _site_dir _input_iso _output_iso + local _release _rel _arch _mime _rd_path + + while getopts :fi:u:s: option; do + case "$option" in + f) _force=1 ;; + i) _install_conf="$OPTARG" ;; + u) _upgrade_conf="$OPTARG" ;; + s) _site_dir="$OPTARG" ;; + :) err_exit "missing argument for option -$OPTARG" ;; + ?) err_exit "illegal option: -$OPTARG" ;; + esac + done + shift $((OPTIND-1)) + + if [ $# -lt 2 ]; then + err_exit "missing arguments" + fi + _input_iso="$1" + _output_iso="$2" + shift 2 + + if [ $# -ne 0 ]; then + err_exit "too many arguments: $*" + fi + + info "Unpacking ISO 9660 image ..." + vnode="$( vnconfig "$_input_iso" )" + debug "Configured $_input_iso as vnode disk $vnode" + mkdir "$tmp/mnt" + mount -t cd9660 "/dev/${vnode}c" "$tmp/mnt" + debug "Mounted vnode disk $vnode" + mkdir "$tmp/cd" + tar -C "$tmp/mnt" -c -f - . | tar -C "$tmp/cd" -x -p -f - + debug "Copied contents of $_input_iso to disk" + umount "$tmp/mnt" + debug "Unmounted vnode disk $vnode" + vnconfig -u "$vnode" + vnode= + debug "Unconfigured vnode disk $vnode" + info "Unpacked ISO 9660 image" + + debug "Inspecting directory layout ..." + _rd_path="$( cd "$tmp/cd" && echo */*/bsd.rd )" + _release="$( echo "$_rd_path" | sed 's,^\([0-9]\)\.\([0-9]\)/\([^/]*\)/bsd.rd,\1.\2,' )" + _rel="$( echo "$_rd_path" | sed 's,^\([0-9]\)\.\([0-9]\)/\([^/]*\)/bsd.rd,\1\2,' )" + debug "Inferred release $_release resp. $_rel" + _arch="$( echo "$_rd_path" | sed 's,^\([0-9]\)\.\([0-9]\)/\([^/]*\)/bsd.rd,\3,' )" + debug "Inferred arch $_arch" + + if [ "$_arch" != amd64 ]; then + err_exit "unsupported architecture: $_arch" + fi + + if [ -e "$_output_iso" ] && [ $_force -eq 0 ]; then + err_exit "file already exists: $_output_iso" + fi + + info "Patching RAMDISK kernel bsd.rd ..." + _mime="$( file --brief --mime "$tmp/cd/$_release/$_arch/bsd.rd" )" + if [ "$_mime" = "application/x-gzip" ]; then + _compressed=1 + gzip -d -o "$tmp/bsd.rd" "$tmp/cd/$_release/$_arch/bsd.rd" + debug "Uncompressed RAMDISK kernel bsd.rd" + else + cp "$tmp/cd/$_release/$_arch/bsd.rd" "$tmp/bsd.rd" + fi + rdsetroot -x "$tmp/bsd.rd" "$tmp/disk.fs" + debug "Extracted disk image from RAMDISK kernel bsd.rd" + vnode="$( vnconfig "$tmp/disk.fs" )" + debug "Configured RAMDISK kernel's disk image as vnode disk $vnode" + mount "/dev/${vnode}a" "$tmp/mnt" + debug "Mounted vnode disk $vnode" + if [ -n "${_install_conf:-}" ]; then + install -o root -g wheel -m 0644 -C "$_install_conf" "$tmp/mnt/auto_install.conf" + debug "Installed autoinstall(8) install response file $_install_conf" + fi + if [ -n "${_upgrade_conf:-}" ]; then + install -o root -g wheel -m 0644 -C "$_upgrade_conf" "$tmp/mnt/auto_upgrade.conf" + debug "Installed autoinstall(8) upgrade response file $_upgrade_conf" + fi + umount "$tmp/mnt" + debug "Unmounted vnode disk $vnode" + vnconfig -u "$vnode" + vnode= + debug "Unconfigured vnode disk $vnode" + rdsetroot "$tmp/bsd.rd" "$tmp/disk.fs" + debug "Inserted modified disk image into RAMDISK kernel bsd.rd" + debug "Patched RAMDISK kernel" + if [ $_compressed -eq 1 ]; then + gzip -9fnq "$tmp/bsd.rd" + mv "$tmp/bsd.rd.gz" "$tmp/bsd.rd" + debug "Compressed patched RAMDISK kernel bsd.rd" + fi + + info "Patching ISO 9660 image contents ..." + install -o root -g 2000 -m 0755 -C "$tmp/bsd.rd" "$tmp/cd/$_release/$_arch/bsd.rd" + debug "Installed modified RAMDISK kernel bsd.rd" + if [ -n "${_site_dir:-}" ]; then + if [ -e "$_site_dir/install.site" ] && [ ! -x "$_site_dir/install.site" ]; then + warn "$_site_dir/install.site is not executable" + fi + ( cd "$_site_dir" && tar -c -z -f "$tmp/cd/$_release/$_arch/site${_rel}.tgz" . ) + ( cd "$tmp/cd/$_release/$_arch" && ls -l > index.txt ) + debug "Installed site-specific file set $_site_dir" + fi + debug "Patched ISO 9660 image contents" + + info "Creating bootable ISO 9660 image ..." + # Source: https://github.com/openbsd/src/blob/1bc16d1a27cb5482308dc0201812e706df3d7287/distrib/amd64/iso/Makefile#L80 + if ! mkhybrid -a -R -T -L -l -d -D -N -o "${_output_iso}" \ + -A "Custom OpenBSD ${_release} ${_arch} Install CD" \ + -P "Copyright (c) $(date +%Y) Theo de Raadt, The OpenBSD project" \ + -p "Generated using installiso(8)" \ + -b "${_release}/${_arch}/cdbr" -c "${_release}/${_arch}/boot.catalog" \ + "${tmp}/cd" + then + rm -f "$_output_iso" + err_exit "failed to create bootable ISO 9660 image" + fi + info "Created bootable ISO 9660 image $_output_iso" +} + +function cleanup { + trap - EXIT ERR INT + set +o errexit + debug "Cleaning up ..." + if [ -n "${vnode:-}" ]; then + if mount | grep -qe "^/dev/$vnode" ; then + umount "/dev/$vnode" && debug "Unmounted vnode disk $vnode" + fi + vnconfig -u "$vnode" && debug "Unconfigured vnode disk $vnode" + fi + if [ -n "${tmp:-}" ]; then + rm -rf "$tmp" && debug "Removed temporary directory $tmp" + fi +} + +function get_mirror { + grep -v -e '^[:space:]*$' -e '^#' /etc/installurl | head -n 1 || + echo "https://cdn.openbsd.org/pub/OpenBSD/" +} + +function err_exit { + print -u2 -- "$*" + exit 1 +} + +function warn { + print -u2 -- "$*" +} + +function info { + if [ $verbosity -lt 1 ]; then + return + fi + print -- "$*" +} + +function debug { + if [ $verbosity -lt 2 ]; then + return + fi + print -- "$*" +} + +main "$@" -- cgit v1.2.3