#!/usr/bin/env bash # Pre-configure an Armbian SD card for fully autonomous first-boot setup. # # What this does: # 1. Writes WiFi profile (correct NM keyfile with UUID) # 2. Sets root password # 3. Generates per-device SSH keypair, stores private key in Infisical # 4. Copies BigBrother source onto the card # 5. Installs a systemd firstboot service that runs setup.sh on first boot # 6. Disables the Armbian first-login wizard # # After this: unmount, insert, power on. Device connects to WiFi and # self-installs BigBrother with no further operator interaction required. # # Usage: sudo bash scripts/preconfig_sd.sh SSID PASSPHRASE DEVICE_ID [MOUNT_POINT] set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' info() { echo -e "${GREEN}[+]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } error() { echo -e "${RED}[-]${NC} $*"; exit 1; } step() { echo -e "${CYAN}[*]${NC} $*"; } [[ $EUID -eq 0 ]] || error "Must run as root (sudo)" SSID="${1:-}" PASSPHRASE="${2:-}" DEVICE_ID="${3:-}" MOUNT="${4:-/mnt/opi}" [[ -z "$SSID" || -z "$PASSPHRASE" || -z "$DEVICE_ID" ]] && { echo "Usage: sudo $0 SSID PASSPHRASE DEVICE_ID [MOUNT_POINT]" echo " DEVICE_ID short label for this unit (e.g. bb-01, drop-lab)" exit 1 } [[ -d "$MOUNT/etc" ]] || error "Mount point not valid: $MOUNT — is the SD card mounted?" SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" CREDS="${HOME}/.local/bin/creds" # ── 1. WiFi ─────────────────────────────────────────────────────────────────── step "Writing WiFi profile..." NM_DIR="${MOUNT}/etc/NetworkManager/system-connections" NM_FILE="${NM_DIR}/wifi-bb.nmconnection" mkdir -p "$NM_DIR" # UUID is required — NM silently ignores keyfiles without it WIFI_UUID=$(cat /proc/sys/kernel/random/uuid) cat > "$NM_FILE" << EOF [connection] id=wifi-bb uuid=${WIFI_UUID} type=wifi autoconnect=true autoconnect-priority=10 [wifi] mode=infrastructure ssid=${SSID} [wifi-security] auth-alg=open key-mgmt=wpa-psk psk=${PASSPHRASE} [ipv4] method=auto [ipv6] addr-gen-mode=stable-privacy method=auto EOF chmod 600 "$NM_FILE" chown root:root "$NM_FILE" info "WiFi profile written (SSID: ${SSID}, UUID: ${WIFI_UUID})" # ── 2. Root password ────────────────────────────────────────────────────────── step "Setting root password..." read -s -r -p "New root password: " ROOT_PASS; echo read -s -r -p "Confirm: " ROOT_PASS2; echo [[ "$ROOT_PASS" == "$ROOT_PASS2" ]] || error "Passwords do not match" HASH=$(openssl passwd -6 "$ROOT_PASS") sed -i "s|^root:[^:]*:|root:${HASH}:|" "${MOUNT}/etc/shadow" info "Root password set" unset ROOT_PASS ROOT_PASS2 # ── 3. Per-device SSH keypair ───────────────────────────────────────────────── step "Generating per-device SSH keypair..." TMP_KEY=$(mktemp) ssh-keygen -t ed25519 -N "" -C "" -f "$TMP_KEY" -q PRIVKEY=$(cat "$TMP_KEY") PUBKEY=$(cat "${TMP_KEY}.pub") rm -f "$TMP_KEY" "${TMP_KEY}.pub" SSH_DIR="${MOUNT}/root/.ssh" mkdir -p "$SSH_DIR" echo "$PUBKEY" > "${SSH_DIR}/authorized_keys" chmod 700 "$SSH_DIR" chmod 600 "${SSH_DIR}/authorized_keys" INFISICAL_KEY="SSH_PRIVKEY_$(echo "$DEVICE_ID" | tr '[:lower:]-' '[:upper:]_')" if [[ -x "$CREDS" ]] && "$CREDS" set "$INFISICAL_KEY" "$PRIVKEY" bigbrother 2>/dev/null; then info "Private key stored in Infisical: bigbrother/${INFISICAL_KEY}" else warn "Could not store in Infisical. SAVE THIS PRIVATE KEY NOW:" echo "---" echo "$PRIVKEY" echo "---" fi unset PRIVKEY info "Public key written to device (no operator keys present)" # ── 4. Copy BigBrother source onto card ─────────────────────────────────────── step "Copying BigBrother source to SD card..." SRC_DEST="${MOUNT}/root/bb-src" rm -rf "$SRC_DEST" rsync -a \ --exclude='.git' \ --exclude='.venv' \ --exclude='storage/' \ --exclude='__pycache__' \ --exclude='*.pyc' \ --exclude='*.db' \ "${SCRIPT_DIR}/" "$SRC_DEST/" info "Source copied to /root/bb-src/" # ── 5. First-boot systemd service ───────────────────────────────────────────── step "Installing first-boot service..." # Sentinel file — service checks for this before running touch "${MOUNT}/root/.bb-firstboot-needed" # The service wrapper — logs to /root/bb-firstboot.log cat > "${MOUNT}/root/bb-firstboot-run.sh" << 'RUNEOF' #!/usr/bin/env bash # Runs once on first boot, then disables itself set -euo pipefail LOG=/root/bb-firstboot.log exec >> "$LOG" 2>&1 echo "=== BigBrother first-boot setup: $(date) ===" # Wait for actual internet connectivity (apt needs it) echo "[*] Waiting for internet..." for i in $(seq 1 30); do if curl -sf --max-time 5 http://deb.debian.org/debian > /dev/null 2>&1; then echo "[+] Internet OK" break fi if [[ $i -eq 30 ]]; then echo "[-] No internet after 90s — aborting" exit 1 fi sleep 3 done cd /root/bb-src bash setup.sh --enable-service echo "[+] Setup complete: $(date)" # Disable ourselves rm -f /root/.bb-firstboot-needed systemctl disable bb-firstboot.service RUNEOF chmod 755 "${MOUNT}/root/bb-firstboot-run.sh" cat > "${MOUNT}/etc/systemd/system/bb-firstboot.service" << 'SVCEOF' [Unit] Description=BigBrother first-boot installation After=network-online.target Wants=network-online.target ConditionPathExists=/root/.bb-firstboot-needed [Service] Type=oneshot ExecStart=/root/bb-firstboot-run.sh RemainAfterExit=yes TimeoutStartSec=600 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target SVCEOF # Enable the service via symlink (same as systemctl enable without running systemd) WANTS_DIR="${MOUNT}/etc/systemd/system/multi-user.target.wants" mkdir -p "$WANTS_DIR" ln -sf /etc/systemd/system/bb-firstboot.service "${WANTS_DIR}/bb-firstboot.service" info "First-boot service installed and enabled" # ── 6. Hostname + wizard bypass ─────────────────────────────────────────────── echo "orangepi" > "${MOUNT}/etc/hostname" rm -f "${MOUNT}/root/.not_logged_in_yet" info "Hostname set, first-login wizard disabled" echo "" info "SD card ready. Unmount and insert." echo "" echo " On boot the device will:" echo " 1. Connect to ${SSID}" echo " 2. Run setup.sh automatically (~5-10 min)" echo " 3. Start bigbrother-core.service" echo "" echo " To connect after setup:" if [[ -x "$CREDS" ]]; then echo " ${CREDS} get ${INFISICAL_KEY} bigbrother > /tmp/${DEVICE_ID}.key" echo " chmod 600 /tmp/${DEVICE_ID}.key" echo " ssh -i /tmp/${DEVICE_ID}.key root@" fi echo "" echo " Monitor first-boot: ssh ... 'tail -f /root/bb-firstboot.log'"