adacam/services/updater/adacam-updater.sh
Kayos 85b3ee39dd feat: signed USB recovery system
- keys/adacam-update-public.pem: RSA-4096 public key (private on Lucy)
- services/updater/adacam-updater.sh: standalone updater (also inlined in liberate.sh)
- services/updater/99-adacam-usb.rules: udev rule for auto-trigger on USB insert
- scripts/sign-bundle.sh: create + sign recovery bundles on Lucy
- keys/README.md: updated with signing key docs and bundle creation instructions

Private key at: /boot/config/adacam/adacam-update-private.pem (Lucy, boot-persistent)
2026-03-14 15:07:32 -07:00

98 lines
3.7 KiB
Bash
Executable file

#!/bin/bash
# adacam-updater — secure USB recovery for AdaCam
#
# Triggered by udev when a USB drive is inserted.
# Looks for a signed recovery bundle on the drive.
# Verifies RSA-4096 signature before touching anything.
# No signature = no action. No exceptions.
#
# Bundle format on USB drive:
# /adacam_recovery/adacam-recovery.tar.gz
# /adacam_recovery/adacam-recovery.tar.gz.sig
set -euo pipefail
VERIFY_KEY="/etc/adacam/update-verify.pem"
USB_MOUNT="/mnt/usb-recovery"
BUNDLE_DIR="adacam_recovery"
BUNDLE_NAME="adacam-recovery.tar.gz"
SIG_NAME="adacam-recovery.tar.gz.sig"
WORK_DIR="/tmp/adacam-recovery-work"
LOG="/data/adacam/recovery.log"
MARKER="/data/adacam/recovery_in_progress"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
die() { log "ERROR: $*"; rm -f "$MARKER"; exit 1; }
# ── Sanity checks ─────────────────────────────────────────────────────────────
[ -f "$VERIFY_KEY" ] || die "no verify key at $VERIFY_KEY — cannot proceed"
[ "$(id -u)" = "0" ] || die "must run as root"
# Only one recovery at a time
[ -f "$MARKER" ] && die "recovery already in progress (marker exists)"
mkdir -p /data/adacam
touch "$MARKER"
log "=== AdaCam USB Recovery Starting ==="
# ── Mount USB ─────────────────────────────────────────────────────────────────
USB_DEV="${1:-}"
if [ -z "$USB_DEV" ]; then
# Auto-detect first USB mass storage device
USB_DEV=$(lsblk -rno NAME,TYPE | awk '$2=="part"{print "/dev/"$1}' | grep -v mmcblk | head -n1)
fi
[ -n "$USB_DEV" ] || die "no USB device found"
log "USB device: $USB_DEV"
mkdir -p "$USB_MOUNT"
mount -o ro "$USB_DEV" "$USB_MOUNT" || die "failed to mount $USB_DEV"
log "mounted $USB_DEV at $USB_MOUNT"
cleanup() {
umount "$USB_MOUNT" 2>/dev/null || true
rm -rf "$WORK_DIR"
rm -f "$MARKER"
}
trap cleanup EXIT
# ── Find bundle ───────────────────────────────────────────────────────────────
BUNDLE="$USB_MOUNT/$BUNDLE_DIR/$BUNDLE_NAME"
SIG="$USB_MOUNT/$BUNDLE_DIR/$SIG_NAME"
[ -f "$BUNDLE" ] || die "no bundle found at $BUNDLE_DIR/$BUNDLE_NAME — nothing to do"
[ -f "$SIG" ] || die "no signature found at $BUNDLE_DIR/$SIG_NAME — refusing unsigned bundle"
log "found bundle: $(du -sh "$BUNDLE" | cut -f1)"
# ── Verify signature ──────────────────────────────────────────────────────────
log "verifying signature..."
if ! openssl dgst -sha256 -verify "$VERIFY_KEY" -signature "$SIG" "$BUNDLE" >> "$LOG" 2>&1; then
die "SIGNATURE VERIFICATION FAILED — bundle rejected, device unchanged"
fi
log "signature OK — bundle is authentic"
# ── Extract and run ───────────────────────────────────────────────────────────
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR"
log "extracting bundle..."
tar -xzf "$BUNDLE" -C "$WORK_DIR" || die "failed to extract bundle"
INSTALL_SCRIPT="$WORK_DIR/install.sh"
[ -f "$INSTALL_SCRIPT" ] || die "no install.sh in bundle — malformed recovery package"
[ -x "$INSTALL_SCRIPT" ] || chmod +x "$INSTALL_SCRIPT"
log "running install.sh..."
bash "$INSTALL_SCRIPT" >> "$LOG" 2>&1 || die "install.sh failed — check $LOG"
log "=== Recovery complete — rebooting in 5s ==="
sync
sleep 5
reboot