diff --git a/.gitignore b/.gitignore index 3ac4683..c30fdbc 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ aether_native_app_config.json # package-lock.json builds/ resources/seed_config.json +event.env diff --git a/README.md b/README.md index 9a9063c..f7ba40d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,15 @@ This application serves as the "Native Mode" runtime for Aether podiums and devi SSH user on all laptops: **`speaker ready`** IP pattern: `192.168.32.1XX` (XX = zero-padded laptop number, e.g. 03 → `.103`). Find/replace this prefix for other onsite environments. +Deploy files live in `deploy/`: + +| File | Purpose | +| --- | --- | +| `deploy/deploy.sh` | Deploy script — handles arch detection, scp, and seed.json | +| `deploy/devices.conf` | Laptop list: number, IP, `event_device_id` | +| `deploy/event.env` | **Gitignored** — per-event API key and URLs (create from example) | +| `deploy/event.env.example` | Template for `event.env` | + ### Step 1 — Build the app (workstation) ```bash @@ -29,35 +38,62 @@ npm run package:mac Only rebuild if source code has changed. The `.app` bundle is identical for all Intel laptops — only `~/seed.json` differs per device. -### Step 2 — Determine target architecture and copy the .app +### Step 2 — Create event.env -Check the target Mac's CPU if unsure: ```bash -ssh "speaker ready"@192.168.32.103 "uname -m" -# x86_64 → use aether_launcher-darwin-x64 (MacBook Air 2018 and all current Intel Macs) -# arm64 → use aether_launcher-darwin-arm64 (Apple Silicon M1/M2/M3/M4) +cp deploy/event.env.example deploy/event.env +# Edit deploy/event.env — fill in AETHER_API_KEY ``` -Copy from the workstation (replace `103` with the target IP last octet): +Create the API key in the Aether admin panel before the show (Core → Accounts or Events → +Devices API key section). All laptops share one key per event. Delete it after the show. + +### Step 3 — Run the deploy script + ```bash -# Intel (current hardware): +# Deploy specific laptops: +./deploy/deploy.sh 01 02 03 + +# Deploy all laptops in devices.conf: +./deploy/deploy.sh all + +# Update seed.json only (no .app copy — e.g. when rotating the API key): +./deploy/deploy.sh --seed-only all +``` + +The script auto-detects each Mac's CPU architecture, copies the correct `.app` build, writes +`seed.json`, and verifies. One SSH connection failure won't abort the batch — it logs and +continues, then reports which laptops need a retry. + +### Step 4 — Verify and launch + +After the script completes, launch the app on each laptop and confirm it connects and shows +the correct device name in the Launcher UI. + +### Adding SSH key to a new laptop (first time only) + +```bash +ssh-copy-id "speaker ready"@192.168.32.1XX +``` + +Run once per laptop before deploying. + +--- + +### Manual deploy reference + +The script covers the normal case. For one-off fixes or if the script isn't available: + +```bash +# Detect arch +ssh "speaker ready"@192.168.32.103 "uname -m" +# x86_64 → darwin-x64 | arm64 → darwin-arm64 + +# Copy .app (Intel example): scp -r builds/aether_launcher-darwin-x64/aether_launcher.app \ "speaker ready"@192.168.32.103:/Applications/aether_launcher.app -# Apple Silicon (future hardware): -scp -r builds/aether_launcher-darwin-arm64/aether_launcher.app \ - "speaker ready"@192.168.32.103:/Applications/aether_launcher.app -``` - -If `/Applications/aether_launcher.app` already exists, `scp -r` overwrites it. No need to -remove the old version first. - -### Step 3 — Write seed.json on the target laptop - -The seed file lives at `~/seed.json` (`/Users/speaker ready/seed.json`) on each Mac. -It is intentionally outside the app bundle so it can be updated without redeploying. - -```bash +# Write seed.json: ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF' { "event_device_id": "DEVICE_ID_FOR_THIS_LAPTOP", @@ -67,57 +103,13 @@ ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF' "onsite_api_base_url": null } EOF + +# Verify: +ssh "speaker ready"@192.168.32.103 "cat ~/seed.json" ``` `event_device_id` values by laptop — see the **Device Reference** table below. -### Step 4 — Verify - -```bash -# Confirm seed.json landed correctly -ssh "speaker ready"@192.168.32.103 "cat ~/seed.json" - -# Confirm the .app is present -ssh "speaker ready"@192.168.32.103 "ls /Applications/aether_launcher.app" -``` - -Then launch the app on the laptop and confirm it connects and shows the correct device name -in the Launcher UI. - -### Updating seed.json only (no app reinstall) - -If only the device config needs updating (e.g. changing `event_device_id` or `onsite_api_base_url`): - -```bash -ssh "speaker ready"@192.168.32.103 "cat > ~/seed.json" << 'EOF' -{ ... } -EOF -``` - -No need to re-copy the `.app`. Restart the Electron app after writing the new seed. - -### Deploy to multiple laptops at once - -Repeat Steps 2–3 for each laptop, or use a loop: - -```bash -for OCTET in 103 104 105 106; do - echo "=== Deploying to 192.168.32.$OCTET ===" - scp -r builds/aether_launcher-darwin-x64/aether_launcher.app \ - "speaker ready"@192.168.32.$OCTET:/Applications/aether_launcher.app -done -``` - -`seed.json` must still be written per-device (each has a unique `event_device_id`). - -### Adding SSH key to a new laptop (first time only) - -```bash -ssh-copy-id "speaker ready"@192.168.32.1XX -``` - -Run once per laptop before attempting any of the deploy steps above. - --- ## 📋 Device Reference diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..a8e5471 --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,201 @@ +#!/usr/bin/env bash +# deploy.sh — Deploy Aether Native Launcher to onsite Mac laptops +# +# USAGE: +# ./deploy.sh [num ...] Deploy to one or more laptops (e.g. 03 04 05) +# ./deploy.sh all Deploy to all laptops in devices.conf +# ./deploy.sh --seed-only Update seed.json only — skip .app copy +# ./deploy.sh --seed-only all +# +# REQUIRES: +# event.env — copy from event.env.example and fill in AETHER_API_KEY +# builds/ — run "npm run package:mac" first if .app is missing +# +# SSH keys must already be installed on each target (run ssh-copy-id once per laptop). + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEVICES_FILE="$SCRIPT_DIR/devices.conf" +EVENT_ENV="$SCRIPT_DIR/event.env" +SSH_USER="speaker ready" +BUILD_DIR="$SCRIPT_DIR/../builds" + +# ── Argument parsing ────────────────────────────────────────────────────────── + +SEED_ONLY=false +TARGETS=() + +usage() { + grep '^#' "$0" | grep -v '^#!/' | sed 's/^# \{0,1\}//' + exit 1 +} + +if [[ $# -eq 0 ]]; then usage; fi + +while [[ $# -gt 0 ]]; do + case "$1" in + --seed-only) SEED_ONLY=true ;; + --help|-h) usage ;; + all) TARGETS+=("all") ;; + *) TARGETS+=("$1") ;; + esac + shift +done + +if [[ ${#TARGETS[@]} -eq 0 ]]; then + echo "ERROR: No targets specified." + usage +fi + +# ── Load event config ───────────────────────────────────────────────────────── + +if [[ ! -f "$EVENT_ENV" ]]; then + echo "ERROR: $EVENT_ENV not found." + echo " Copy event.env.example → event.env and fill in AETHER_API_KEY." + exit 1 +fi + +# shellcheck source=/dev/null +source "$EVENT_ENV" + +: "${AETHER_API_KEY:?event.env must set AETHER_API_KEY}" +: "${PRIMARY_API_BASE_URL:?event.env must set PRIMARY_API_BASE_URL}" +: "${BACKUP_API_BASE_URL:?event.env must set BACKUP_API_BASE_URL}" + +# Render onsite URL as JSON null or quoted string +if [[ -n "${ONSITE_API_BASE_URL:-}" ]]; then + ONSITE_JSON="\"$ONSITE_API_BASE_URL\"" +else + ONSITE_JSON="null" +fi + +# ── Device list helpers ─────────────────────────────────────────────────────── + +# Returns "ip device_id" for a laptop number, or empty if not found +lookup_device() { + local num="$1" + grep -E "^[[:space:]]*${num}[[:space:]]" "$DEVICES_FILE" \ + | grep -v '^[[:space:]]*#' \ + | awk '{print $2, $3}' \ + | head -1 +} + +# Returns all laptop numbers from devices.conf (first column, non-comment lines) +all_device_nums() { + grep -v '^[[:space:]]*#' "$DEVICES_FILE" \ + | grep -v '^[[:space:]]*$' \ + | awk '{print $1}' +} + +# ── Deploy one laptop ───────────────────────────────────────────────────────── + +deploy_laptop() { + local num="$1" + + local info + info=$(lookup_device "$num") + if [[ -z "$info" ]]; then + echo " ERROR: Laptop $num not found in devices.conf" + return 1 + fi + + local ip device_id + ip=$(echo "$info" | awk '{print $1}') + device_id=$(echo "$info" | awk '{print $2}') + + echo "" + echo "══════════════════════════════════════════════" + echo " Laptop $num · $ip · $device_id" + echo "══════════════════════════════════════════════" + + # ── Copy .app ────────────────────────────────────────────────────────── + if [[ "$SEED_ONLY" != "true" ]]; then + echo " Detecting architecture..." + local arch + arch=$(ssh "$SSH_USER@$ip" "uname -m" 2>/dev/null) || { + echo " ERROR: SSH failed for $ip — is the laptop on the network?" + return 1 + } + + local bundle + case "$arch" in + x86_64) bundle="$BUILD_DIR/aether_launcher-darwin-x64/aether_launcher.app" ;; + arm64) bundle="$BUILD_DIR/aether_launcher-darwin-arm64/aether_launcher.app" ;; + *) + echo " ERROR: Unknown arch '$arch' on $ip" + return 1 + ;; + esac + + if [[ ! -d "$bundle" ]]; then + echo " ERROR: Build not found: $bundle" + echo " Run: npm run package:mac" + return 1 + fi + + echo " Arch: $arch → copying $(basename "$bundle")..." + scp -r "$bundle" "$SSH_USER@$ip:/Applications/aether_launcher.app" || { + echo " ERROR: scp failed." + return 1 + } + echo " .app copied." + else + echo " (--seed-only: skipping .app copy)" + fi + + # ── Write seed.json ──────────────────────────────────────────────────── + echo " Writing seed.json..." + ssh "$SSH_USER@$ip" "cat > ~/seed.json" <