#!/bin/sh
# Signed manual kernel transaction. Never reboot, restart services or modify modules.
set -eu
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
umask 077
action=${1:?verify|stage|activate|status|confirm|rollback}
release=$(readlink -f "${2:?release directory required}")
case "$action" in verify|stage|activate|status|confirm|rollback) ;; *) exit 2;; esac
test "$(id -u)" = 0
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=="manual-security-kernel") and (.release_id=="cve98432-20261104-r2") and (.board=="bpi-r4") and (.kernel_release=="5.4.260-octava-nft1") and (.build_number==2) and ([.files[].name]|sort==["README-RU.md","System.map","uImage","update-security-kernel.sh"]) and all(.files[];(.sha256|test("^[0-9a-f]{64}$")) and (.size>0))' "$release/manifest.json" >/dev/null
hash(){ sha256sum "$1" | cut -d' ' -f1; }
check_hash(){ test "$(hash "$1")" = "$2"; }
jq -r '.files[]|[.name,.sha256,(.size|tostring)]|@tsv' "$release/manifest.json" | while read -r name sum bytes;do
 test -f "$release/$name";test ! -L "$release/$name"
 test "$(stat -c %u "$release/$name")" = 0
 test "$(stat -c %s "$release/$name")" = "$bytes";check_hash "$release/$name" "$sum"
done
test "$(uname -m)" = aarch64
test "$(uname -r)" = 5.4.260-octava-nft1
test "$(tr -d '\000' </proc/device-tree/model)" = 'Bananapi BPI-R4'
base=/boot/bananapi/bpi-r4
selector=$base/linux-5.4/uEnv.txt
new_service=linux-5.4-octava-sec98432-1
new=$base/$new_service
state=/var/lib/octava-kernel-update/cve98432-20261104-r2
backup=/root/octava-kernel-security-cve98432-20261104-r2
service(){ tr ' ' '\n' </proc/cmdline | sed -n 's/^service=//p'; }
idle(){
 if test -f /var/lib/octava-update/status.json;then
  phase=$(jq -r '.phase' /var/lib/octava-update/status.json)
  case "$phase" in idle|complete|available|checked|no_updates) ;; *) echo OTHER_UPDATE_NOT_IDLE >&2;exit 1;;esac
 fi
}
config_snapshot(){ octava-cli --json config-status | jq -ceS 'select(.error==null and .data.pending==false)|.data|{confirmed,pending,factory_sha256}'; }
ready(){ octava-cli --json health | jq -e '.error==null and .data.ready==true and .data.mode=="normal"' >/dev/null; }
original(){
 old_service=$(service)
 case "$old_service" in linux-5.4-octava-nft1|linux-5.4-octava-ma5671a) ;; *) echo UNSUPPORTED_RUNNING_KERNEL_SELECTION >&2;exit 1;;esac
 old=$base/$old_service
 expected=$(jq -er --arg name "$old_service" '.baselines[]|select(.service==$name)|.image_sha256' "$release/manifest.json")
 check_hash "$old/uImage" "$expected"
 check_hash "$old/config-5.4.260-octava-nft1" "$(jq -r '.config_sha256' "$release/manifest.json")"
 jq -r '.dtbs[]|[.name,.sha256]|@tsv' "$release/manifest.json" | while read -r name sum;do check_hash "$old/dtb/$name" "$sum";done
 test "$(sed -n 's/^service=//p' "$selector")" = "$old_service"
 test "$(sed -n 's/^service=//p' "$old/uEnv.txt")" = "$old_service"
 test -d /lib/modules/5.4.260-octava-nft1
}
new_ok(){
 cmp "$release/uImage" "$new/uImage"
 cmp "$release/System.map" "$new/System.map-5.4.260-octava-nft1"
 cmp "$state/selected.uEnv" "$new/uEnv.txt"
}
if test "$action" = verify;then original;idle;ready;test -z "$(systemctl --failed --plain --no-legend)";config_snapshot >/dev/null;echo VERIFIED_NO_MUTATION;exit 0;fi
if test "$action" = status;then
 saved=not_staged;test ! -f "$state/status" || saved=$(cat "$state/status")
 jq -n --arg state "$saved" --arg running "$(service)" --arg selected "$(sed -n 's/^service=//p' "$selector")" --arg build "$(uname -v)" '{state:$state,running_service:$running,selected_service:$selected,kernel_build:$build}'
 exit 0
fi
exec 9>/run/octava-kernel-update.lock
flock -n 9
idle
if test "$action" = stage;then
 original;ready;test -z "$(systemctl --failed --plain --no-legend)"
 if test -d "$state";then
  case "$(cat "$state/status")" in staged|rolled_back) ;; *) exit 1;;esac;new_ok
  cmp "$selector" "$backup/uEnv.before"
  config_snapshot >"$state/config.check";cmp "$state/config.before" "$state/config.check"
  printf staged >"$state/status";sync
  echo ALREADY_STAGED_NO_BOOT_CHANGE;exit 0
 fi
 test ! -e "$new";test ! -e "$new.preparing";test ! -e "$backup"
 boot_need=$(( $(du -sk "$old" | awk '{print $1}') + $(stat -c %s "$release/uImage") / 1024 + 8192 ))
 root_need=$(( $(du -sk /boot/bananapi | awk '{print $1}') * 2 + 8192 ))
 test "$(df -Pk /boot | awk 'NR==2 {print $4}')" -gt "$boot_need"
 test "$(df -Pk / | awk 'NR==2 {print $4}')" -gt "$root_need"
 install -d -m0700 "$state" "$backup"
 printf preparing >"$state/status"
 config_snapshot >"$state/config.before"
 cp "$selector" "$backup/uEnv.before"
 hash "$backup/uEnv.before" >"$state/selector.before.sha256"
 printf '%s\n' "$old_service" >"$state/service.before"
 cat /proc/sys/kernel/random/boot_id >"$state/boot.before"
 tar -C /boot -czf "$backup/boot-before.tar.gz" bananapi
 tar -tzf "$backup/boot-before.tar.gz" >/dev/null
 hash "$backup/boot-before.tar.gz" >"$backup/boot-before.sha256"
 cp -a "$old" "$new.preparing"
 rm -f "$new.preparing/uImage" "$new.preparing/System.map-5.4.260-octava-nft1" "$new.preparing/uEnv.txt"
 install -m0644 "$release/uImage" "$new.preparing/uImage"
 install -m0644 "$release/System.map" "$new.preparing/System.map-5.4.260-octava-nft1"
 sed "s/^service=$old_service\$/service=$new_service/" "$backup/uEnv.before" >"$state/selected.uEnv"
 test "$(sed -n 's/^service=//p' "$state/selected.uEnv")" = "$new_service"
 cp "$state/selected.uEnv" "$new.preparing/uEnv.txt"
 sync;mv "$new.preparing" "$new";sync
 new_ok;cmp "$selector" "$backup/uEnv.before"
 config_snapshot >"$state/config.check";cmp "$state/config.before" "$state/config.check"
 printf staged >"$state/status";sync
 echo STAGED_OLD_BOOT_SELECTION_UNCHANGED
elif test "$action" = activate;then
 test -d "$state";new_ok
 if test "$(cat "$state/status")" = activated;then cmp "$selector" "$state/selected.uEnv";echo ALREADY_ACTIVATED_MANUAL_REBOOT_REQUIRED;exit 0;fi
 test "$(cat "$state/status")" = staged
 original;ready
 config_snapshot >"$state/config.check";cmp "$state/config.before" "$state/config.check"
 cmp "$selector" "$backup/uEnv.before"
 cat /proc/sys/kernel/random/boot_id >"$state/boot.before"
 cp "$state/selected.uEnv" "$selector.sec98432-tmp"
 sync;mv "$selector.sec98432-tmp" "$selector";sync
 cmp "$selector" "$state/selected.uEnv"
 printf activated >"$state/status";sync
 echo ACTIVATED_MANUAL_REBOOT_REQUIRED
elif test "$action" = confirm;then
 test -d "$state";new_ok
 case "$(cat "$state/status")" in staged|activated|confirmed) ;; *) exit 1;;esac
 test "$(cat "$state/boot.before")" != "$(cat /proc/sys/kernel/random/boot_id)"
 test "$(service)" = "$new_service"
 case "$(uname -v)" in '#2 SMP '*) ;; *) echo UNEXPECTED_KERNEL_BUILD >&2;exit 1;;esac
 cmp "$selector" "$state/selected.uEnv";ready
 config_snapshot >"$state/config.check";cmp "$state/config.before" "$state/config.check"
 test -z "$(systemctl --failed --plain --no-legend)"
 printf confirmed >"$state/status";sync
 echo CONFIRMED_KERNEL_IDENTITY_AND_CONFIG_HARDWARE_ACCEPTANCE_STILL_REQUIRED
else
 test -f "$backup/uEnv.before";test -f "$state/selector.before.sha256"
 check_hash "$backup/uEnv.before" "$(cat "$state/selector.before.sha256")"
 if cmp -s "$selector" "$backup/uEnv.before";then printf rolled_back >"$state/status";echo OLD_BOOT_ALREADY_SELECTED;exit 0;fi
 cmp "$selector" "$state/selected.uEnv"
 cp "$backup/uEnv.before" "$selector.sec98432-tmp"
 sync;mv "$selector.sec98432-tmp" "$selector";sync
 cmp "$selector" "$backup/uEnv.before"
 printf rolled_back >"$state/status";sync
 echo EXACT_OLD_KERNEL_SELECTED_MANUAL_REBOOT_REQUIRED
fi
