Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active May 12, 2020 14:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smoser/3c2ee3ece2a389b322ae4c15b5124379 to your computer and use it in GitHub Desktop.
Save smoser/3c2ee3ece2a389b322ae4c15b5124379 to your computer and use it in GitHub Desktop.
repack ubuntu desktop iso with console output

repack ubuntu iso without graphical login.

This was useful for me to use remotely. Basically a live server image.

Basically run:

./fix-iso consolify -vv ubuntu-18.04.2.0-desktop-amd64.iso.dist ubuntu-18.04.2.0-desktop-amd64.iso

And then

qemu-system-x86_64 -enable-kvm -device virtio-scsi-pci,id=virtio-scsi-xkvm \
    -object rng-random,filename=/dev/urandom,id=objrng0 \
    -device virtio-rng-pci,rng=objrng0,id=rng0 \
    -device virtio-net-pci,netdev=net00 \
    -netdev type=user,id=net00 \
    -drive file=ubuntu-18.04.2.0-desktop-amd64.iso,id=disk00,if=none,format=raw,index=0 \
    -device virtio-blk,drive=disk00,serial=ubuntu-18.04.2.0-desktop-amd64.iso \
    -snapshot -m 2G
#!/bin/bash
VERBOSITY=0
unset TEMP_D
TEMP_D=""
error() { echo "$@" 1>&2; }
fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; }
failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; }
Usage() {
local ufunc="${1:-main}_Usage"
"$ufunc"
}
bad_Usage() {
Usage "$1" 1>&2;
[ $# -eq 0 ] || shift
[ $# -eq 0 ] || error "$@";
return 1;
}
cleanup() {
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
vflag() {
# vflag([change=+0], [base=$VERBOSITY])
# write to stdout the 'vflag' (-v or -vv ..) for expected verbosity.
# vflag(-1, 3) = "-vv"
local myv=$((${2-${VERBOSITY}}${1:-+0}))
[ "$myv" -le "0" ] && return
local flag="-" i=0
while [ $i -lt $myv ] && i=$((i+1)); do
flag="${flag}v"
done
echo "$flag"
}
find_cmd() {
local cmd="$1" found="" p=""
shift
if found=$(command -v "$cmd"); then
echo "$found"
return 0
fi
for p in "$@"; do
[ -x "$p/$cmd" ] && { echo "$p/$cmd"; return 0; }
done
return 1
}
extract_Usage() {
cat <<EOF
Usage: ${0##*/} extract iso_in out_d
Extract the iso to out_d.
EOF
}
extract_iso() {
local iso="$1" out_d="$2"
shift 2
[ -d "$out_d" ] || mkdir -p "$out_d" || {
error "Failed to create dir '$out_d'"
return 1
}
[ -f "$iso" ] || { error "$iso: not a file."; return 1; }
debug 1 "extracting iso $iso to $out_d"
bsdtar "--directory=$out_d" -xf "$iso" "$@" ||
{ error "Failed to extract iso '$iso'"; return 1; }
# needed or cleanup will fail.
find "$out_d/" -type d -exec chmod u+w '{}' \; ||
{ error "Failed to give user write perms."; return 1; }
local out=""
if [ -f "${out_d}/images/efiboot.img" ]; then
debug 1 "creating images/isohdpfx.bin from $iso"
out=$(dd if=$iso bs=432 count=1 of="$out_d/images/isohdpfx.bin" 2>&1) ||
{ error "Failed to isohdpfx.bin from front of $iso"; return 1; }
fi
}
extract_main() {
local short_opts="hv"
local long_opts="help,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage get_krd; return; }
local cur="" next="" iso_in="" out_d=""
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage extract; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -eq 2 ] || {
bad_Usage extract "Expected 2 args, got $# ($*)"
return;
}
iso_in="$1"
out_d="$2"
[ -f "$iso_in" ] || { bad_Usage extract "$1: not a file"; return; }
[ ! -e "$out_d" -o -d "$out_d" ] || {
bad_Usage extract "$2: exists but is not a directory.";
return;
}
extract_iso "$iso_in" "$out_d" || return
return 0
}
pack_Usage() {
cat <<EOF
Usage: pack [options] iso_dir iso_file
Create iso_file from iso_dir.
EOF
}
cat_isohdpfx() {
# isolinux 3:6.03+dfsg1-2 /usr/lib/ISOLINUX/isohdpfx.bin
base64 --decode <<"EOF"
M+2QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJAz7fqO1bwAfPv8ZjHbZjHJZlNmUQZXjt2O
xVK+AHy/AAa5AAHzpepLBgAAUrRBu6pVMckw9vnNE3IWgftVqnUQg+EBdAtmxwbzBrRC6xXrAjHJ
WlG0CM0TWw+2xkBQg+E/UffhU1JQuwB8uQQAZqGwB+hEAA+CgABmQIDHAuLyZoE+QHz7wHhwdQn6
vOx76kR8AADogwBpc29saW51eC5iaW4gbWlzc2luZyBvciBjb3JydXB0Lg0KZmBmMdJmAwb4e2YT
Fvx7ZlJmUAZTagFqEInmZvc26HvA5AaI4YjFkvY27nuIxgjhQbgBAooW8nvNE41kEGZhw+geAE9w
ZXJhdGluZyBzeXN0ZW0gbG9hZCBlcnJvci4NCl6stA6KPmIEswfNEDwKdfHNGPTr/QAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
EOF
}
pack_iso() {
local isocmd="" iso="$1" in_d="$2" ret=""
[ -d "$in_d" ] || { error "$in_d: not a directory"; return 1; }
assert_tmpdir
local hdpfx="${TEMP_D}/isohdpfx.bin" efiimg="boot/grub/efi.img"
local label="ubuntu-repack"
cat_isohdpfx > "$hdpfx" || { error "Failed writing $hdpfx"; return 1; }
if [ -f "${in_d}/$efiimg" ]; then
isocmd=(
xorriso -as mkisofs
-output "$iso"
-V "$label"
-isohybrid-mbr "${hdpfx}"
-eltorito-catalog isolinux/boot.cat
-eltorito-boot isolinux/isolinux.bin
-no-emul-boot -boot-load-size 4 -boot-info-table
-eltorito-alt-boot
-e "$efiimg" -no-emul-boot -isohybrid-gpt-basdat
"${in_d}"
)
else
isocmd=(
mkisofs
-V "$label" -JR -o "$iso"
-c isolinux/boot.cat -b isolinux/isolinux.bin
-no-emul-boot -boot-load-size 4 -boot-info-table
-joliet-long
"$in_d"
)
fi
local isolinux="$in_d/isolinux/isolinux.bin"
if [ -e "$isolinux" -a ! -w "$isolinux" ]; then
chmod u+w "$isolinux" || {
error "Failed adding write to $isolinux"
return 1
}
fi
debug 2 "creating $iso: ${isocmd[*]}"
[ ! -e "$iso" ] || rm -f "$iso" ||
{ error "Failed to remove existing $iso"; return 1; }
if [ $VERBOSITY -lt 2 ]; then
local out=""
out=$("${isocmd[@]}" 2>&1)
ret=$?
else
"${isocmd[@]}"
ret=$?
fi
[ $ret -eq 0 ] || {
rm -f "$iso"
error "Failed to create iso $iso from $in_d"
return $ret
}
}
pack_main() {
local short_opts="hv"
local long_opts="help,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage get_krd; return; }
local cur="" next="" iso_in="" out_d=""
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage get_krd; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -eq 2 ] || {
bad_Usage pack "Expected 2 args, got $# ($*)"
return;
}
in_d="$1"
iso="$2"
[ -d "$in_d" ] || {
bad_Usage pack "$1: is not a directory"
return;
}
pack_iso "$iso" "$in_d" || fail
error "Created $iso from $in_d"
return 0
}
dir2squashfs() {
local src_d="$1" out="$2" owner="${3:-$(id -u):$(id -g)}"
local tmpfile ret="" cmd=""
tmpfile="${out}.${0##*/}.$$" || return
cmd=( mksquashfs "$src_d" "$tmpfile" -xattrs -comp xz )
debug 1 "starting: ${cmd[*]}"
"${cmd[@]}"
ret=$?
debug 1 "finished: returned $ret"
if [ $ret -eq 0 ]; then
chown "$owner" "$tmpfile" ||
{ error "failed chown $owner $tmpfile"; return 1; }
mv "$tmpfile" "$out" ||
{ error "failed to move file to $out"; return 1; }
else
rm -f "$tmpfile"
error "mksquashfs failed [$ret]: ${cmd[*]}."
fi
return $ret
}
consolify_Usage() {
cat <<EOF
Usage: ${0##*/} consolify [-v] input.iso output.iso
modify input.iso to disable graphics and other server-like things.
options:
-v | --verbose be more verbose
EOF
}
consolify_main() {
local short_opts="hv"
local long_opts="help,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage consolify; return; }
local cur="" next="" iso_in="" iso_out="" mp=""
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage consolify; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -ge 2 ] || {
bad_Usage edit "must provide iso_in and iso_out.";
return;
}
iso_in="$1"
iso_out="$2"
shift 2
[ -f "$iso_in" ] || { fail "$iso_in: not a file"; return 1; }
local iso_out_fp=""
iso_out_fp=$(realpath "$iso_out") ||
fail "Failed to get full path to '$iso_out'"
if [ -d "$iso_in" ]; then
debug 1 "using source directory $iso_in"
mp="$iso_in"
else
assert_tmpdir
[ -n "$mp" ] || mp="$TEMP_D/workd"
[ -d "$mp" ] || mkdir -p "$mp" ||
fail "Failed to create dir '$mp'"
fi
debug 1 "iso_in=$iso_in iso_out=$iso_out"
local start_d="$PWD" cmd="" arg=""
cmd=( "$0" consolify-iso $(vflag) "$mp" )
if [ ! -d "$iso_in" ]; then
extract_iso "$iso_in" "$mp" || return
fi
debug 1 "invoking ${cmd[*]} in $PWD"
"${cmd[@]}"
ret=$?
if [ $ret -ne 0 ]; then
debug 1 "cmd returned $ret. Not creating '$iso_out'"
failrc $ret
fi
pack_iso "$iso_out_fp" "$mp" ||
fail "Failed to pack iso."
error "created $iso_out."
}
consolify_iso_main() {
local short_opts="hv"
local long_opts="help,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage update_from; return; }
local cur="" next="" atx_d="" iso_in="" iso_out=""
local mp="" changedir=false
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage update_from; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
local iso_d="$1"
local img="$iso_d/casper/filesystem.squashfs"
debug 1 "Fixing default options"
local conparms="console=tty0 console=ttyS0,115200n8"
sed -i "s/quiet splash/$conparms/" \
"$iso_d/isolinux/txt.cfg" "$iso_d/boot/grub/grub.cfg" || {
error "Failed to edit isolinux/txt.cfg or boot/grub/grub.cfg"
return 1;
}
local s_opts="serial --speed=115200; " bgrub="boot/grub/grub.cfg"
s_opts="${s_opts}terminal_input serial console; "
s_opts="${s_opts}terminal_output serial console; "
sed -i "/set timeout=/s/^/$s_opts/" "$iso_d/$bgrub" &&
grep -q "$s_opts" "$iso_d/$bgrub" || {
error "Failed to add serial opts to $bgrub"
return 1
}
debug 1 "Calling mount-image-callback to edit ${img#${iso_d}/}"
sudo mount-image-callback --overlay --system-resolvconf "$img" -- \
"$0" consolify_dir _MOUNTPOINT_ "$img.new" ||
{ error "Failed to consolify $img"; return 1; }
mv --force "$img.new" "$img" ||
{ error "Failed to rename $img.new to $img"; return 1; }
debug 1 "updated $img"
return 0
}
consolify_dir_main() {
local d="$1" out="$2"
chroot "$d" env DEBIAN_FRONTEND=noninteractive sh -exc '
apt-get --purge --assume-yes remove snapd
apt-get update --quiet
apt-get install --no-install-recommends --assume-yes \
openssh-server ssh-import-id \
screen tmux git
rm -f /etc/ssh/ssh_host_*_key
' </dev/null
cat > "$d/lib/systemd/system/ssh-keygen.service" <<"EOF"
[Unit]
Description=OpenSSH Server Key Generation
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
PartOf=ssh.service
[Service]
ExecStart=/bin/sh -c '\
fails=0; \
for kt in rsa ecdsa ed25519; do \
f="/etc/ssh/ssh_host_"$kt"_key"; \
[ -f "$f" ] && { echo "$kt: $f existed."; continue; }; \
ssh-keygen -t "$kt" -f "$f" -C "" -N "" || \
{ echo "$kt failed"; fails=$((fails+1)); }; \
done; \
exit $fails'
Type=oneshot
RemainAfterExit=yes
[Install]
WantedBy=ssh.service
Alias=sshd.service
EOF
chroot "$d" systemctl set-default -f multi-user.target
chroot "$d" systemctl enable serial-getty@ttyS0.service
chroot "$d" systemctl enable ssh-keygen.service
dir2squashfs "$d" "$2" || return 1
return
}
edit_Usage() {
cat <<EOF
Usage: ${0##*/} edit [ options ] [iso_in|dir] iso_out [cmd [arg1 [.. ]]]
extract iso_in to a temp dir, then execute cmd. Default cmd is \$SHELL.
extraction dir is set in ISO_D variable.
See also '--cd' and '--mountpoint'
If an argument in cmd is ISO_D then it will be replaced with the value.
options:
-m | --mountpoint MP mount the directory to MP rather than a tmpdir.
-C | --cd change directory to ISO_DIR before executing cmd.
EOF
}
assert_tmpdir() {
[ -n "$TEMP_D" ] && return 0
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
fail "failed to make tempdir"
}
edit_main() {
local short_opts="hCm:Sv"
local long_opts="help,cd,mountpoint:,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage edit; return; }
local cur="" next="" iso_in="" iso_out=""
local mp="" changedir=false
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage edit; exit 0;;
-C|--cd) changedir=true;;
-m|--mountpoint) mp=$next; shift;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -ge 2 ] || {
bad_Usage edit "must provide iso_in and iso_out.";
return;
}
iso_in="$1"
iso_out="$2"
shift 2
[ -f "$iso_in" ] || { fail "$iso_in: not a file"; return 1; }
local iso_out_fp=""
iso_out_fp=$(realpath "$iso_out") ||
fail "Failed to get full path to '$iso_out'"
if [ -d "$iso_in" ]; then
debug 1 "using source directory $iso_in"
mp="$iso_in"
else
assert_tmpdir
[ -n "$mp" ] || mp="$TEMP_D/workd"
[ -d "$mp" ] || mkdir -p "$mp" ||
fail "Failed to create dir '$mp'"
fi
local start_d="$PWD" cmd="" arg=""
cmd=( env "ISO_D=$mp" "START_D=$start_d" )
if [ $# -eq 0 ]; then
if [ "${SHELL:-/bin/sh}" = "/bin/bash" ]; then
# norc to not get PS1 overwritten
set -- PS1="[update-${iso_in##*/} \W]$ " bash --norc
else
set -- ${SHELL}
fi
fi
for arg in "$@"; do
if [ "${arg}" = "ISO_D" ]; then
debug 1 "replaced string ISO_D in arguments arg ${#cmd[@]}"
arg="$mp"
fi
cmd[${#cmd[@]}]="$arg"
done
if [ "$mp" != "$iso_in" ]; then
extract_iso "$iso_in" "$mp" || return
fi
${changedir} && cd "$mp"
debug 1 "invoking ${cmd[*]} in $PWD"
"${cmd[@]}"
ret=$?
cd "$start_d"
if [ $ret -ne 0 ]; then
debug 1 "cmd returned $ret. Not creating '$iso_out'"
failrc $ret
fi
pack_iso "$iso_out_fp" "$mp" ||
fail "Failed to pack iso."
error "created $iso_out."
}
main_Usage() {
cat <<EOF
Usage: ${0##*/} [-v|-h] subcmd [args [...]]
Subcommands are:
edit extract, execute comand, re-pack
extract extract the iso to a directory
pack create an ISO from a directory.
consolify get rid of graphics in iso.
EOF
}
main() {
while [ $# -ne 0 ]; do
case "$1" in
-h|--help) Usage main; exit 0;;
-v|--verbose) VERBOSITY=$((VERBOSITY+1)); shift; continue;;
--) shift; break;;
-*|--*) bad_Usage main "$1: not a main argument."; return;;
*) break;;
esac
done
[ $# -gt 0 ] || { bad_Usage main; return; }
local submain="${1//-/_}_main"
type -t "$submain" >/dev/null 2>&1 || {
bad_Usage main "Unknown subcommand '$1'."
return
}
shift
trap cleanup EXIT
"$submain" "$@"
}
main "$@"
# vi: ts=4 expandtab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment