#!/bin/sh
# Narrow signed diagnostic kernel updater; no userspace package/manifest changes.
set -eu
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH
umask 077
action=${1:?verify, stage, activate, confirm or rollback required}
release=${2:?verified release directory required}
case "$action" in verify|stage|activate|confirm|rollback) ;; *) exit 2;; esac
test "$(id -u)" = 0
release=$(readlink -f "$release")
test -d "$release"
test "$(stat -c %u "$release")" = 0
test -z "$(find "$release" -maxdepth 1 -perm /022 -print)"
gpgv --keyring /usr/share/keyrings/octava-repo.gpg "$release/manifest.json.asc" "$release/manifest.json"
jq -e '(.schema == 1) and (.kind == "diagnostic-kernel") and (.release_id == "ma5671a-20261102") and (.board == "bpi-r4") and (.kernel_release == "5.4.260-octava-nft1") and (.files | length == 4) and ([.files[].name] | sort == ["System.map", "apply-ma5671a.sh", "postboot-ma5671a.sh", "uImage"]) and all(.files[]; (.sha256 | test("^[0-9a-f]{64}$")) and (.size > 0))' "$release/manifest.json" >/dev/null
jq -r '.files[] | [.sha256, .name, (.size|tostring)] | @tsv' "$release/manifest.json" | while read -r hash name size; do
 test "$(stat -c %s "$release/$name")" = "$size"
 test "$(sha256sum "$release/$name" | cut -d' ' -f1)" = "$hash"
done
old=/boot/bananapi/bpi-r4/linux-5.4-octava-nft1
new=/boot/bananapi/bpi-r4/linux-5.4-octava-ma5671a
selector=/boot/bananapi/bpi-r4/linux-5.4/uEnv.txt
state=/var/lib/octava-kernel-update/ma5671a-20261102
backup=/root/octava-kernel-ma5671a-20261102
original_selector=587b5df7498b4e165fe2aa6b5db8ebb59085909e91b599d0abecee9adcb692a6
old_image=51610740208ea93ed20b1464a0e87fa425603dd48d65735ffa3fad02d945982c
unit=octava-kernel-ma5671a-guard.service
check_hash() { test "$(sha256sum "$1" | cut -d' ' -f1)" = "$2"; }
check_hash "$old/uImage" "$old_image"
check_hash "$old/config-5.4.260-octava-nft1" 113c6a082bcf42d8ba93d30c85b5ef894b5c5a6f8b4e9546207e7e1a865c08f0
check_hash "$old/dtb/mt7988a-bananapi-bpi-r4-sdmmc-snand.dtb" 2e19055f8f3918d1e3ae67247ba55c3b48df162315ba3da76a25508b075403e0
test "$(tr -d '\000' </proc/device-tree/model)" = 'Bananapi BPI-R4'
test "$(cat /sys/class/net/lan0/address)" = 22:44:0e:0c:ae:f3
test "$(uname -r)" = 5.4.260-octava-nft1
test "$(dpkg-query -W -f='${Version}' octava-router-control)" = '0.1.0~dev509'
if test "$action" = verify; then printf 'VERIFIED_NO_MUTATION\n'; exit 0; fi
exec 9>/run/octava-kernel-update.lock
flock -n 9
if test "$action" = stage; then
 octava-cli --json config-status | jq -e '.error == null and .data.pending == false' >/dev/null
 check_hash "$selector" "$original_selector"
 test ! -e "$state"; test ! -e "$backup"; test ! -e "$new"
 test "$(df -Pk /boot | awk 'NR==2 {print $4}')" -gt 60000
 test "$(df -Pk / | awk 'NR==2 {print $4}')" -gt 350000
 octava-cli --json network | jq -e '.error == null and .data.pending == null and (.data.config.links[] | select(.name == "lan0") | .ipv4_mode == "static" and .addresses == ["192.168.100.214/24"])' >/dev/null
 install -d -m 0700 "$backup" "$state"
 tar -C /boot -czf "$backup/boot-before.tar.gz" bananapi
 tar -tzf "$backup/boot-before.tar.gz" >/dev/null
 cp "$selector" "$backup/uEnv.before"
 check_hash "$backup/uEnv.before" "$original_selector"
 cat /proc/sys/kernel/random/boot_id >"$state/boot.before"
 cp -a "$old" "$new"
 install -m 0644 "$release/uImage" "$new/uImage"
 install -m 0644 "$release/System.map" "$new/System.map-5.4.260-octava-nft1"
 sed 's/^service=linux-5.4-octava-nft1$/service=linux-5.4-octava-ma5671a/' "$old/uEnv.txt" >"$new/uEnv.txt"
 cmp "$release/uImage" "$new/uImage"
 sync
 printf 'staged\n' >"$state/status"
 printf 'STAGED_OLD_BOOT_SELECTION_UNCHANGED\n'
elif test "$action" = activate; then
 test "$(cat "$state/status")" = staged
 test "$(cat "$state/boot.before")" = "$(cat /proc/sys/kernel/random/boot_id)"
 check_hash "$selector" "$original_selector"
 cmp "$release/uImage" "$new/uImage"
 install -m 0700 "$release/postboot-ma5671a.sh" "$state/postboot.sh"
 printf '%s\n' "$release" >"$state/release.path"
 cat >"/etc/systemd/system/$unit" <<UNIT
[Unit]
Description=Octava diagnostic kernel confirmation guard
After=local-fs.target
[Service]
Type=oneshot
ExecStart=$state/postboot.sh
TimeoutStartSec=660
[Install]
WantedBy=multi-user.target
UNIT
 systemctl daemon-reload
 systemctl enable "$unit"
 printf 'armed\n' >"$state/status"
 sed 's/^service=linux-5.4-octava-nft1$/service=linux-5.4-octava-ma5671a/' "$backup/uEnv.before" >"$selector.ma5671a-new"
 cmp "$new/uEnv.txt" "$selector.ma5671a-new"
 sync
 mv "$selector.ma5671a-new" "$selector"
 sync
 cmp "$new/uEnv.txt" "$selector"
 printf 'ACTIVATED_REBOOT_REQUIRED\n'
elif test "$action" = confirm; then
 test "$(cat "$state/status")" = armed
 octava-cli --json config-status | jq -e '.error == null and .data.pending == false' >/dev/null
 test "$(cat "$state/boot.before")" != "$(cat /proc/sys/kernel/random/boot_id)"
 grep -q 'service=linux-5.4-octava-ma5671a' /proc/cmdline
 grep -q ' sfp_fixup_ignore_tx_fault_and_los$' /proc/kallsyms
 octava-cli --json health | jq -e '.error == null and .data.status == "ready" and .data.mode == "normal"' >/dev/null
 octava-cli --json network | jq -e '.error == null and .data.pending == null and (.data.config.links[] | select(.name == "lan0") | .ipv4_mode == "static" and .addresses == ["192.168.100.214/24"])' >/dev/null
 ping -I lan0 -c 2 -W 2 192.168.100.1 >/dev/null
 getent ahostsv4 repo.octava.online >/dev/null
 test -z "$(systemctl --failed --no-legend --plain)"
 printf 'confirmed\n' >"$state/status"
 sync
 systemctl disable "$unit"
 printf 'CONFIRMED_NEW_KERNEL_MANAGEMENT\n'
else
 check_hash "$backup/uEnv.before" "$original_selector"
 cp "$backup/uEnv.before" "$selector.ma5671a-rollback"
 sync
 mv "$selector.ma5671a-rollback" "$selector"
 sync
 check_hash "$selector" "$original_selector"
 printf 'rolled_back\n' >"$state/status"
 systemctl disable "$unit"
 printf 'OLD_KERNEL_SELECTED_REBOOT_REQUIRED\n'
fi
