diff options
Diffstat (limited to 'install-alpine-avf')
| -rwxr-xr-x | install-alpine-avf | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/install-alpine-avf b/install-alpine-avf new file mode 100755 index 0000000..f5b1dea --- /dev/null +++ b/install-alpine-avf @@ -0,0 +1,117 @@ +#!/bin/sh +set -eu + +vm="/mnt/internal/linux" +marker="alpine-avf-install" +extras="vmlinuz initrd.img build_id vm_config.json" # everything but root_part + +die() { echo "$*" >&2; exit 1; } + +[ "$(id -u)" = 0 ] || die "run as root" +[ -e /sdcard ] && die "run this INSIDE the Terminal VM, not Android" +[ -d "$vm" ] || die "$vm not found; is this the Terminal VM?" +for c in python3 truncate stat blockdev findmnt e2fsck cmp dd sync reboot; do + command -v "$c" >/dev/null 2>&1 || die "need $c" +done + +if [ -f "$marker" ]; then + stage=2; img="$(cat "$marker")" +else + stage=1; img="$(CDPATH='' cd -- alpine-avf && pwd)" +fi + +for f in root_part $extras; do + [ -f "$img/$f" ] || die "missing $img/$f" +done + +if [ "$stage" = 1 ]; then + echo "stage 1: staging $img" + # Size the new partition to the larger of our image and the outgoing Debian + # root_part, so Alpine inherits the full disk. The file is sparse, so the + # larger apparent size costs no real space; avf-resize grows ext4 into it. + size="$(stat -c %s "$img/root_part")" + cur="$(stat -c %s "$vm/root_part" 2>/dev/null || echo 0)" + [ "$cur" -gt "$size" ] && size="$cur" + guid="$(cat /proc/sys/kernel/random/uuid)" + # Hide the stock backup while the install is in flight, so the app cannot + # restore it over the staged root; stage 2 puts it back after the swap. + [ -e "$vm/root_part_backup" ] && mv -f "$vm/root_part_backup" "$vm/root_part_backup.aside" + truncate -s "$size" "$vm/alpine_root" + # Append a temporary ALPINE partition; crosvm exposes it as a new /dev/vdaN. + python3 - "$vm/vm_config.json" "$guid" <<'PY' +import json, os, sys +p, guid = sys.argv[1], sys.argv[2] +c = json.load(open(p)); parts = c["disks"][0]["partitions"] +if not any(str(x.get("path", "")).endswith("/alpine_root") for x in parts): + parts.append({"label": "ALPINE", "path": "$PAYLOAD_DIR/alpine_root", "writable": True, "guid": guid}) +t = p + ".new" +with open(t, "w") as f: + json.dump(c, f, indent=4) + f.write("\n") +os.replace(t, p) +PY + printf '%s\n' "$img" > "$marker" + echo "staged; rebooting. Reopen Terminal, then run this again." + sync; reboot + exit 0 +fi + +echo "stage 2: installing $img" +target="$(python3 - "$vm/vm_config.json" <<'PY' +import json, sys +parts = json.load(open(sys.argv[1]))["disks"][0]["partitions"] +i = next((n for n, x in enumerate(parts, 1) if str(x.get("path", "")).endswith("/alpine_root")), None) +if i is None: + sys.exit("no ALPINE partition in vm_config.json; rm ./alpine-avf-install and rerun stage 1") +print("/dev/vda%d" % i) +PY +)" +echo "target: $target" +n=0; while [ ! -b "$target" ] && [ "$n" -lt 20 ]; do sleep 1; n=$((n + 1)); done +[ -b "$target" ] || die "$target absent; reboot and rerun, or rm ./$marker and rerun stage 1" +[ "$target" != "$(findmnt -no SOURCE /)" ] || die "$target is the live root; refusing" +want="$(stat -c %s "$img/root_part")" +[ "$(blockdev --getsize64 "$target")" -ge "$want" ] || die "target smaller than source" + +# dd through the block device (writing the file over virtiofs corrupts). +# Chunked with a sync per chunk to bound dirty page cache in the VM's RAM. +echo "copying root_part -> $target" +chunk=$((250 * 1024 * 1024)); n=0 +while [ $((n * chunk)) -lt "$want" ]; do + dd if="$img/root_part" of="$target" bs="$chunk" count=1 skip="$n" seek="$n" conv=notrunc iflag=fullblock status=none + sync; n=$((n + 1)) +done +# The copy of a freshly built image must check out clean; even "errors +# corrected" (rc 1/2) means the copy diverged from the verified source. +e2fsck -fy "$target" || die "e2fsck rc $? on $target: copy diverged; rerun stage 2" + +# Stage kernel/initrd/config over virtiofs, verified; fail before the swap. +for f in $extras; do + rm -f "$vm/$f.new" + n=0 + until cmp -s "$img/$f" "$vm/$f.new"; do + [ "$n" -lt 3 ] || die "could not write $vm/$f.new reliably; aborted before swap" + dd if="$img/$f" of="$vm/$f.new" bs=1M conv=fsync status=none + sync + n=$((n + 1)) + done +done + +echo "swapping root" +rm -f "$vm/root_part.previous" +mv -f "$vm/root_part" "$vm/root_part.previous" +mv -f "$vm/alpine_root" "$vm/root_part" || { + mv -f "$vm/root_part.previous" "$vm/root_part" + die "could not replace root_part" +} +for f in $extras; do + mv -f "$vm/$f.new" "$vm/$f" +done +rm -f "$vm/root_part.previous" +# stock Debian's extra-modules image; the new vm_config.json does not use it +rm -f "$vm/kernel_extras_part" +[ -e "$vm/root_part_backup.aside" ] && mv -f "$vm/root_part_backup.aside" "$vm/root_part_backup" +rm -f "$marker" +echo "installed; rebooting into Alpine. Reopen the Terminal app." +# the pause lets virtiofs settle the renames host-side before the VM dies +sync; sleep 2; reboot |