|
#!/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 |