Full autonomous first-boot: fix WiFi UUID, self-installing setup
WiFi keyfiles require UUID or NM silently ignores them — that was why WiFi never connected. Rewrote preconfig_sd.sh to: - Generate UUID for NM keyfile - Copy BB source to /root/bb-src/ on the card - Install bb-firstboot.service that runs setup.sh --enable-service after network-online.target, then disables itself - Per-device keygen, private key to Infisical Single command now produces a card that boots and self-installs.
This commit is contained in:
+142
-44
@@ -1,20 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pre-configure an Armbian SD card before first boot.
|
||||
# Handles: WiFi, root password, per-device SSH keypair, hostname, wizard bypass.
|
||||
# Pre-configure an Armbian SD card for fully autonomous first-boot setup.
|
||||
#
|
||||
# Generates a fresh ed25519 keypair unique to this device.
|
||||
# Private key is stored in Infisical under /bigbrother/DEVICE_ID.
|
||||
# Public key is written to the SD card — no operator keys touch the device.
|
||||
# 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]
|
||||
# DEVICE_ID short identifier for this unit (e.g. bb-01, drop-lab)
|
||||
# MOUNT_POINT defaults to /mnt/opi
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
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)"
|
||||
|
||||
@@ -25,24 +30,31 @@ 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 stored in Infisical (e.g. bb-01)"
|
||||
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"
|
||||
[[ -x "$CREDS" ]] || error "creds CLI not found at $CREDS — cannot store key in Infisical"
|
||||
|
||||
# ── WiFi ──────────────────────────────────────────────────────────────────────
|
||||
# ── 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
|
||||
@@ -57,12 +69,16 @@ psk=${PASSPHRASE}
|
||||
method=auto
|
||||
|
||||
[ipv6]
|
||||
method=disabled
|
||||
addr-gen-mode=stable-privacy
|
||||
method=auto
|
||||
EOF
|
||||
chmod 600 "$NM_FILE"
|
||||
info "WiFi profile written (${SSID})"
|
||||
|
||||
# ── Root password ─────────────────────────────────────────────────────────────
|
||||
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"
|
||||
@@ -71,48 +87,130 @@ sed -i "s|^root:[^:]*:|root:${HASH}:|" "${MOUNT}/etc/shadow"
|
||||
info "Root password set"
|
||||
unset ROOT_PASS ROOT_PASS2
|
||||
|
||||
# ── Per-device SSH keypair ────────────────────────────────────────────────────
|
||||
# Generate fresh keypair — no operator keys, no identifying comments
|
||||
# ── 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"
|
||||
|
||||
# Store private key in Infisical under bigbrother/DEVICE_ID
|
||||
# Key name: SSH_PRIVKEY_<DEVICE_ID> (uppercase, hyphens to underscores)
|
||||
INFISICAL_KEY="SSH_PRIVKEY_$(echo "$DEVICE_ID" | tr '[:lower:]-' '[:upper:]_')"
|
||||
if "$CREDS" set "$INFISICAL_KEY" "$PRIVKEY" bigbrother 2>/dev/null; then
|
||||
info "Private key stored in Infisical: bigbrother/${INFISICAL_KEY}"
|
||||
else
|
||||
# Fallback: print key and warn — operator must store it manually
|
||||
warn "Could not store key in Infisical. SAVE THIS PRIVATE KEY NOW:"
|
||||
echo "------"
|
||||
echo "$PRIVKEY"
|
||||
echo "------"
|
||||
fi
|
||||
unset PRIVKEY
|
||||
|
||||
# Write only the public key to the device
|
||||
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"
|
||||
info "Per-device public key written to SD card (no operator keys present)"
|
||||
|
||||
# ── Hostname ──────────────────────────────────────────────────────────────────
|
||||
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"
|
||||
info "Hostname set to: orangepi"
|
||||
|
||||
# ── Bypass first-login wizard ─────────────────────────────────────────────────
|
||||
rm -f "${MOUNT}/root/.not_logged_in_yet"
|
||||
info "First-login wizard disabled"
|
||||
info "Hostname set, first-login wizard disabled"
|
||||
|
||||
echo ""
|
||||
info "SD card ready. Unmount, insert, and power on."
|
||||
info "To connect after boot:"
|
||||
info " ${CREDS} get ${INFISICAL_KEY} bigbrother > /tmp/bb-${DEVICE_ID}.key"
|
||||
info " chmod 600 /tmp/bb-${DEVICE_ID}.key"
|
||||
info " ssh -i /tmp/bb-${DEVICE_ID}.key root@10.0.0.0"
|
||||
info "To deploy: bash scripts/deploy.sh root@10.0.0.0 --enable-service"
|
||||
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@<DEVICE_IP>"
|
||||
fi
|
||||
echo ""
|
||||
echo " Monitor first-boot: ssh ... 'tail -f /root/bb-firstboot.log'"
|
||||
|
||||
Reference in New Issue
Block a user