aboutsummaryrefslogtreecommitdiff
path: root/install-alpine-avf
diff options
context:
space:
mode:
Diffstat (limited to 'install-alpine-avf')
-rwxr-xr-xinstall-alpine-avf117
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