iso-remaster.sh 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. #! /bin/bash
  2. set -eE
  3. set -o pipefail
  4. # TODO:
  5. # - new mode using `udiskctl loop-setup` instead of `fuseiso`
  6. # - maybe find workaround for iso-patcher to work in fuse mode
  7. # despite https://github.com/containers/fuse-overlayfs/issues/377
  8. die() {
  9. echo >&2
  10. echo >&2 "ERROR: $*"
  11. echo >&2
  12. exit 1
  13. }
  14. usage() {
  15. cat <<EOF
  16. Usage: $0 [<options>] <input-iso> <output-iso>
  17. Options:
  18. --mode <mode> Force specified operation mode (default: use best available)
  19. fuse : more resource-friendly, needs fuseiso and fuse-overlayfs
  20. copy : more portable, needs more disk space, wears disk more
  21. --install-patcher, -l <script>
  22. Unpack install.img, run <script> with its location as single argument,
  23. and repack install.img for the output ISO
  24. --iso-patcher, -s <script>
  25. Run <script> with ISO contents location as single argument,
  26. before repacking output ISO.
  27. Forces "--mode copy" to avoid fuse-overlay bug
  28. https://github.com/containers/fuse-overlayfs/issues/377
  29. -V <volume-id> Use specified volume id instead of reusing the original one
  30. EOF
  31. }
  32. die_usage() {
  33. usage >&2
  34. die "$*"
  35. }
  36. [ $(whoami) != root ] || die "not meant to run as root"
  37. # select default operating mode
  38. OPMODE=fuse
  39. command -v fuseiso >/dev/null || { echo >&2 "fuseiso not found"; OPMODE=copy; }
  40. command -v fuse-overlayfs >/dev/null || { echo >&2 "fuse-overlayfs not found"; OPMODE=copy; }
  41. command -v 7z >/dev/null || die "required tool not found: 7z (e.g. p7zip-plugins in EPEL)"
  42. command -v fakeroot >/dev/null || die "required tool not found: fakeroot"
  43. command -v genisoimage >/dev/null || die "required tool not found: genisoimage"
  44. command -v isohybrid >/dev/null || die "required tool not found: isohybrid (package syslinux-utils or syslinux)"
  45. command -v isoinfo >/dev/null || die "required tool not found: isoinfo (package cdrkit-isotools?)"
  46. ISOPATCHER=""
  47. IMGPATCHER=""
  48. VOLID=""
  49. while [ $# -ge 1 ]; do
  50. case "$1" in
  51. --mode)
  52. [ $# -ge 2 ] || die_usage "--mode needs an argument"
  53. OPMODE="$2"
  54. shift
  55. ;;
  56. --iso-patcher|-s)
  57. [ $# -ge 2 ] || die_usage "--iso-patcher needs an argument"
  58. ISOPATCHER="$2"
  59. echo >&2 "NOTE: iso-patcher use, forcing 'copy' mode"
  60. OPMODE=copy
  61. shift
  62. ;;
  63. --install-patcher|-l)
  64. [ $# -ge 2 ] || die_usage "--install-patcher needs an argument"
  65. IMGPATCHER="$2"
  66. shift
  67. ;;
  68. -V)
  69. [ $# -ge 2 ] || die_usage "-V needs an argument"
  70. VOLID="$2"
  71. shift
  72. ;;
  73. --help|-h)
  74. usage
  75. exit 0
  76. ;;
  77. -*)
  78. die_usage "unknown flag '$1'"
  79. ;;
  80. *)
  81. break
  82. ;;
  83. esac
  84. shift
  85. done
  86. [ $# = 2 ] || die_usage "need exactly 2 non-option arguments"
  87. INISO="$1"
  88. OUTISO="$2"
  89. [ -r "$INISO" ] || die_usage "input ISO '$INISO' cannot be read"
  90. umountrm() {
  91. sync "$1"
  92. fusermount -u -z "$1"
  93. rmdir "$1"
  94. }
  95. USERMOUNTS=()
  96. exitcleanup() {
  97. set +e
  98. case $OPMODE in
  99. copy)
  100. rm -rf "$RWISO"
  101. ;;
  102. fuse)
  103. for MOUNT in "${USERMOUNTS[@]}"; do
  104. umountrm "$MOUNT"
  105. done
  106. rm -rf "$OVLRW" "$OVLWD"
  107. ;;
  108. *)
  109. die "unknown mode '$OPMODE'"
  110. ;;
  111. esac
  112. rm -rf "$INSTALLIMG"
  113. rm "$FAKEROOTSAVE"
  114. }
  115. trap 'exitcleanup' EXIT INT
  116. # (absolute path) where we unpack install.img
  117. INSTALLIMG=$(mktemp -d -p "$PWD" installimg.XXXXXX)
  118. # (absolute path) where we get a RW copy of input ISO contents
  119. RWISO=$(mktemp -d -p "$PWD" isorw.XXXXXX)
  120. # allow successive fakeroot calls to act as a single session
  121. FAKEROOTSAVE=$(realpath $(mktemp fakerootsave.XXXXXX))
  122. touch "$FAKEROOTSAVE" # avoid "does not exist" warning on first use
  123. FAKEROOT=(fakeroot -i "$FAKEROOTSAVE" -s "$FAKEROOTSAVE" --)
  124. ### produce patched iso contents in $RWISO
  125. # provide a RW view of ISO
  126. case $OPMODE in
  127. copy)
  128. 7z x "$INISO" -o"$RWISO"
  129. rm -rfv "$RWISO/[BOOT]"
  130. SRCISO="$RWISO"
  131. DESTISO="$RWISO"
  132. ;;
  133. fuse)
  134. MNT=$(mktemp -d isomnt.XXXXXX)
  135. OVLRW=$(mktemp -d ovlfs-upper.XXXXXX)
  136. OVLWD=$(mktemp -d ovlfs-work.XXXXXX)
  137. fuseiso "$INISO" "$MNT"
  138. MOUNTS+=("$MNT")
  139. # genisoimage apparently needs write access to those
  140. mkdir -p "$OVLRW/boot/isolinux"
  141. cp "$MNT/boot/isolinux/isolinux.bin" "$OVLRW/boot/isolinux/"
  142. chmod +w "$OVLRW/boot/isolinux/isolinux.bin"
  143. SRCISO="$MNT"
  144. DESTISO="$OVLRW"
  145. ;;
  146. *)
  147. die "unknown mode '$OPMODE'"
  148. ;;
  149. esac
  150. # maybe run install.img patcher
  151. if [ -n "$IMGPATCHER" ]; then
  152. bzcat "$SRCISO/install.img" | (cd "$INSTALLIMG" && "${FAKEROOT[@]}" cpio -idm)
  153. # patch install.img contents
  154. "${FAKEROOT[@]}" "$IMGPATCHER" "$INSTALLIMG" || die "IMG patcher exited in error: $?"
  155. # repack install.img
  156. (cd "$INSTALLIMG" && "${FAKEROOT[@]}" sh -c "find . | cpio -o -H newc") |
  157. bzip2 > "$DESTISO/install.img"
  158. fi
  159. # produce merged view
  160. case $OPMODE in
  161. copy)
  162. ;;
  163. fuse)
  164. # produce a merged iso tree
  165. fuse-overlayfs -o lowerdir="$MNT" -o upperdir="$OVLRW" -o workdir="$OVLWD" "$RWISO"
  166. MOUNTS+=("$RWISO")
  167. ;;
  168. *)
  169. die "unknown mode '$OPMODE'"
  170. ;;
  171. esac
  172. if [ -n "$ISOPATCHER" ]; then
  173. "${FAKEROOT[@]}" "$ISOPATCHER" "$RWISO" || die "ISO patcher exited in error: $?"
  174. fi
  175. # default value for volume id
  176. : ${VOLID:=$(isoinfo -i "$INISO" -d | grep "Volume id" | sed "s/Volume id: //")}
  177. "${FAKEROOT[@]}" genisoimage \
  178. -o "$OUTISO" \
  179. -v -r -J --joliet-long -V "$VOLID" -input-charset utf-8 \
  180. -c boot/isolinux/boot.cat -b boot/isolinux/isolinux.bin -no-emul-boot \
  181. -boot-load-size 4 -boot-info-table \
  182. \
  183. -eltorito-alt-boot -e boot/efiboot.img \
  184. -no-emul-boot \
  185. \
  186. $RWISO
  187. isohybrid --uefi "$OUTISO"