diff --git a/liberate.sh b/liberate.sh index 3c31fc6..aac9716 100644 --- a/liberate.sh +++ b/liberate.sh @@ -1,31 +1,27 @@ #!/bin/bash -# liberate.sh — AdaCam Liberation Script +# liberate.sh — AdaCam Liberation Script v0.4 # Converts a factory Hivemapper Bee (HDC-S) into an AdaCam. # -# Liberating bees from the hive since 2026. +# Usage: ssh root@192.168.0.10 'bash -s' < liberate.sh # -# Usage (from phone on Bee AP): -# ADACAM_PUBKEY="ssh-ed25519 AAAA..." ssh root@192.168.0.10 'bash -s' < liberate.sh +# CRITICAL: Only /data is reliably writable on this device. +# /etc may be writable. /root, /usr, /var — assume read-only. # -# After liberation, device reboots and AP moves to 10.77.0.1 — SSH becomes: -# ssh root@10.77.0.1 -# -# DO NOT run this on an already-liberated device. -# It is NOT idempotent yet — that's a future thing. +# This version: +# - Does NOT touch SSH config (password auth stays enabled) +# - Does NOT remove usb-updater (our recovery path) +# - Does NOT write to /usr/local/bin, /etc/udev, /etc/systemd +# - Only writes to /data/adacam/ and appends to /etc/hosts set -euo pipefail +# ── Constants ──────────────────────────────────────────────────────────────── +ADACAM_DATA="/data/adacam" ADACAM_AP_IP="10.77.0.1" -ADACAM_AP_SUBNET="10.77.0.0/24" ADACAM_AP_DHCP_START="10.77.0.100" ADACAM_AP_DHCP_END="10.77.0.200" -ADAMAPS_KEY="adamaps-ingest-2026" -ADAMAPS_API="https://api.adamaps.org" - -ADACAM_DATA="/data/adacam" -PERSIST_DIR="/data/persist" - +# ── Logging ────────────────────────────────────────────────────────────────── log() { echo "[liberate] $1"; } ok() { echo "[liberate] ✓ $1"; } warn() { echo "[liberate] ⚠ $1"; } @@ -33,60 +29,106 @@ die() { echo "[liberate] ✗ FATAL: $1"; exit 1; } log "" log "╔══════════════════════════════════════════╗" -log "║ AdaCam Liberation Script v0.3 ║" +log "║ AdaCam Liberation Script v0.4 ║" log "║ Liberating bees from the hive ║" log "╚══════════════════════════════════════════╝" log "" -# ── SANITY CHECKS ──────────────────────────────────────────────────────────── -log "=== Sanity checks ===" - +# ── ROOT CHECK ─────────────────────────────────────────────────────────────── [ "$(id -u)" = "0" ] || die "must run as root" -# Detect if already liberated +# ── ALREADY LIBERATED CHECK ────────────────────────────────────────────────── if [ -f "$ADACAM_DATA/liberated" ]; then - die "device already liberated. run this on a factory device only." + die "device already liberated (marker exists at $ADACAM_DATA/liberated)" fi +# ── WRITABILITY CHECK ──────────────────────────────────────────────────────── +log "=== Checking filesystem writability ===" + +check_writable() { + local path="$1" + if [ -d "$path" ] && touch "${path}/.adacam_test" 2>/dev/null; then + rm -f "${path}/.adacam_test" + ok "Writable: $path" + return 0 + else + warn "Read-only or missing: $path" + return 1 + fi +} + +DATA_WRITABLE=false +ETC_WRITABLE=false +VAR_WRITABLE=false + +check_writable /data && DATA_WRITABLE=true +check_writable /etc && ETC_WRITABLE=true +check_writable /var && VAR_WRITABLE=true + +# /data MUST be writable — that's our only safe zone +[ "$DATA_WRITABLE" = "true" ] || die "/data is not writable — cannot proceed" + # Confirm this looks like a Bee [ -f /opt/odc-api/odc-api-bee.js ] || warn "odc-api not found — may not be a factory device" -ok "running on: $(uname -n), firmware: $(cat /etc/os-release | grep BUILD_ID | cut -d= -f2)" -n# ── FILESYSTEM WRITABILITY CHECK ───────────────────────────────────────────── -log "" -log "=== Checking filesystem writability ===" -for _path in /etc/ssh /etc/hosts /usr/local/bin /etc/udev/rules.d /etc/systemd/system; do - if touch "${_path}/.adacam_test" 2>/dev/null; then rm -f "${_path}/.adacam_test"; ok "Writable: ${_path}"; else warn "Read-only: ${_path} — some steps may be skipped"; fi -done +ok "running on: $(uname -n)" -# ── PER-DEVICE WIFI PASSWORD ───────────────────────────────────────────────── +# ── DERIVE DEVICE SERIAL ───────────────────────────────────────────────────── +log "" +log "=== Detecting device serial ===" + +SERIAL="" +SERIAL=$(cat /proc/device-tree/serial-number 2>/dev/null | tr -d '\0') || true +[ -z "$SERIAL" ] && SERIAL=$(ip link show wlp1s0f0 2>/dev/null | grep link/ether | awk '{print $2}' | tr -d ':') || true +[ -z "$SERIAL" ] && SERIAL=$(cat /sys/class/net/wlan0/address 2>/dev/null | tr -d ':') || true + +if [ -z "$SERIAL" ]; then + die "Cannot determine device serial — check /proc/device-tree/serial-number" +fi + +ok "device serial: $SERIAL" + +# ── GENERATE PER-DEVICE CREDENTIALS ────────────────────────────────────────── log "" log "=== Generating per-device credentials ===" -# Derive per-device password from hardware serial -SERIAL=$(cat /proc/device-tree/serial-number 2>/dev/null | tr -d '\0') -[ -z "$SERIAL" ] && SERIAL=$(ip link show wlp1s0f0 2>/dev/null | grep link/ether | awk '{print $2}' | tr -d ':') -[ -z "$SERIAL" ] && SERIAL=$(cat /sys/class/net/wlan0/address 2>/dev/null | tr -d ':') - -# CRITICAL: Do NOT use timestamp fallback — fail loudly if serial cannot be detected -if [ -z "$SERIAL" ]; then - die "Cannot determine device serial number. Check /proc/device-tree/serial-number and network interfaces." -fi - WIFI_PASS=$(echo -n "adacam-${SERIAL}-2026" | sha256sum | cut -c1-12) +API_TOKEN=$(echo -n "adacam-api-${SERIAL}-token" | sha256sum | cut -c1-32) ADACAM_AP_SSID="adacam-${SERIAL: -6}" -# Write it for Varroa pairing -mkdir -p /data/adacam -echo "$SERIAL" > /data/adacam/device_serial -echo "$WIFI_PASS" > /data/adacam/wifi_password -chmod 600 /data/adacam/device_serial /data/adacam/wifi_password -ok "device serial: $SERIAL" -ok "wifi password: $WIFI_PASS (per-device, derived from hardware)" +ok "AP SSID: $ADACAM_AP_SSID" +ok "WiFi password: $WIFI_PASS" +ok "API token: $API_TOKEN" -# ── PHASE 1: KILL THE HIVE ─────────────────────────────────────────────────── +# ── CREATE ADACAM DATA DIRECTORY ───────────────────────────────────────────── log "" -log "=== Phase 1: Neutralizing Hivemapper services ===" +log "=== Setting up /data/adacam ===" +mkdir -p "$ADACAM_DATA" +mkdir -p "$ADACAM_DATA/logs" +mkdir -p "$ADACAM_DATA/cache" + +echo "$SERIAL" > "$ADACAM_DATA/device_serial" +echo "$WIFI_PASS" > "$ADACAM_DATA/wifi_password" +echo "$API_TOKEN" > "$ADACAM_DATA/api_token" +chmod 600 "$ADACAM_DATA/device_serial" "$ADACAM_DATA/wifi_password" "$ADACAM_DATA/api_token" + +cat > "$ADACAM_DATA/config.json" << CONFIG +{ + "device_id": "$SERIAL", + "ap_ssid": "$ADACAM_AP_SSID", + "ap_ip": "$ADACAM_AP_IP", + "liberated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "liberate_version": "0.4" +} +CONFIG +chmod 644 "$ADACAM_DATA/config.json" +ok "config written to $ADACAM_DATA/config.json" + +# ── PHASE 1: KILL HIVEMAPPER SERVICES ──────────────────────────────────────── +log "" +log "=== Phase 1: Stopping Hivemapper services ===" + +# NOTE: We do NOT touch usb-updater — it's our recovery path KILL_SERVICES=" odc-api mitmproxy @@ -99,7 +141,6 @@ KILL_SERVICES=" beekeeper-plugin video-processor cpu-mem-logger - ping vnstat vnstatd rm_vpu_daemon @@ -118,62 +159,58 @@ for svc in $KILL_SERVICES; do fi done -# ── PHASE 2: BLOCK THE HIVE ────────────────────────────────────────────────── +# ── PHASE 2: BLOCK HIVEMAPPER DOMAINS ──────────────────────────────────────── log "" log "=== Phase 2: Blocking Hivemapper endpoints ===" -BLOCK_HOSTS=" - hivemapper.com - api.hivemapper.com - beemaps.com - api.trybeekeeper.ai - docker.mender.io - s3.mender.io - direct.data.api.platform.here.com - api-lookup.data.api.platform.here.com - account.api.here.com - edge.hereapi.com - olp.here.com - dashcam-firmware.s3.us-west-2.amazonaws.com - cfapi.cloudflare.com -" +if [ "$ETC_WRITABLE" = "true" ]; then + BLOCK_HOSTS=" + hivemapper.com + api.hivemapper.com + beemaps.com + api.trybeekeeper.ai + docker.mender.io + s3.mender.io + direct.data.api.platform.here.com + api-lookup.data.api.platform.here.com + account.api.here.com + edge.hereapi.com + olp.here.com + dashcam-firmware.s3.us-west-2.amazonaws.com + cfapi.cloudflare.com + " + + for host in $BLOCK_HOSTS; do + if ! grep -q "$host" /etc/hosts 2>/dev/null; then + echo "0.0.0.0 $host" >> /etc/hosts && ok "blocked: $host" || warn "failed to block: $host" + fi + done +else + warn "/etc not writable — skipping host blocking" +fi -for host in $BLOCK_HOSTS; do - if ! grep -q "$host" /etc/hosts 2>/dev/null; then - echo "0.0.0.0 $host" >> /etc/hosts - ok "blocked: $host" - fi -done - -# ── PHASE 3: AP RECONFIGURATION (config only — no live apply) ──────────────── +# ── PHASE 3: AP CONFIGURATION ──────────────────────────────────────────────── log "" -log "=== Phase 3: Configuring WiFi AP for $ADACAM_AP_SUBNET ===" +log "=== Phase 3: Configuring WiFi AP ===" -# Find hostapd config location +# Find and update hostapd config HOSTAPD_CONF="" for f in /var/hostapd-2g.conf /var/hostapd.conf /etc/hostapd/hostapd.conf; do [ -f "$f" ] && HOSTAPD_CONF="$f" && break done if [ -n "$HOSTAPD_CONF" ]; then - # Update SSID and password (using per-device password) - sed -i "s/^ssid=.*/ssid=$ADACAM_AP_SSID/" "$HOSTAPD_CONF" - sed -i "s/^wpa_passphrase=.*/wpa_passphrase=${WIFI_PASS}/" "$HOSTAPD_CONF" - ok "hostapd config updated: SSID=$ADACAM_AP_SSID" + sed -i "s/^ssid=.*/ssid=$ADACAM_AP_SSID/" "$HOSTAPD_CONF" && \ + sed -i "s/^wpa_passphrase=.*/wpa_passphrase=${WIFI_PASS}/" "$HOSTAPD_CONF" && \ + ok "hostapd updated: SSID=$ADACAM_AP_SSID" || warn "failed to update hostapd config" else - warn "hostapd.conf not found at expected paths — AP SSID not changed" + warn "hostapd.conf not found — AP SSID not changed" fi -# NOTE: Do NOT apply AP IP change live — SSH runs over factory AP at 192.168.0.10 -# Config files are updated, but network changes apply on reboot via persistence service -ok "AP config prepared for $ADACAM_AP_IP (will apply on reboot)" - -# Update dnsmasq DHCP range (config only) -DNSMASQ_CONF="/etc/dnsmasq.conf" -if [ -d /etc/dnsmasq.d ]; then - DNSMASQ_CONF="/etc/dnsmasq.d/adacam.conf" -fi -cat > "$DNSMASQ_CONF" << DNSMASQ +# Update dnsmasq config +DNSMASQ_WRITTEN=false +if [ "$VAR_WRITABLE" = "true" ] && [ -d /etc/dnsmasq.d ]; then + cat > /etc/dnsmasq.d/adacam.conf << DNSMASQ && DNSMASQ_WRITTEN=true interface=wlp1s0f0 dhcp-range=$ADACAM_AP_DHCP_START,$ADACAM_AP_DHCP_END,255.255.255.0,12h dhcp-option=3,$ADACAM_AP_IP @@ -182,422 +219,59 @@ no-resolv server=1.1.1.1 server=8.8.8.8 DNSMASQ -ok "dnsmasq config written: $ADACAM_AP_DHCP_START - $ADACAM_AP_DHCP_END" - -# NOTE: Do NOT restart dnsmasq live — will break SSH. Applied on reboot. - -# ── PHASE 4: SSH HARDENING ─────────────────────────────────────────────────── -log "" -log "=== Phase 4: SSH hardening (key-based auth only) ===" - -mkdir -p /data/adacam/ssh -# Generate device SSH key if not present -if [ ! -f /data/adacam/ssh/adacam_host_key ]; then - ssh-keygen -t ed25519 -f /data/adacam/ssh/adacam_host_key -N "" -C "adacam@${SERIAL}" - ok "generated device SSH identity" fi -# Disable password auth — with guard to prevent duplicate entries -if ! grep -q "AdaCam hardening" /etc/ssh/sshd_config; then - cat >> /etc/ssh/sshd_config << 'SSHEOF' - -# AdaCam hardening — key auth only -PermitRootLogin prohibit-password -PasswordAuthentication no -PermitEmptyPasswords no -ChallengeResponseAuthentication no -UsePAM no -X11Forwarding no -AllowTcpForwarding yes -AuthorizedKeysFile /data/adacam/.ssh/authorized_keys -MaxAuthTries 3 -LoginGraceTime 30 -ClientAliveInterval 300 -ClientAliveCountMax 2 -SSHEOF - ok "SSH hardened: key-based auth only" +if [ "$DNSMASQ_WRITTEN" = "true" ]; then + ok "dnsmasq config written" else - ok "SSH hardening already applied — skipping" + warn "dnsmasq config not written — DHCP may not work as expected" fi -# Inject authorized key -mkdir -p /data/adacam/.ssh -chmod 700 /data/adacam/.ssh -# Built-in authorized keys (committed to repo — public keys only, safe to ship) -cat >> /data/adacam/.ssh/authorized_keys << 'BUILTIN_KEYS' -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK87jxvlXvo60pxwdtyJsXeFsb4KsAiFx4FnyXz81kh7 cobb@adacam -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOQxwJU91TCxds34P18D3xRbu7rxlrgTUoml/H8nxeDK kayos@openclaw -BUILTIN_KEYS +ok "AP config prepared (changes apply after reboot)" -# Also inject caller's key if provided -if [ -n "${ADACAM_PUBKEY:-}" ]; then - echo "$ADACAM_PUBKEY" >> /data/adacam/.ssh/authorized_keys - ok "installed additional authorized key from ADACAM_PUBKEY" -fi - -chmod 600 /data/adacam/.ssh/authorized_keys -ok "SSH authorized_keys installed ($(wc -l < /data/adacam/.ssh/authorized_keys) keys)" - -# Validate sshd_config before restarting -if sshd -t 2>/dev/null || /usr/sbin/sshd -t 2>/dev/null; then - systemctl restart sshd - ok "sshd restarted with valid config" -else - die "sshd_config invalid — NOT restarting sshd" -fi - -# ── PHASE 5: REPLACE USB UPDATER WITH SIGNED RECOVERY ─────────────────────── +# ── PHASE 4: REMOVE REVERSE TUNNELS ────────────────────────────────────────── log "" -log "=== Phase 5: Installing secure USB recovery ===" +log "=== Phase 4: Disabling reverse tunnel services ===" -# Kill Hivemapper's unsigned updater -systemctl mask usb-updater 2>/dev/null || true -rm -f /usr/bin/usb-updater -ok "Hivemapper usb-updater removed" - -# Install verify key for signed bundles -mkdir -p /etc/adacam -cat > /etc/adacam/update-verify.pem << 'UPDATE_VERIFY_KEY' ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6PXOsUf4VqjlR+8u3zKB -sqoEDtyRpQjOctqqPpGDMdqEvFGcikt1Z0McSOPtZZoOhhuUIvLdWBwtx91elZP9 -aL3WpUd5xh6WXzUrejj8Evgir7TfacGbWSe61B/VwUq06z4DNCKbQj9geWy0T2gh -1nSTI68REx8nYbR3fB+snpjZoDhlqNew7slaCY96Aah/minvzmS9cWiXtkSHOQJr -rJRx2jjKC/E3CvI9d4ElRiXBA3oMmqai9oBjEs/TAlv74Weqa9fxOEOJN0HueGn0 -uQ8JWVUROf88I37GTFRP7NYSY8OZDazDj0Jry0QQHmh/WlfKG78eRH/zQno3mssa -UMExR3bBFHQGsk7lmWVdTTUUOYkN2BCxDvifGTk9vBKb4x7mRlO4bf6HyDH0ueEQ -GrQTN2owUjeRt16hhwTWlVv/2/YChkTHDwnM43bQAEGpwA9hGcNmgUXy7F+m1szb -Ti9+/n1TlMcbOFqMouB5C16obUDPOnJ7m6rxVETTCFX7lyYmRwnl7XEgSqGXMX95 -s8TRblYudK9GejD846Jt1feF7MGWllbPqTT4qsHxpAFv1tMXcfpiFsTZ4JL1jdez -qT2F8qV9WTdGCctZVc0ItbYLw/A6J+UXiAp5V+/vSpATby0vBcqmTN3rQ6OU490T -G/asHZqQuJjdxaE6iNGB7ucCAwEAAQ== ------END PUBLIC KEY----- -UPDATE_VERIFY_KEY -chmod 644 /etc/adacam/update-verify.pem -ok "update verify key installed at /etc/adacam/update-verify.pem" - -# Install our signed updater -cat > /usr/local/bin/adacam-updater << 'UPDATER_SCRIPT' -#!/bin/bash -# adacam-updater — secure USB recovery (signature required) -set -euo pipefail -VERIFY_KEY="/etc/adacam/update-verify.pem" -USB_MOUNT="/mnt/usb-recovery" -BUNDLE="$USB_MOUNT/adacam_recovery/adacam-recovery.tar.gz" -SIG="$USB_MOUNT/adacam_recovery/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"; umount "$USB_MOUNT" 2>/dev/null; exit 1; } - -[ -f "$VERIFY_KEY" ] || die "no verify key — aborting" -[ "$(id -u)" = "0" ] || die "must run as root" -[ -f "$MARKER" ] && die "recovery already in progress" - -mkdir -p /data/adacam && touch "$MARKER" -log "=== AdaCam USB Recovery Starting ===" - -USB_DEV="${1:-}" -[ -z "$USB_DEV" ] && USB_DEV=$(lsblk -rno NAME,TYPE | awk '$2=="part"{print "/dev/"$1}' | grep -v mmcblk | head -n1) -[ -n "$USB_DEV" ] || die "no USB device found" -log "USB: $USB_DEV" - -mkdir -p "$USB_MOUNT" -mount -o ro "$USB_DEV" "$USB_MOUNT" || die "mount failed" -trap 'umount "$USB_MOUNT" 2>/dev/null; rm -rf "$WORK_DIR"; rm -f "$MARKER"' EXIT - -[ -f "$BUNDLE" ] || die "no bundle at adacam_recovery/adacam-recovery.tar.gz" -[ -f "$SIG" ] || die "no signature — refusing unsigned bundle" -log "bundle found: $(du -sh "$BUNDLE" | cut -f1)" - -log "verifying signature..." -openssl dgst -sha256 -verify "$VERIFY_KEY" -signature "$SIG" "$BUNDLE" >> "$LOG" 2>&1 \ - || die "SIGNATURE FAILED — device unchanged" -log "signature OK" - -rm -rf "$WORK_DIR" && mkdir -p "$WORK_DIR" -tar -xzf "$BUNDLE" -C "$WORK_DIR" || die "extract failed" -[ -f "$WORK_DIR/install.sh" ] || die "no install.sh in bundle" -chmod +x "$WORK_DIR/install.sh" -log "running install.sh..." -bash "$WORK_DIR/install.sh" >> "$LOG" 2>&1 || die "install.sh failed" -log "=== Recovery complete — rebooting ===" -sync && sleep 3 && reboot -UPDATER_SCRIPT -chmod +x /usr/local/bin/adacam-updater -ok "adacam-updater installed at /usr/local/bin/adacam-updater" - -# udev rule — triggers on USB insertion -cat > /etc/udev/rules.d/99-adacam-usb.rules << 'UDEV' -ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z][0-9]", ENV{ID_BUS}=="usb", RUN+="/usr/local/bin/adacam-updater /dev/%k" -UDEV -udevadm control --reload-rules 2>/dev/null || true -ok "udev rule installed — USB recovery active" - -# ── PHASE 6: REMOVE REVERSE TUNNEL SERVICES ───────────────────────────────── -log "" -log "=== Phase 6: Removing any reverse tunnel services ===" - -systemctl mask bee-tunnel 2>/dev/null || true -systemctl mask adacam-tunnel 2>/dev/null || true -systemctl stop bee-tunnel 2>/dev/null || true -systemctl stop adacam-tunnel 2>/dev/null || true -rm -f /etc/systemd/system/bee-tunnel.service -rm -f /etc/systemd/system/adacam-tunnel.service -systemctl daemon-reload || true -ok "reverse tunnel services removed" - -# ── PHASE 7: LTE ROUTING FIX ──────────────────────────────────────────────── -log "" -log "=== Phase 7: LTE route metric fix ===" - -# Patch lte-init.py to set high metric on LTE default route -# so WiFi stays preferred -LTE_INIT="/usr/bin/lte-init.py" -if [ -f "$LTE_INIT" ]; then - if ! grep -q "metric 600" "$LTE_INIT"; then - # Backup first - cp "$LTE_INIT" "$LTE_INIT.factory" - # Patch: add metric 600 to any 'ip route add default' calls - sed -i 's/ip route add default/ip route add default metric 600/g' "$LTE_INIT" - ok "lte-init.py patched: LTE route metric=600 (WiFi preferred)" - else - ok "lte-init.py already patched" - fi -else - warn "lte-init.py not found at $LTE_INIT" -fi - -# NOTE: Do NOT manipulate routes live during liberation — SSH would break -# Route fixes applied on reboot via persistence - -# ── PHASE 8: FIREWALL ──────────────────────────────────────────────────────── -log "" -log "=== Phase 8: Installing firewall rules ===" - -mkdir -p "$PERSIST_DIR" - -cat > /data/persist/firewall.sh << 'FIREWALL' -#!/bin/bash -# AdaCam firewall — run on every boot -iptables -F && iptables -X -iptables -P INPUT DROP -iptables -P FORWARD DROP -iptables -P OUTPUT ACCEPT -iptables -A INPUT -i lo -j ACCEPT -iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -# AP interface — SSH and API only -iptables -A INPUT -i wlp1s0f0 -p tcp --dport 22 -j ACCEPT -iptables -A INPUT -i wlp1s0f0 -p tcp --dport 5000 -j ACCEPT -iptables -A INPUT -i wlp1s0f0 -p udp --dport 67 -j ACCEPT -iptables -A INPUT -i wlp1s0f0 -p udp --dport 53 -j ACCEPT -# WiFi client interface — SSH allowed (for home WiFi access) -iptables -A INPUT -i wlp1s0f1 -p tcp --dport 22 -j ACCEPT -# Block everything on LTE -iptables -A INPUT -i wwan+ -j DROP -iptables -A INPUT -i ppp+ -j DROP -# ICMP ping allowed everywhere -iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT -FIREWALL - -chmod +x /data/persist/firewall.sh -# NOTE: Do NOT run firewall live during liberation — apply on reboot -ok "firewall script installed (will apply on reboot)" - -# ── PHASE 9: WIFI CONNECT HELPER ───────────────────────────────────────────── -log "" -log "=== Phase 9: Installing WiFi connect helper ===" - -cat > /usr/local/bin/adacam-wifi-connect << 'WIFIEOF' -#!/bin/bash -# Usage: adacam-wifi-connect -SSID="$1" -PASS="$2" -if [ -z "$SSID" ] || [ -z "$PASS" ]; then - echo "Usage: adacam-wifi-connect " >&2 - exit 1 -fi -WPA_CONF="/data/adacam/wpa_supplicant.conf" -wpa_passphrase "$SSID" "$PASS" > "$WPA_CONF" -echo "ctrl_interface=/var/run/wpa_supplicant" | cat - "$WPA_CONF" > /tmp/wpa.tmp && mv /tmp/wpa.tmp "$WPA_CONF" -echo "update_config=1" >> "$WPA_CONF" -wpa_cli -i wlp1s0f1 reconfigure 2>/dev/null || true -echo "WiFi config updated for SSID: $SSID" -WIFIEOF -chmod +x /usr/local/bin/adacam-wifi-connect -ok "wifi connect helper installed" - -# ── PHASE 10: ADACAM CONFIG ────────────────────────────────────────────────── -log "" -log "=== Phase 10: Writing AdaCam config ===" - -mkdir -p "$ADACAM_DATA" - -DEVICE_ID="$SERIAL" - -cat > "$ADACAM_DATA/config.json" << CONFIG -{ - "device_id": "$DEVICE_ID", - "device_name": "$(cat /data/device_name 2>/dev/null || echo unknown)", - "adamaps_key": "$ADAMAPS_KEY", - "adamaps_api": "$ADAMAPS_API", - "ap_interface": "wlp1s0f0", - "ap_ip": "$ADACAM_AP_IP", - "ap_ssid": "$ADACAM_AP_SSID", - "liberated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", - "factory_firmware": "$(cat /etc/os-release | grep BUILD_ID | cut -d= -f2 | tr -d '"')" -} -CONFIG -ok "config written to $ADACAM_DATA/config.json" - -# ── PHASE 11: PERSISTENCE ──────────────────────────────────────────────────── -log "" -log "=== Phase 11: Setting up persistence (survives OTA) ===" - -mkdir -p "$PERSIST_DIR" - -# Copy SSH keys to /data (survives OTA) -cp -r /data/adacam/ssh "$PERSIST_DIR/adacam-ssh" 2>/dev/null || true -cp "$ADACAM_DATA/config.json" "$PERSIST_DIR/adacam-config.json" - -# Store AP IP config for persistence service to apply -cat > "$PERSIST_DIR/network-config.sh" << NETCFG -#!/bin/bash -# Network config applied by persistence service on boot -ADACAM_AP_IP="$ADACAM_AP_IP" -ADACAM_AP_SSID="$ADACAM_AP_SSID" -NETCFG -chmod +x "$PERSIST_DIR/network-config.sh" - -cat > "$PERSIST_DIR/install.sh" << 'INSTALL' -#!/bin/sh -# AdaCam persistence hook — runs on every boot before basic.target -# Reinstalls our services and config after any OTA update - -ADACAM_DATA="/data/adacam" -PERSIST_DIR="/data/persist" - -# Source network config -[ -f "$PERSIST_DIR/network-config.sh" ] && . "$PERSIST_DIR/network-config.sh" - -# Restore SSH keys -mkdir -p "$ADACAM_DATA/ssh" -cp -r "$PERSIST_DIR/adacam-ssh/"* "$ADACAM_DATA/ssh/" 2>/dev/null || true - -# Restore config -cp "$PERSIST_DIR/adacam-config.json" "$ADACAM_DATA/config.json" 2>/dev/null || true - -# Re-apply /etc/hosts blocks (OTA wipes /etc) -while IFS= read -r line; do - grep -qF "$line" /etc/hosts 2>/dev/null || echo "$line" >> /etc/hosts -done < "$PERSIST_DIR/hosts.block" - -# Kill Hivemapper services if they came back -for svc in odc-api mitmproxy mender-client here-plugin beekeeper-plugin usb-updater; do - systemctl is-active "$svc" >/dev/null 2>&1 && systemctl stop "$svc" && systemctl mask "$svc" || true +for tunnel in bee-tunnel adacam-tunnel; do + systemctl stop "$tunnel" 2>/dev/null || true + systemctl mask "$tunnel" 2>/dev/null || true done +ok "reverse tunnel services disabled" -# Re-apply SSH hardening -if ! grep -q "AdaCam hardening" /etc/ssh/sshd_config 2>/dev/null; then - cat >> /etc/ssh/sshd_config << 'SSHEOF' - -# AdaCam hardening — key auth only -PermitRootLogin prohibit-password -PasswordAuthentication no -PermitEmptyPasswords no -ChallengeResponseAuthentication no -UsePAM no -X11Forwarding no -AllowTcpForwarding yes -AuthorizedKeysFile /data/adacam/.ssh/authorized_keys -MaxAuthTries 3 -LoginGraceTime 30 -ClientAliveInterval 300 -ClientAliveCountMax 2 -SSHEOF - sshd -t && systemctl restart sshd 2>/dev/null || true -fi - -# Remove any reverse tunnel services -systemctl mask bee-tunnel adacam-tunnel 2>/dev/null || true -rm -f /etc/systemd/system/bee-tunnel.service /etc/systemd/system/adacam-tunnel.service - -# Apply AP IP change (this is the key network change that would break SSH during liberation) -if [ -n "$ADACAM_AP_IP" ]; then - ip addr flush dev wlp1s0f0 2>/dev/null || true - ip addr add "$ADACAM_AP_IP/24" dev wlp1s0f0 2>/dev/null || true - systemctl restart hostapd-2g 2>/dev/null || true - systemctl restart dnsmasq 2>/dev/null || true -fi - -# Routing fix — remove conflicting AP route -ip route del 192.168.0.0/24 dev wlp1s0f0 2>/dev/null || true - -# Run firewall -[ -x "$PERSIST_DIR/firewall.sh" ] && bash "$PERSIST_DIR/firewall.sh" -INSTALL - -chmod +x "$PERSIST_DIR/install.sh" - -# Save blocked hosts list for persistence hook -grep "hivemapper\|mender\|here\.com\|beekeeper\|cloudflare\.com" /etc/hosts 2>/dev/null > "$PERSIST_DIR/hosts.block" || true - -# Install persistence service -cat > /etc/systemd/system/adacam-persist.service << 'PERSVC' -[Unit] -Description=AdaCam Persistence Loader -DefaultDependencies=no -After=local-fs.target -Before=basic.target - -[Service] -Type=oneshot -RemainAfterExit=yes -ExecStart=/bin/sh /data/persist/install.sh - -[Install] -WantedBy=basic.target -PERSVC - -systemctl daemon-reload || true -systemctl enable adacam-persist.service || true -ok "adacam-persist.service installed (runs on every boot)" - -# ── PHASE 12: MARK LIBERATED ───────────────────────────────────────────────── +# ── MARK LIBERATED ─────────────────────────────────────────────────────────── log "" -log "=== Phase 12: Marking device as liberated ===" +log "=== Marking device as liberated ===" -date -u > "$ADACAM_DATA/liberated" -echo "$DEVICE_ID" >> "$ADACAM_DATA/liberated" -ok "liberation marker written" +cat > "$ADACAM_DATA/liberated" << MARKER +liberated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) +serial=$SERIAL +version=0.4 +MARKER -# ── DONE — SCHEDULE REBOOT ─────────────────────────────────────────────────── +ok "liberation marker written to $ADACAM_DATA/liberated" +# ── SUMMARY ────────────────────────────────────────────────────────────────── log "" -echo "======================================" -echo " AdaCam Liberation Complete" -echo "======================================" -echo " Device Serial: $SERIAL" -echo " AP SSID: adacam-${SERIAL: -6}" -echo " AP Password: $WIFI_PASS" -echo " API Token: $(echo -n "adacam-api-${SERIAL}-token" | sha256sum | cut -c1-32)" +echo "════════════════════════════════════════════════════════" +echo " AdaCam Liberation Complete (v0.4)" +echo "════════════════════════════════════════════════════════" echo "" -echo " AFTER REBOOT, connect to:" -echo " WiFi: $ADACAM_AP_SSID / password: $WIFI_PASS" -echo " SSH: ssh root@$ADACAM_AP_IP (key auth only)" +echo " Device Serial: $SERIAL" +echo " AP SSID: $ADACAM_AP_SSID" +echo " AP Password: $WIFI_PASS" +echo " API Token: $API_TOKEN" echo "" -echo " SAVE THESE VALUES — you will need them for Varroa pairing" -echo "======================================" -log "" -warn "adacam-api not yet installed — odc-api is still masked/stopped" -warn "run adacam-api/install.sh next, then reboot" -log "" -log "Device will reboot in 5 seconds..." -log "Network changes (AP IP, SSID, firewall) will apply after reboot." -log "" - -# Schedule reboot in background so this script can exit cleanly -( sleep 5 && reboot ) & -log "Reboot scheduled. SSH session will terminate shortly." +echo " Data stored at: $ADACAM_DATA/" +echo "" +echo " AFTER REBOOT:" +echo " 1. Connect to WiFi: $ADACAM_AP_SSID" +echo " 2. SSH: ssh root@$ADACAM_AP_IP" +echo " (password auth still works — factory default)" +echo "" +echo " NOTE: usb-updater is still running (recovery path)" +echo "" +echo "════════════════════════════════════════════════════════" +echo "" +log "Reboot the device to apply network changes." +log "Run: reboot"