adacam/scripts/sign-bundle.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

175 lines
6.3 KiB
Bash
Executable file

#!/bin/bash
# sign-bundle.sh — create a signed AdaCam recovery bundle
#
# Run this on Lucy to package and sign a recovery bundle.
# The signed bundle goes on a USB drive for emergency device recovery.
#
# Usage:
# ./scripts/sign-bundle.sh [output-dir]
#
# Output:
# adacam_recovery/adacam-recovery.tar.gz
# adacam_recovery/adacam-recovery.tar.gz.sig
#
# Requires:
# - /boot/config/adacam/adacam-update-private.pem (RSA-4096 private key on Lucy)
# - The adacam repo checked out (source of services/ and configs/)
set -euo pipefail
PRIVATE_KEY="/boot/config/adacam/adacam-update-private.pem"
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
OUTPUT_DIR="${1:-/tmp/adacam-recovery-bundle}"
BUNDLE_DIR="$OUTPUT_DIR/adacam_recovery"
BUNDLE="$BUNDLE_DIR/adacam-recovery.tar.gz"
SIG="$BUNDLE_DIR/adacam-recovery.tar.gz.sig"
STAGING="/tmp/adacam-recovery-staging"
log() { echo "[$(date '+%H:%M:%S')] $*"; }
die() { echo "ERROR: $*" >&2; exit 1; }
[ -f "$PRIVATE_KEY" ] || die "private key not found at $PRIVATE_KEY"
[ -d "$REPO_DIR/services" ] || die "run from inside the adacam repo (services/ not found)"
log "Building AdaCam recovery bundle"
log "Source: $REPO_DIR"
log "Output: $BUNDLE_DIR"
# ── Stage recovery contents ───────────────────────────────────────────────────
rm -rf "$STAGING" && mkdir -p "$STAGING"
# Core services
cp -r "$REPO_DIR/services/capture/" "$STAGING/capture/" 2>/dev/null || true
cp -r "$REPO_DIR/services/updater/" "$STAGING/updater/" 2>/dev/null || true
# adacam-api (if present locally — otherwise pull from Gitea in install.sh)
if [ -d "$REPO_DIR/../adacam-api" ]; then
cp -r "$REPO_DIR/../adacam-api" "$STAGING/adacam-api/"
fi
# Keys
mkdir -p "$STAGING/keys"
cp "$REPO_DIR/keys/adacam_authorized_key.pub" "$STAGING/keys/"
cp "$REPO_DIR/keys/adacam-update-public.pem" "$STAGING/keys/"
# Write install.sh — runs on device after extraction
cat > "$STAGING/install.sh" << 'INSTALL'
#!/bin/bash
# AdaCam recovery install — runs on device after bundle is verified and extracted
# Working directory is the extracted bundle root
set -euo pipefail
BUNDLE_ROOT="$(cd "$(dirname "$0")" && pwd)"
GITEA="http://192.168.0.5:3001"
GITEA_TOKEN="33a9eb57b58c262f4434c12028bc3a30b1ff7021"
log() { echo "[recovery] $*"; }
die() { echo "[recovery] ERROR: $*" >&2; exit 1; }
log "=== AdaCam Recovery Install ==="
# 1. Restore authorized SSH keys
log "restoring SSH keys..."
mkdir -p /root/.ssh && chmod 700 /root/.ssh
cat "$BUNDLE_ROOT/keys/adacam_authorized_key.pub" >> /root/.ssh/authorized_keys
sort -u /root/.ssh/authorized_keys -o /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
# 2. Re-install update verify key
log "restoring update verify key..."
mkdir -p /etc/adacam
cp "$BUNDLE_ROOT/keys/adacam-update-public.pem" /etc/adacam/update-verify.pem
# 3. Re-install updater itself (self-healing)
log "restoring updater..."
cp "$BUNDLE_ROOT/updater/adacam-updater.sh" /usr/local/bin/adacam-updater
chmod +x /usr/local/bin/adacam-updater
cp "$BUNDLE_ROOT/updater/99-adacam-usb.rules" /etc/udev/rules.d/
udevadm control --reload-rules 2>/dev/null || true
# 4. Re-install capture services
if [ -d "$BUNDLE_ROOT/capture" ]; then
log "restoring capture services..."
mkdir -p /opt/adacam/capture
cp -r "$BUNDLE_ROOT/capture/"* /opt/adacam/capture/
if [ -f "$BUNDLE_ROOT/capture/adacam-capture.service" ]; then
cp "$BUNDLE_ROOT/capture/adacam-capture.service" /etc/systemd/system/
cp "$BUNDLE_ROOT/capture/adacam-forwarder.service" /etc/systemd/system/ 2>/dev/null || true
systemctl daemon-reload
systemctl enable adacam-capture adacam-forwarder 2>/dev/null || true
fi
fi
# 5. Pull latest adacam-api from Gitea and reinstall
log "pulling adacam-api from Gitea..."
if curl -sf "$GITEA/Sulkta-Coop/adacam-api/archive/main.tar.gz" \
-H "Authorization: token $GITEA_TOKEN" \
-o /tmp/adacam-api.tar.gz 2>/dev/null; then
mkdir -p /opt/adacam/api
tar -xzf /tmp/adacam-api.tar.gz -C /opt/adacam/api --strip-components=1
if [ -f /opt/adacam/api/install.sh ]; then
bash /opt/adacam/api/install.sh
fi
rm /tmp/adacam-api.tar.gz
log "adacam-api restored from Gitea"
else
log "WARNING: could not reach Gitea — adacam-api not restored (LAN required)"
fi
# 6. Re-apply firewall
if [ -f /data/persist/firewall.sh ]; then
log "reapplying firewall..."
bash /data/persist/firewall.sh
fi
# 7. Restart services
log "restarting services..."
systemctl restart sshd 2>/dev/null || true
systemctl restart adacam-api 2>/dev/null || true
systemctl restart adacam-capture 2>/dev/null || true
log "=== Recovery install complete ==="
INSTALL
chmod +x "$STAGING/install.sh"
# ── Create tarball ────────────────────────────────────────────────────────────
mkdir -p "$BUNDLE_DIR"
log "creating bundle..."
tar -czf "$BUNDLE" -C "$STAGING" .
BUNDLE_SIZE=$(du -sh "$BUNDLE" | cut -f1)
log "bundle: $BUNDLE ($BUNDLE_SIZE)"
# ── Sign it ───────────────────────────────────────────────────────────────────
log "signing with RSA-4096..."
openssl dgst -sha256 -sign "$PRIVATE_KEY" -out "$SIG" "$BUNDLE"
log "signature: $SIG"
# Verify our own signature (sanity check)
PUBLIC_KEY="$(dirname "$PRIVATE_KEY")/adacam-update-public.pem"
if openssl dgst -sha256 -verify "$PUBLIC_KEY" -signature "$SIG" "$BUNDLE" > /dev/null 2>&1; then
log "self-verify: OK"
else
die "self-verify FAILED — bundle may be corrupt"
fi
# ── Cleanup and report ────────────────────────────────────────────────────────
rm -rf "$STAGING"
echo ""
echo "======================================"
echo " Recovery Bundle Ready"
echo "======================================"
echo " Bundle: $BUNDLE"
echo " Signature: $SIG"
echo " Size: $BUNDLE_SIZE"
echo ""
echo " Copy the adacam_recovery/ folder to a USB drive:"
echo " cp -r $BUNDLE_DIR /path/to/usb/adacam_recovery"
echo ""
echo " The USB drive root must contain: adacam_recovery/"
echo "======================================"