123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- #! /bin/bash
- set -eE
- set -o pipefail
- # TODO:
- # - new mode using `udiskctl loop-setup` instead of `fuseiso`
- # - maybe find workaround for iso-patcher to work in fuse mode
- # despite https://github.com/containers/fuse-overlayfs/issues/377
- die() {
- echo >&2
- echo >&2 "ERROR: $*"
- echo >&2
- exit 1
- }
- usage() {
- cat <<EOF
- Usage: $0 [<options>] <input-iso> <output-iso>
- Options:
- --mode <mode> Force specified operation mode (default: use best available)
- fuse : more resource-friendly, needs fuseiso and fuse-overlayfs
- copy : more portable, needs more disk space, wears disk more
- --install-patcher, -l <script>
- Unpack install.img, run <script> with its location as single argument,
- and repack install.img for the output ISO
- --iso-patcher, -s <script>
- Run <script> with ISO contents location as single argument,
- before repacking output ISO.
- Forces "--mode copy" to avoid fuse-overlay bug
- https://github.com/containers/fuse-overlayfs/issues/377
- -V <volume-id> Use specified volume id instead of reusing the original one
- EOF
- }
- die_usage() {
- usage >&2
- die "$*"
- }
- [ $(whoami) != root ] || die "not meant to run as root"
- # select default operating mode
- OPMODE=fuse
- command -v fuseiso >/dev/null || { echo >&2 "fuseiso not found"; OPMODE=copy; }
- command -v fuse-overlayfs >/dev/null || { echo >&2 "fuse-overlayfs not found"; OPMODE=copy; }
- command -v 7z >/dev/null || die "required tool not found: 7z (e.g. p7zip-plugins in EPEL)"
- command -v fakeroot >/dev/null || die "required tool not found: fakeroot"
- command -v genisoimage >/dev/null || die "required tool not found: genisoimage"
- command -v isohybrid >/dev/null || die "required tool not found: isohybrid (package syslinux-utils or syslinux)"
- command -v isoinfo >/dev/null || die "required tool not found: isoinfo (package cdrkit-isotools?)"
- ISOPATCHER=""
- IMGPATCHER=""
- VOLID=""
- while [ $# -ge 1 ]; do
- case "$1" in
- --mode)
- [ $# -ge 2 ] || die_usage "--mode needs an argument"
- OPMODE="$2"
- shift
- ;;
- --iso-patcher|-s)
- [ $# -ge 2 ] || die_usage "--iso-patcher needs an argument"
- ISOPATCHER="$2"
- echo >&2 "NOTE: iso-patcher use, forcing 'copy' mode"
- OPMODE=copy
- shift
- ;;
- --install-patcher|-l)
- [ $# -ge 2 ] || die_usage "--install-patcher needs an argument"
- IMGPATCHER="$2"
- shift
- ;;
- -V)
- [ $# -ge 2 ] || die_usage "-V needs an argument"
- VOLID="$2"
- shift
- ;;
- --help|-h)
- usage
- exit 0
- ;;
- -*)
- die_usage "unknown flag '$1'"
- ;;
- *)
- break
- ;;
- esac
- shift
- done
- [ $# = 2 ] || die_usage "need exactly 2 non-option arguments"
- INISO="$1"
- OUTISO="$2"
- [ -r "$INISO" ] || die_usage "input ISO '$INISO' cannot be read"
- umountrm() {
- sync "$1"
- fusermount -u -z "$1"
- rmdir "$1"
- }
- USERMOUNTS=()
- exitcleanup() {
- set +e
- case $OPMODE in
- copy)
- rm -rf "$RWISO"
- ;;
- fuse)
- for MOUNT in "${USERMOUNTS[@]}"; do
- umountrm "$MOUNT"
- done
- rm -rf "$OVLRW" "$OVLWD"
- ;;
- *)
- die "unknown mode '$OPMODE'"
- ;;
- esac
- rm -rf "$INSTALLIMG"
- rm "$FAKEROOTSAVE"
- }
- trap 'exitcleanup' EXIT INT
- # (absolute path) where we unpack install.img
- INSTALLIMG=$(mktemp -d -p "$PWD" installimg.XXXXXX)
- # (absolute path) where we get a RW copy of input ISO contents
- RWISO=$(mktemp -d -p "$PWD" isorw.XXXXXX)
- # allow successive fakeroot calls to act as a single session
- FAKEROOTSAVE=$(realpath $(mktemp fakerootsave.XXXXXX))
- touch "$FAKEROOTSAVE" # avoid "does not exist" warning on first use
- FAKEROOT=(fakeroot -i "$FAKEROOTSAVE" -s "$FAKEROOTSAVE" --)
- ### produce patched iso contents in $RWISO
- # provide a RW view of ISO
- case $OPMODE in
- copy)
- 7z x "$INISO" -o"$RWISO"
- rm -rfv "$RWISO/[BOOT]"
- SRCISO="$RWISO"
- DESTISO="$RWISO"
- ;;
- fuse)
- MNT=$(mktemp -d isomnt.XXXXXX)
- OVLRW=$(mktemp -d ovlfs-upper.XXXXXX)
- OVLWD=$(mktemp -d ovlfs-work.XXXXXX)
- fuseiso "$INISO" "$MNT"
- MOUNTS+=("$MNT")
- # genisoimage apparently needs write access to those
- mkdir -p "$OVLRW/boot/isolinux"
- cp "$MNT/boot/isolinux/isolinux.bin" "$OVLRW/boot/isolinux/"
- chmod +w "$OVLRW/boot/isolinux/isolinux.bin"
- SRCISO="$MNT"
- DESTISO="$OVLRW"
- ;;
- *)
- die "unknown mode '$OPMODE'"
- ;;
- esac
- # maybe run install.img patcher
- if [ -n "$IMGPATCHER" ]; then
- bzcat "$SRCISO/install.img" | (cd "$INSTALLIMG" && "${FAKEROOT[@]}" cpio -idm)
- # patch install.img contents
- "${FAKEROOT[@]}" "$IMGPATCHER" "$INSTALLIMG" || die "IMG patcher exited in error: $?"
- # repack install.img
- (cd "$INSTALLIMG" && "${FAKEROOT[@]}" sh -c "find . | cpio -o -H newc") |
- bzip2 > "$DESTISO/install.img"
- fi
- # produce merged view
- case $OPMODE in
- copy)
- ;;
- fuse)
- # produce a merged iso tree
- fuse-overlayfs -o lowerdir="$MNT" -o upperdir="$OVLRW" -o workdir="$OVLWD" "$RWISO"
- MOUNTS+=("$RWISO")
- ;;
- *)
- die "unknown mode '$OPMODE'"
- ;;
- esac
- if [ -n "$ISOPATCHER" ]; then
- "${FAKEROOT[@]}" "$ISOPATCHER" "$RWISO" || die "ISO patcher exited in error: $?"
- fi
- # default value for volume id
- : ${VOLID:=$(isoinfo -i "$INISO" -d | grep "Volume id" | sed "s/Volume id: //")}
- "${FAKEROOT[@]}" genisoimage \
- -o "$OUTISO" \
- -v -r -J --joliet-long -V "$VOLID" -input-charset utf-8 \
- -c boot/isolinux/boot.cat -b boot/isolinux/isolinux.bin -no-emul-boot \
- -boot-load-size 4 -boot-info-table \
- \
- -eltorito-alt-boot -e boot/efiboot.img \
- -no-emul-boot \
- \
- $RWISO
- isohybrid --uefi "$OUTISO"
|