- 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)
175 lines
6.3 KiB
Bash
Executable file
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 "======================================"
|