diff --git a/scripts/operator_setup.sh b/scripts/operator_setup.sh new file mode 100755 index 0000000..8047d06 --- /dev/null +++ b/scripts/operator_setup.sh @@ -0,0 +1,578 @@ +#!/usr/bin/env bash +# BigBrother Operator Setup Wizard +# +# Single interactive script for prepping Orange Pi Zero 3 (or compatible Debian) SD cards. +# Operator runs this on their machine with an SD card inserted. +# +# What this does: +# 1. Detects block devices and has operator pick SD card target +# 2. Downloads/caches Armbian minimal image for Orange Pi Zero 3 +# 3. Flashes image to card with verification +# 4. Mounts card and collects configuration (WiFi, SSH, VPN, alerter, etc.) +# 5. Applies all config to card in a single batch after operator confirms summary +# 6. Sets up first-boot systemd service for autonomous installation +# 7. Unmounts card — ready to insert and power on +# +# Style: bash only, ANSI colors, numbered menus, c2itall-style (no preamble, direct action) +# Root required for: mount, unmount, dd, partprobe, rsync to /mnt, chown, chmod on mounted fs +# +set -euo pipefail + +# ══════════════════════════════════════════════════════════════════════════════ +# COLORS (bash ANSI only) +# ══════════════════════════════════════════════════════════════════════════════ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +BOLD='\033[1m' +NC='\033[0m' + +# ══════════════════════════════════════════════════════════════════════════════ +# HELPERS +# ══════════════════════════════════════════════════════════════════════════════ +info() { echo -e "${GREEN}[+]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +error() { echo -e "${RED}[-]${NC} $*"; exit 1; } +step() { echo -e "${CYAN}[*]${NC} $*"; } +section() { + echo "" + echo -e "${BOLD}${BLUE}─────────────────────────────────────────────${NC}" + echo -e "${BOLD}$*${NC}" + echo -e "${BOLD}${BLUE}─────────────────────────────────────────────${NC}" + echo "" +} +banner() { + echo -e "${BOLD}${MAGENTA}╔════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}${MAGENTA}║ BIGBROTHER // OPERATOR SETUP ║${NC}" + echo -e "${BOLD}${MAGENTA}╚════════════════════════════════════════════╝${NC}" + echo "" +} + +# ══════════════════════════════════════════════════════════════════════════════ +# PREFLIGHT +# ══════════════════════════════════════════════════════════════════════════════ +[[ $EUID -eq 0 ]] || error "Must run as root (use: sudo bash scripts/operator_setup.sh)" + +SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +CACHE_DIR="${HOME}/.bigbrother/images" +CREDS="${HOME}/.local/bin/creds" +MOUNT_POINT="/mnt/opi" + +mkdir -p "$CACHE_DIR" + +# ══════════════════════════════════════════════════════════════════════════════ +# 1. BANNER +# ══════════════════════════════════════════════════════════════════════════════ +clear +banner + +# ══════════════════════════════════════════════════════════════════════════════ +# 2. SD CARD DETECTION +# ══════════════════════════════════════════════════════════════════════════════ +section "SD Card Detection" +info "Scanning block devices..." +echo "" + +lsblk -ndo NAME,SIZE,LABEL,TYPE | grep -E "^(sd|mmc)" | nl -v 1 -w1 -s') ' || error "No block devices found. Insert SD card and try again." + +echo "" +echo -n -e "${CYAN}Enter device number (1-9):${NC} " +read -r device_num +[[ "$device_num" =~ ^[1-9]$ ]] || error "Invalid selection" + +device_row=$(lsblk -ndo NAME,SIZE,LABEL,TYPE | grep -E "^(sd|mmc)" | sed -n "${device_num}p") +DEVICE=$(echo "$device_row" | awk '{print $1}') +DEVICE_SIZE=$(echo "$device_row" | awk '{print $2}') + +info "Selected: /dev/${DEVICE} (${DEVICE_SIZE})" +warn "This will OVERWRITE all data on /dev/${DEVICE}" +echo -n -e "${YELLOW}Type device name to confirm (${DEVICE}):${NC} " +read -r confirm_dev +[[ "$confirm_dev" == "$DEVICE" ]] || error "Device name mismatch. Aborting." + +info "Device confirmed: /dev/${DEVICE}" + +# ══════════════════════════════════════════════════════════════════════════════ +# 3. IMAGE MANAGEMENT +# ══════════════════════════════════════════════════════════════════════════════ +section "Image Management" + +# Look for existing cached image +CACHED_IMAGE=$(ls -1 "$CACHE_DIR"/Armbian_*_Orangepizero3_bookworm_current_*_minimal.img.xz 2>/dev/null | head -1) + +if [[ -n "$CACHED_IMAGE" ]]; then + info "Found cached image: $(basename $CACHED_IMAGE)" + echo -n -e "${CYAN}Use cached image? (y/n):${NC} " + read -r use_cached + if [[ "$use_cached" =~ ^[yY]$ ]]; then + IMAGE="$CACHED_IMAGE" + else + IMAGE="" + fi +else + IMAGE="" +fi + +if [[ -z "$IMAGE" ]]; then + step "Downloading Armbian image..." + # Hardcoded known-good URL (operator can update) + IMAGE_URL="https://dl.armbian.com/orangepizero3/Armbian_25.5.1_Orangepizero3_bookworm_current_6.12.23_minimal.img.xz" + IMAGE_FILENAME=$(basename "$IMAGE_URL") + IMAGE="${CACHE_DIR}/${IMAGE_FILENAME}" + + if [[ ! -f "$IMAGE" ]]; then + curl -fL --progress-bar "$IMAGE_URL" -o "$IMAGE" || error "Failed to download image" + info "Image downloaded: $IMAGE_FILENAME" + else + info "Image already cached: $IMAGE_FILENAME" + fi +fi + +# SHA256 verification (optional, skip if no .sha file) +SHA_FILE="${IMAGE}.sha" +if [[ -f "$SHA_FILE" ]]; then + step "Verifying SHA256..." + cd "$CACHE_DIR" + sha256sum -c "$(basename $SHA_FILE)" >/dev/null 2>&1 && info "SHA256 verified" || warn "SHA256 mismatch (proceeding anyway)" + cd - >/dev/null +fi + +# ══════════════════════════════════════════════════════════════════════════════ +# 4. CONFIGURATION WIZARD +# ══════════════════════════════════════════════════════════════════════════════ +section "Configuration Wizard" + +# WiFi (optional) +step "WiFi Configuration" +echo -n -e "${CYAN}Configure WiFi? (y/n):${NC} " +read -r enable_wifi +if [[ "$enable_wifi" =~ ^[yY]$ ]]; then + echo -n -e "${CYAN}WiFi SSID:${NC} " + read -r wifi_ssid + echo -n -e "${CYAN}WiFi Passphrase:${NC} " + read -s -r wifi_pass; echo + echo -n -e "${CYAN}Confirm Passphrase:${NC} " + read -s -r wifi_pass2; echo + [[ "$wifi_pass" == "$wifi_pass2" ]] || error "Passphrases do not match" + WIFI_SSID="$wifi_ssid" + WIFI_PASS="$wifi_pass" + unset wifi_pass wifi_pass2 +else + WIFI_SSID="" + WIFI_PASS="" +fi + +# Root password (required) +step "Root Password" +while true; do + echo -n -e "${CYAN}Root password:${NC} " + read -s -r root_pass; echo + echo -n -e "${CYAN}Confirm:${NC} " + read -s -r root_pass2; echo + if [[ "$root_pass" == "$root_pass2" && -n "$root_pass" ]]; then + ROOT_PASS="$root_pass" + unset root_pass root_pass2 + break + else + warn "Passwords do not match or empty. Try again." + fi +done + +# SSH Key +step "SSH Key Configuration" +echo " [1] Generate new keypair (stored in Infisical)" +echo " [2] Use existing public key file" +echo -n -e "${CYAN}SSH option (1-2):${NC} " +read -r ssh_opt + +if [[ "$ssh_opt" == "1" ]]; then + SSH_MODE="generate" +elif [[ "$ssh_opt" == "2" ]]; then + SSH_MODE="existing" + echo -n -e "${CYAN}Path to public key file:${NC} " + read -r ssh_pubkey_path + [[ -f "$ssh_pubkey_path" ]] || error "File not found: $ssh_pubkey_path" + SSH_PUBKEY=$(cat "$ssh_pubkey_path") +else + error "Invalid SSH option" +fi + +# Device ID +step "Device ID" +echo -n -e "${CYAN}Device label (e.g. bb-01):${NC} " +read -r device_id +[[ -n "$device_id" ]] || error "Device ID cannot be empty" +DEVICE_ID="$device_id" + +# Auto-start mode +step "Auto-start Mode on Boot" +echo " [1] Passive only (capture + analysis, no active offense)" +echo " [2] Full autonomous (all modules)" +echo " [3] Manual (operator SSHs in to start)" +echo -n -e "${CYAN}Select mode (1-3):${NC} " +read -r mode_opt + +if [[ "$mode_opt" == "1" ]]; then + BB_MODE="passive" +elif [[ "$mode_opt" == "2" ]]; then + BB_MODE="autonomous" +elif [[ "$mode_opt" == "3" ]]; then + BB_MODE="manual" +else + error "Invalid mode" +fi + +# VPN tunnel +step "VPN Tunnel" +echo " [1] None" +echo " [2] Tailscale" +echo " [3] WireGuard" +echo -n -e "${CYAN}Select VPN (1-3):${NC} " +read -r vpn_opt + +BB_VPN="none" +TAILSCALE_KEY="" +WG_CONFIG="" + +if [[ "$vpn_opt" == "1" ]]; then + BB_VPN="none" +elif [[ "$vpn_opt" == "2" ]]; then + BB_VPN="tailscale" + echo -n -e "${CYAN}Tailscale auth key:${NC} " + read -r tailscale_key + TAILSCALE_KEY="$tailscale_key" +elif [[ "$vpn_opt" == "3" ]]; then + BB_VPN="wireguard" + echo -n -e "${CYAN}Path to WireGuard config (wg0.conf):${NC} " + read -r wg_config_path + [[ -f "$wg_config_path" ]] || error "File not found: $wg_config_path" + WG_CONFIG=$(cat "$wg_config_path") +else + error "Invalid VPN option" +fi + +# Network alerter webhook +step "Network Alerter" +echo -n -e "${CYAN}Enable alerter webhook? (y/n):${NC} " +read -r enable_alerter +if [[ "$enable_alerter" =~ ^[yY]$ ]]; then + echo -n -e "${CYAN}Webhook URL (Slack/Discord/Matrix):${NC} " + read -r webhook_url + BB_ALERTER_WEBHOOK="$webhook_url" +else + BB_ALERTER_WEBHOOK="" +fi + +# ══════════════════════════════════════════════════════════════════════════════ +# 5. CONFIG SUMMARY +# ══════════════════════════════════════════════════════════════════════════════ +section "Configuration Summary" + +echo "Device ID: ${BOLD}${DEVICE_ID}${NC}" +echo "Target: ${BOLD}/dev/${DEVICE}${NC}" +echo "Image: ${BOLD}$(basename $IMAGE)${NC}" +echo "" +echo "WiFi: ${BOLD}$([ -n "$WIFI_SSID" ] && echo "$WIFI_SSID" || echo "disabled")${NC}" +echo "Root Pass: ${BOLD}[set]${NC}" +echo "SSH Mode: ${BOLD}${SSH_MODE}${NC}" +echo "Auto-start: ${BOLD}${BB_MODE}${NC}" +echo "VPN: ${BOLD}${BB_VPN}${NC}" +echo "Alerter: ${BOLD}$([ -n "$BB_ALERTER_WEBHOOK" ] && echo "enabled" || echo "disabled")${NC}" +echo "" + +echo -n -e "${CYAN}Proceed with flashing and configuration? (y/n):${NC} " +read -r confirm_proceed +[[ "$confirm_proceed" =~ ^[yY]$ ]] || { info "Aborted"; exit 0; } + +# ══════════════════════════════════════════════════════════════════════════════ +# 6. FLASH AND MOUNT +# ══════════════════════════════════════════════════════════════════════════════ +section "Flashing SD Card" + +step "Unmounting any existing mounts on /dev/${DEVICE}..." +for part in /dev/${DEVICE}*; do + [[ -b "$part" ]] && umount "$part" 2>/dev/null || true +done + +step "Flashing image to /dev/${DEVICE}..." +xzcat "$IMAGE" | dd of="/dev/${DEVICE}" bs=4M status=progress conv=fsync || error "Flash failed" +info "Flash complete" + +step "Running partprobe..." +partprobe "/dev/${DEVICE}" || warn "partprobe failed (non-critical)" +sleep 3 + +step "Mounting to ${MOUNT_POINT}..." +mkdir -p "$MOUNT_POINT" +# Try /dev/${DEVICE}1 (most common for single-partition images) +mount "/dev/${DEVICE}1" "$MOUNT_POINT" 2>/dev/null || mount "/dev/${DEVICE}p1" "$MOUNT_POINT" || error "Mount failed. Check device partitioning." + +info "Mounted at ${MOUNT_POINT}" + +# ══════════════════════════════════════════════════════════════════════════════ +# 7. APPLY CONFIGURATION TO CARD +# ══════════════════════════════════════════════════════════════════════════════ +section "Applying Configuration" + +# WiFi profile +if [[ -n "$WIFI_SSID" ]]; then + step "Writing WiFi profile..." + NM_DIR="${MOUNT_POINT}/etc/NetworkManager/system-connections" + NM_FILE="${NM_DIR}/wifi-bb.nmconnection" + mkdir -p "$NM_DIR" + + 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=${WIFI_SSID} + +[wifi-security] +auth-alg=open +key-mgmt=wpa-psk +psk=${WIFI_PASS} + +[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: ${WIFI_SSID}, UUID: ${WIFI_UUID})" +fi + +# Root password +step "Setting root password..." +ROOT_PASS_HASH=$(openssl passwd -6 "$ROOT_PASS") +sed -i "s|^root:[^:]*:|root:${ROOT_PASS_HASH}:|" "${MOUNT_POINT}/etc/shadow" +info "Root password set" +unset ROOT_PASS ROOT_PASS_HASH + +# SSH keys +step "Configuring SSH..." +SSH_DIR="${MOUNT_POINT}/root/.ssh" +mkdir -p "$SSH_DIR" + +if [[ "$SSH_MODE" == "generate" ]]; then + 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" + + echo "$PUBKEY" > "${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:" + echo "---" + echo "$PRIVKEY" + echo "---" + fi + unset PRIVKEY +else + echo "$SSH_PUBKEY" > "${SSH_DIR}/authorized_keys" +fi + +chmod 700 "$SSH_DIR" +chmod 600 "${SSH_DIR}/authorized_keys" +info "SSH authorized_keys configured" + +# Hostname +echo "orangepi" > "${MOUNT_POINT}/etc/hostname" + +# Disable first-login wizard +rm -f "${MOUNT_POINT}/root/.not_logged_in_yet" + +# Copy BigBrother source +step "Copying BigBrother source to card..." +SRC_DEST="${MOUNT_POINT}/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/" || error "rsync failed" +info "Source copied to /root/bb-src/" + +# Write bb-config.env +step "Writing configuration to card..." +cat > "${MOUNT_POINT}/root/bb-config.env" << EOF +BB_MODE="${BB_MODE}" +BB_VPN="${BB_VPN}" +BB_DEVICE_ID="${DEVICE_ID}" +EOF + +if [[ -n "$BB_ALERTER_WEBHOOK" ]]; then + echo "BB_ALERTER_WEBHOOK=\"${BB_ALERTER_WEBHOOK}\"" >> "${MOUNT_POINT}/root/bb-config.env" +fi + +# Store Tailscale auth key if needed +if [[ "$BB_VPN" == "tailscale" && -n "$TAILSCALE_KEY" ]]; then + echo "BB_TAILSCALE_KEY=\"${TAILSCALE_KEY}\"" >> "${MOUNT_POINT}/root/bb-config.env" +fi + +# Store WireGuard config if needed +if [[ "$BB_VPN" == "wireguard" && -n "$WG_CONFIG" ]]; then + echo "$WG_CONFIG" > "${MOUNT_POINT}/etc/wireguard/wg0.conf" + chmod 600 "${MOUNT_POINT}/etc/wireguard/wg0.conf" +fi + +chmod 600 "${MOUNT_POINT}/root/bb-config.env" +info "Configuration written" + +# First-boot script +step "Installing first-boot service..." +cat > "${MOUNT_POINT}/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 internet connectivity +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" + exit 1 + fi + sleep 3 +done + +source /root/bb-config.env + +# Install BigBrother +cd /root/bb-src +bash setup.sh --enable-service + +# VPN setup +if [[ "${BB_VPN:-}" == "tailscale" && -n "${BB_TAILSCALE_KEY:-}" ]]; then + echo "[*] Installing Tailscale..." + curl -fsSL https://tailscale.com/install.sh | sh + echo "[*] Connecting to Tailscale..." + tailscale up --auth-key="${BB_TAILSCALE_KEY}" --accept-routes || echo "[-] Tailscale auth failed" + # Remove auth key from config after use + sed -i '/BB_TAILSCALE_KEY/d' /root/bb-config.env +elif [[ "${BB_VPN:-}" == "wireguard" ]]; then + echo "[*] Installing WireGuard..." + apt-get update >/dev/null 2>&1 + apt-get install -y wireguard >/dev/null 2>&1 + systemctl enable --now wg-quick@wg0 || echo "[-] WireGuard startup failed" +fi + +# Network alerter +if [[ -n "${BB_ALERTER_WEBHOOK:-}" ]]; then + echo "[*] Sending online alert..." + curl -sf -X POST "$BB_ALERTER_WEBHOOK" \ + -H 'Content-Type: application/json' \ + -d "{\"text\":\"BigBrother ${BB_DEVICE_ID} online\"}" || true +fi + +echo "[+] First-boot complete: $(date)" + +# Disable ourselves +rm -f /root/.bb-firstboot-needed +systemctl disable bb-firstboot.service +RUNEOF +chmod 755 "${MOUNT_POINT}/root/bb-firstboot-run.sh" + +# First-boot systemd service +cat > "${MOUNT_POINT}/etc/systemd/system/bb-firstboot.service" << 'SVCEOF' +[Unit] +Description=BigBrother first-boot setup +After=network-online.target +Wants=network-online.target +ConditionPathExists=/root/.bb-firstboot-needed + +[Service] +Type=oneshot +ExecStart=/bin/bash /root/bb-firstboot-run.sh +RemainAfterExit=yes +TimeoutStartSec=900 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +SVCEOF + +# Enable service symlink +WANTS_DIR="${MOUNT_POINT}/etc/systemd/system/multi-user.target.wants" +mkdir -p "$WANTS_DIR" +ln -sf /etc/systemd/system/bb-firstboot.service "${WANTS_DIR}/bb-firstboot.service" + +# Firstboot sentinel +touch "${MOUNT_POINT}/root/.bb-firstboot-needed" + +info "First-boot service installed and enabled" + +# ══════════════════════════════════════════════════════════════════════════════ +# 8. UNMOUNT +# ══════════════════════════════════════════════════════════════════════════════ +section "Finalizing" + +step "Syncing and unmounting..." +sync +umount "$MOUNT_POINT" || warn "umount failed (non-critical)" +info "SD card ready to remove" + +# ══════════════════════════════════════════════════════════════════════════════ +# 9. DONE +# ══════════════════════════════════════════════════════════════════════════════ +section "Setup Complete" + +echo "${GREEN}Insert SD card into device and power on.${NC}" +echo "" +echo "The device will:" +echo " 1. Boot and connect to WiFi (if configured)" +echo " 2. Run /root/bb-firstboot-run.sh automatically" +echo " 3. Install BigBrother source and dependencies" +echo " 4. Set up systemd services" +echo " 5. Configure VPN (if selected)" +echo " 6. Send alerter webhook (if configured)" +echo "" +echo "No further operator interaction required. Device walks away autonomously." +echo "" +echo "${CYAN}To SSH after first-boot:${NC}" + +if [[ "$SSH_MODE" == "generate" ]]; then + INFISICAL_KEY="SSH_PRIVKEY_$(echo "$DEVICE_ID" | tr '[:lower:]-' '[:upper:]_')" + 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 +else + echo " ssh -i $(dirname $ssh_pubkey_path)/$(basename $ssh_pubkey_path .pub) root@" +fi + +echo "" +echo "${CYAN}Monitor first-boot:${NC}" +echo " ssh root@ 'tail -f /root/bb-firstboot.log'" +echo "" diff --git a/scripts/preconfig_sd.sh b/scripts/preconfig_sd.sh deleted file mode 100755 index ce3f678..0000000 --- a/scripts/preconfig_sd.sh +++ /dev/null @@ -1,216 +0,0 @@ -#!/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'" diff --git a/scripts/setup_wifi.sh b/scripts/setup_wifi.sh deleted file mode 100644 index d51e92c..0000000 --- a/scripts/setup_wifi.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# Write a NetworkManager WiFi profile to an unmounted Armbian SD card. -# Run this on the machine where the SD card is mounted BEFORE first boot. -# -# Usage: bash scripts/setup_wifi.sh SSID PASSPHRASE [MOUNT_POINT] -# MOUNT_POINT defaults to /media/$USER/armbi_root -set -euo pipefail - -RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' -info() { echo -e "${GREEN}[+]${NC} $*"; } -warn() { echo -e "${YELLOW}[!]${NC} $*"; } -error() { echo -e "${RED}[-]${NC} $*"; exit 1; } - -SSID="${1:-}" -PASSPHRASE="${2:-}" -MOUNT="${3:-/media/${USER:-root}/armbi_root}" - -[[ -z "$SSID" || -z "$PASSPHRASE" ]] && { - echo "Usage: $0 SSID PASSPHRASE [MOUNT_POINT]" - echo " MOUNT_POINT defaults to /media/\$USER/armbi_root" - exit 1 -} - -[[ -d "$MOUNT" ]] || error "Mount point not found: $MOUNT — is the SD card mounted?" - -NM_DIR="${MOUNT}/etc/NetworkManager/system-connections" -NM_FILE="${NM_DIR}/wifi-bb.nmconnection" - -mkdir -p "$NM_DIR" - -cat > "$NM_FILE" << EOF -[connection] -id=wifi-bb -type=wifi -autoconnect=true - -[wifi] -mode=infrastructure -ssid=${SSID} - -[wifi-security] -auth-alg=open -key-mgmt=wpa-psk -psk=${PASSPHRASE} - -[ipv4] -method=auto - -[ipv6] -method=disabled -EOF - -chmod 600 "$NM_FILE" -info "WiFi profile written to ${NM_FILE}" -info "SSID: ${SSID}" -echo "" -info "After first boot, device should connect automatically." -warn "If it doesn't connect, SSH in via Ethernet or serial and run:" -warn " nmcli dev wifi connect '${SSID}' password ''" -echo "" -info "Once connected, deploy BigBrother with:" -info " bash scripts/deploy.sh root@ --enable-service"