#!/bin/sh prog=${0##*/} version=@VERSION@ cleanup_mounts() { local i= cd / sync sleep 1 for i in $read_only_mounts; do mount -o remount,ro "$i" || echo "Warning: Failed to remount as read-only. Is modloop mounted?" done if [ -n "$umounts" ]; then umount $umounts fi } cleanup_installs() { if [ -n "$uninstalls" ]; then apk del --quiet syslinux fi } die() { echo "$@" >&2 cleanup_mounts cleanup_installs exit 1 } # find device for mountpoint find_dev() { local mnt="${1%/}" # strip trailing / awk "\$2 == \"$mnt\" {print \$1}" /proc/mounts } # check if given device is on usb bus on_usb_bus() { local dev="$1" [ -e /sys/block/$dev ] || return 1 local sysdev=$(readlink -f /sys/block/$dev/device) test "${sysdev##*/usb[0-9]}" != "$sysdev" } # mount source as loopback and set srcdir mount_srcdir() { local srcmnt=${MNT:-/mnt} mount -o loop -t iso9660 "$src" $srcmnt \ || die "Failed to mount loopback $src" umounts="$srcmnt" srcdir="$srcmnt" if ! [ -f "$srcdir"/.alpine-release ]; then die "No .alpine-release found on image $src" fi } vecho() { [ -z "$verbose" ] && return 0 echo "$@" } # check if given dir is read-only is_read_only() { local tmpfile=$(mktemp -p "$1" 2>/dev/null) [ -z "$tmpfile" ] && return 0 rm -f "$tmpfile" return 1 } # find what disk this partition belongs to find_disk_dev() { local i= sysfsname=${1#/dev/} sysfsname=${sysfsname//\/!} # cciss/c0d0 -> cciss!c0d0 if [ -e /sys/block/$sysfsname ]; then echo "/dev/${sysfsname//!/'/'}" return 0 fi for i in /sys/block/*/$sysfsname; do [ -e "$i" ] || continue echo "$i" | cut -d/ -f4 | sed -e 's:!:/:g' -e 's:^:/dev/:' return 0 done return 1 } find_syslinux_cfg() { # find where new syslinux.cfg is for i in boot/syslinux/syslinux.cfg syslinux.cfg; do if [ -e "$1"/$i ]; then syslinux_cfg=$i vecho "Found $syslinux_cfg" break fi done } fix_syslinux_kernel() { echo "Fixing $syslinux_cfg: kernel $1 -> $2" sed -i -e "/^\s*[Kk][Ee][Rr][Nn][Ee][Ll]\s/s|$1|$2|" \ "$destdir/$syslinux_cfg" } fix_syslinux_initrd() { echo "Fixing $syslinux_cfg: initrd $1 -> $2" sed -i -e "/^\s*[Ii][Nn][Ii][Tt][Rr][Dd]\s/s|$1|$2|" \ -e "/^\s*[Aa][Pp][Pp][Ee][Nn][Dd]\s/s|initrd=$1|initrd=$2|" \ "$destdir/$syslinux_cfg" } check_syslinux() { if [ -z "$syslinux_cfg" ]; then find_syslinux_cfg "$destdir" fi if [ -z "$syslinux_cfg" ]; then die "Could not find any syslinux.cfg. Aborting" fi # kernels for i in $(awk 'tolower($1) == "kernel" {print $2}' "$destdir"/$syslinux_cfg); do k="${destdir%/}/${i#/}" f=${k##*/} if [ -e "$k" ] && [ "${f#vmlinuz}" != "$f" ]; then continue fi if [ -e "${k%/*}"/vmlinuz-$f ] && [ -n "$fix_syslinux_cfg" ]; then fix_syslinux_kernel "$i" "${i%/*}"/vmlinuz-$f elif ! [ -e "$k" ]; then echo "Warning: $syslinux_cfg: kernel $k was not found" echo " Run $0 -c -f "$destdir" to fix" fi done #initramfs initrds=$(awk 'tolower($1) == "initrd" {print $2}' \ "$destdir"/$syslinux_cfg) for i in $(awk 'tolower($1) == "append" {print $0}' \ "$destdir"/$syslinux_cfg); do case $i in initrd=*) initrds=${i#initrd=};; esac done for i in $initrds; do if [ -e "$destdir"/$i ]; then continue fi fname=${i##*/} flavor=${fname%.gz} new=${i%/*}/initramfs-$flavor if [ -e "$destdir"/$new ] && [ -n "$fix_syslinux_cfg" ]; then fix_syslinux_initrd "$i" "$new" else echo "Warning: initrd $i was not found. System will likely not boot" echo " Run $0 -f -c "$destdir" to fix" fi done } usage() { cat <<__EOF__ $prog $version usage: $prog [-fhUusv] SOURCE [DEST] $prog -c DIR Copy the contents of SOURCE to DEST and make DEST bootable. SOURCE can be a directory or a ISO image. DEST can be a mounted directory or a device. If DEST is ommitted /media/usb will be used. Options: -f Force overwrite existing files. Will overwrite syslinux.cfg if upgrade. -h Show this help. -k fix kernel and initrd name in syslinux.cfg if needed. -U Replace current alpine_dev in syslinux.cfg with UUID if UUID found. -u Upgrade mode. Keep existing syslinux.cfg and don't run syslinux. -s Force run syslinux, even if upgrade mode. -v Verbose mode. Display whats going on. -c Check syslinux.cfg in destination DIR. Use with -f to fix. __EOF__ exit 1 } while getopts "c:fhkUusv" opt; do case "$opt" in c) check_syslinux="$OPTARG";; f) force=1; fix_syslinux_cfg=1;; h) usage;; k) fix_syslinux_cfg=1;; U) replace_alpine_dev=1;; u) upgrade=1;; s) syslinux=1;; v) verbose=1;; esac done shift $(($OPTIND - 1)) src=${1} dest=${2:-/media/usb} if [ -n "$check_syslinux" ]; then destdir="$check_syslinux" check_syslinux exit 0 fi [ -z "$src" ] && usage srcdir= # Find the srcdir or srcurl. mount loopback if needed if [ -f "$src"/.alpine-release ]; then srcdir="`echo $src | sed -r 's,/$,,'`" else case "$src" in http://*|ftp://*) srcurl="$src";; *) mount_srcdir;; esac fi if [ -n "$srcdir" ]; then to_version=$(cat "$srcdir"/.alpine-release) fi # find target device if [ -d "$dest" ]; then dest=${dest%/} # strip trailing / if ! awk '{print $2}' /proc/mounts | grep -q "^$dest\$"; then mount "$dest" || die "Failed to mount $dest" umounts="$umounts $dest" elif [ -n "$syslinux" ]; then die "Cannot run syslinux on mounted device" else nosyslinux=1 fi destdir="$dest" dest=$(find_dev "$destdir") elif [ -b "$dest" ]; then destdir="/media/${dest##*/}" mkdir -p "$destdir" mount "$dest" "$destdir" || die "Failed to mount $dest on $destdir" umounts="$umounts $destdir" fi # remount as rw if needed if is_read_only "$destdir"; then vecho "Remounting $destdir as read/write" mount -o remount,rw "$dest" || die "Failed to remount $destdir as rw" read_only_mounts="$read_only_mounts $destdir" fi # fish out label, uuid and type eval $(blkid $dest | cut -d: -f2-) vecho "Using $dest as target (mounted on $destdir)" # find parent device (i.e sda) dev="$dest" while [ -L "$dev" ]; do dev=$(readlink -f $dev) done parent_dev=$(find_disk_dev $dev) # check if this files exist and not in upgrade mode if [ -z "$upgrade" ] && [ -z "$force" ]; then for i in boot apks syslinux.cfg .alpine-release; do [ -e "$destdir"/$i ] && die "$destdir/$i already exists. Use -u to upgrade." done fi # check if its same version if [ -n "$upgrade" ] && [ -e "$destdir"/.alpine-release ]; then from_version=$(cat "$destdir"/.alpine-release) if [ -z "$force" ] && [ -n "$to_version" ] && [ "$from_version" = "$to_version" ]; then die "Source and target seems to have same version ($from_version). Aborting." fi fi # Display what versions we are installing/upgrading if [ -n "$from_version" ]; then echo "Upgrading $dest from $from_version to $to_version" else echo "Copying $to_version to $dest (mounted on $destdir)" fi # remove partial upgrades if any. rm -rf "$destdir"/.new "$destdir"/.old mkdir -p "$destdir"/.new || die "Failed to create $destdir/.new" # check that we have the space we need # we calculate on MB since shell arthimetic gets problems with big disks # and bytes. free_blocks=$(stat -f -c "%f" "$destdir") block_size=$(stat -f -c "%s" "$destdir") blocks_per_mb=$(( 1024 * 1024 / $block_size)) available_space=$(( $free_blocks / $blocks_per_mb )) vecho "Available space: $available_space MiB" if [ -n "$srcdir" ]; then needed_space=$(cd "$srcdir" && du -m -s -c boot apks .alpine-release | awk '$2 == "total" {print $1}') vecho "Needed space: $needed_space MiB" [ $available_space -lt $needed_space ] \ && die "Not enough space on $destdir. Aborting." # copy the files to .new for i in boot apks syslinux.cfg .alpine-release; do if [ -e "$srcdir"/$i ]; then vecho "Copying $srcdir/$i to $destdir/.new/" cp -a "$srcdir"/$i "$destdir"/.new/ fi done elif [ -n "$srcurl" ]; then cd "$destdir"/.new ${WGET:-wget} -O - "$srcurl" | uniso \ || die "Failed to download or extract $srcurl" echo "" fi # find where new syslinux.cfg is find_syslinux_cfg "$destdir"/.new # abort early in case unexpected trouble if [ -z "$syslinux_cfg" ]; then die "Could not find any syslinux.cfg on new iso?" fi # make sure files are really there before we replace existing vecho "Flushing cache..." sync vecho "Replacing existing files..." mkdir -p "$destdir"/.old || die "Failed to create $destdir/.old" tomove="boot apks syslinux.cfg .alpine-release" # move current files to .old for i in $tomove; do if [ -e "$destdir"/$i ]; then mv "$destdir"/$i "$destdir"/.old/ || die "Failed to move $destdir/$i to $destdir/.old/" fi done # keep any existing syslinux.cfg if [ -e "$destdir"/.old/$syslinux_cfg ]; then mv "$destdir"/.old/$syslinux_cfg "$destdir"/.new/$syslinux_cfg elif [ -e "$destdir"/.old/syslinux.cfg ] \ && [ -e "$destdir"/.new/boot/syslinux/syslinux.cfg ]; then echo "Warning: moving syslinux.cfg to boot/syslinux/syslinux.cfg" >&2 mv "$destdir"/.old/syslinux.cfg "$destdir"/.new/boot/syslinux if [ -z "$syslinux" ]; then echo " You might need run: syslinux $dest" >&2 fi fi # move .new to current for i in $tomove; do if [ -e "$destdir"/.new/$i ]; then mv "$destdir"/.new/$i "$destdir"/ \ || die "Failed to move $destdir/.new/ to $destdir" fi done if [ -n "$replace_alpine_dev" -o -z "$upgrade" ] && [ -n "$UUID" ]; then sed -E -i -e "s/alpine_dev=[^ \t:]+/alpine_dev=UUID=$UUID/" \ "$destdir"/$syslinux_cfg fi # verify syslinux.cfg check_syslinux # cleanup [ -z "$keep_old" ] && rm -rf "$destdir"/.old "$destdir"/.new # If we only copy then we are done. if [ -n "$upgrade" ] && [ -z "$syslinux" ]; then cleanup_installs cleanup_mounts exit 0 fi # prevent running syslinux on mounted device if [ -n "$nosyslinux" ]; then echo "Warning: Can not run syslinux on a mounted device" echo " You might need run syslinux manually and install MBR manually" exit 0 fi echo "Making $dest bootable..." if ! [ -x "$(which syslinux)" ]; then apk add --quiet syslinux || die "Failed to install syslinux" uninstalls="syslinux" fi # we need to unmount the device before we can run syslinux cleanup_mounts fsync $dest syslinux $dest if [ -b $parent_dev ]; then dd if=/usr/share/syslinux/mbr.bin of=$parent_dev else echo "Warning: Could not find the parent device for $dest" fi cleanup_installs