feat: test VM build/run script with SSH diagnostics
- nixos/vm.nix: enable PasswordAuthentication for kiosk user (SSH test access) - scripts/test-vm.sh: one-command build → start → diagnose workflow - macOS: builds and runs on remote Linux server, SSH through server - Linux: builds and runs locally - Commands: --build, --start, --stop, --diagnose, --shell - Diagnostics: NetworkManager, PipeWire, wpctl, ALSA, pactl, processes, logs - AGENTS.md: compact agent guide for the repo
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
# RadiantWave Agent Guide
|
||||||
|
|
||||||
|
## What This Is
|
||||||
|
|
||||||
|
Go kiosk application using SDL2, OpenGL 3.3, SQLite (via GORM), and D-Bus (NetworkManager). Nix flake is the primary build system. CI runs on Forgejo (not GitHub Actions).
|
||||||
|
|
||||||
|
## Build & Run
|
||||||
|
|
||||||
|
Dev shell via direnv (`use flake` in `.envrc`). Inside the nix dev shell:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Build
|
||||||
|
go build -o bin/rhemawave rhemawave.go
|
||||||
|
|
||||||
|
# Run
|
||||||
|
./bin/rhemawave
|
||||||
|
|
||||||
|
# Nix build (CI uses this)
|
||||||
|
nix build -L
|
||||||
|
```
|
||||||
|
|
||||||
|
CGO is required (`CGO_ENABLED=1`). The nix dev shell and `flake.nix` `preBuild` set the necessary `CGO_CFLAGS` and `CGO_LDFLAGS` for SDL2. Do not attempt a plain `go build` outside the nix shell — it will fail on missing SDL2 headers.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Only one test suite exists: `internal/mixer/mixer_test.go`. Run with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go test ./internal/mixer/
|
||||||
|
```
|
||||||
|
|
||||||
|
No lint or typecheck commands are configured in the repo. CI only runs `nix build -L`.
|
||||||
|
|
||||||
|
## Branch Model
|
||||||
|
|
||||||
|
`dev` → `main` via pull requests. CI workflows are in `.forgejo/workflows/`:
|
||||||
|
- `build-dev.yml`: PRs to `dev`
|
||||||
|
- `build-prod.yml`: PRs to `main`
|
||||||
|
|
||||||
|
Both run `nix build -L` on a self-hosted runner (`neils-server-runner`).
|
||||||
|
|
||||||
|
## Key Architecture
|
||||||
|
|
||||||
|
- **Entry**: `rhemawave.go` → `internal/application.Run()`
|
||||||
|
- **State machine**: `internal/application/application.go` — FIRST_RUN → NEEDS_INTERNET → NEEDS_EMAIL_ADDRESS → NEEDS_LICENSE_KEY → DOWNLOADING_ASSETS → READY
|
||||||
|
- **Pages**: `internal/page/` — router pattern with page interface (`page.go`)
|
||||||
|
- **DB**: SQLite at `~/.local/share/rhemawave/database.sqlite`, managed by GORM in `internal/db/`
|
||||||
|
- **Assets**: `share/rhemawave/` (fonts, shaders, proclamations, tos) — copied into nix store at build time
|
||||||
|
- **Audio**: `.wav` files are gitignored, downloaded at runtime from `files.radiantwavetech.com`
|
||||||
|
- **Vendor**: Dependencies are vendored and committed. Use `go mod vendor` after adding deps.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
- The `dev_scripts/reset.sh` referenced in the nix devShell hook does not exist in the repo.
|
||||||
|
- `share/rhemawave/audio/*.wav` is gitignored — do not commit audio assets.
|
||||||
|
- The `vendor/` directory is committed — always run `go mod vendor` after `go mod tidy`.
|
||||||
|
- CI runs on a self-hosted Forgejo runner, not GitHub-hosted runners. Workflows source nix via `/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh`.
|
||||||
|
- The `packaging/` directory contains a kiosk-specific updater script and is not part of the main build.
|
||||||
+8
-2
@@ -62,14 +62,20 @@ in
|
|||||||
systemd.network.wait-online.enable = false;
|
systemd.network.wait-online.enable = false;
|
||||||
|
|
||||||
# ── SSH ───────────────────────────────────────────────────────────────────
|
# ── SSH ───────────────────────────────────────────────────────────────────
|
||||||
services.openssh.enable = true;
|
services.openssh = {
|
||||||
services.openssh.settings.PermitRootLogin = "prohibit-password";
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PermitRootLogin = "prohibit-password";
|
||||||
|
PasswordAuthentication = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# ── User (matches production) ─────────────────────────────────────────────
|
# ── User (matches production) ─────────────────────────────────────────────
|
||||||
users.users.kiosk = {
|
users.users.kiosk = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
uid = 1000;
|
uid = 1000;
|
||||||
extraGroups = [ "audio" "video" "input" "networkmanager" "wheel" ];
|
extraGroups = [ "audio" "video" "input" "networkmanager" "wheel" ];
|
||||||
|
password = "kiosk";
|
||||||
};
|
};
|
||||||
services.getty.autologinUser = "kiosk";
|
services.getty.autologinUser = "kiosk";
|
||||||
|
|
||||||
|
|||||||
Executable
+445
@@ -0,0 +1,445 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test-vm.sh — Build, start, and diagnose the RadiantWave NixOS test VM.
|
||||||
|
#
|
||||||
|
# On macOS (ARM): builds and runs the VM on a remote Linux server.
|
||||||
|
# On Linux (x86_64): builds and runs locally.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/test-vm.sh Build + start + run diagnostics
|
||||||
|
# ./scripts/test-vm.sh --build Build only
|
||||||
|
# ./scripts/test-vm.sh --start Start only (must already be built)
|
||||||
|
# ./scripts/test-vm.sh --stop Stop the running VM
|
||||||
|
# ./scripts/test-vm.sh --diagnose Run diagnostics on a running VM
|
||||||
|
# ./scripts/test-vm.sh --shell Interactive SSH shell into the VM
|
||||||
|
#
|
||||||
|
# Environment variables:
|
||||||
|
# RADIANTWAVE_REMOTE Remote server for macOS builds (default: nclack@192.168.1.203)
|
||||||
|
# RADIANTWAVE_REMOTE_DIR Remote repo path (default: ~/radiantwave)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Paths & defaults ──────────────────────────────────────────────────────────
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
REMOTE="${RADIANTWAVE_REMOTE:-nclack@192.168.1.203}"
|
||||||
|
REMOTE_DIR="${RADIANTWAVE_REMOTE_DIR:-~/radiantwave}"
|
||||||
|
SSH_PORT=2222
|
||||||
|
VM_USER="kiosk"
|
||||||
|
VM_PASS="kiosk"
|
||||||
|
|
||||||
|
# ── Colors ────────────────────────────────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'
|
||||||
|
DIM='\033[2m'; BOLD='\033[1m'; NC='\033[0m'
|
||||||
|
|
||||||
|
log() { echo -e "${BLUE}▸${NC} $*"; }
|
||||||
|
ok() { echo -e "${GREEN}✓${NC} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
||||||
|
err() { echo -e "${RED}✗${NC} $*" >&2; }
|
||||||
|
hr() { echo -e "${DIM}────────────────────────────────────────────────${NC}"; }
|
||||||
|
|
||||||
|
# ── Platform detection ────────────────────────────────────────────────────────
|
||||||
|
detect_platform() {
|
||||||
|
local os arch
|
||||||
|
os="$(uname -s)"
|
||||||
|
arch="$(uname -m)"
|
||||||
|
case "$os" in
|
||||||
|
Darwin) echo "macos" ;;
|
||||||
|
Linux)
|
||||||
|
case "$arch" in
|
||||||
|
x86_64) echo "linux" ;;
|
||||||
|
aarch64) echo "linux-arm" ;;
|
||||||
|
*) echo "linux-unknown" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*) echo "unknown" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
PLATFORM="$(detect_platform)"
|
||||||
|
|
||||||
|
# ── Dependency checks ─────────────────────────────────────────────────────────
|
||||||
|
check_deps() {
|
||||||
|
local missing=()
|
||||||
|
|
||||||
|
if ! command -v nix &>/dev/null; then
|
||||||
|
missing+=("nix")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "macos" ]]; then
|
||||||
|
for cmd in ssh rsync; do
|
||||||
|
command -v "$cmd" &>/dev/null || missing+=("$cmd")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( ${#missing[@]} > 0 )); then
|
||||||
|
err "Missing required commands: ${missing[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# sshpass — try direct, then nix-shell wrapper
|
||||||
|
if command -v sshpass &>/dev/null; then
|
||||||
|
SSPASS="sshpass"
|
||||||
|
elif nix-shell -p sshpass --run 'which sshpass' &>/dev/null; then
|
||||||
|
SSPASS="nix-shell -p sshpass --run"
|
||||||
|
else
|
||||||
|
err "sshpass not found. Install via: brew install sshpass (macOS) or nix profile install nixpkgs#sshpass"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── VM path helpers ───────────────────────────────────────────────────────────
|
||||||
|
# The nix build output is a directory containing bin/run-radiantwave-test-vm.
|
||||||
|
VM_RESULT_FILE="/tmp/radiantwave-test-vm-path"
|
||||||
|
VM_BIN_DIR=""
|
||||||
|
|
||||||
|
resolve_vm_bin() {
|
||||||
|
if [[ -n "$VM_BIN_DIR" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cached=""
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
if [[ -f "$VM_RESULT_FILE" ]]; then
|
||||||
|
cached="$(cat "$VM_RESULT_FILE")"
|
||||||
|
if [[ -x "$cached/bin/run-radiantwave-test-vm" ]]; then
|
||||||
|
VM_BIN_DIR="$cached"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cached="$(ssh "$REMOTE" "cat '$VM_RESULT_FILE' 2>/dev/null")" || true
|
||||||
|
if [[ -n "$cached" ]]; then
|
||||||
|
# Check if it exists on the remote server
|
||||||
|
if ssh "$REMOTE" "test -x '$cached/bin/run-radiantwave-test-vm'" 2>/dev/null; then
|
||||||
|
VM_BIN_DIR="$cached"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Platform-specific execution ───────────────────────────────────────────────
|
||||||
|
# On Linux: run commands locally.
|
||||||
|
# On macOS: sync repo to remote, then run commands there via SSH.
|
||||||
|
run_cmd() {
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
# Run on remote server
|
||||||
|
ssh "$REMOTE" "cd '$REMOTE_DIR' && $(printf '%q ' "$@")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_cmd_capture() {
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
ssh "$REMOTE" "cd '$REMOTE_DIR' && $(printf '%q ' "$@")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_to_remote() {
|
||||||
|
if [[ "$PLATFORM" == "macos" ]]; then
|
||||||
|
log "Syncing repo to remote server ($REMOTE)..."
|
||||||
|
# Ensure remote dir exists
|
||||||
|
ssh "$REMOTE" "mkdir -p '$REMOTE_DIR'"
|
||||||
|
rsync -avz --delete \
|
||||||
|
--exclude='.git/' \
|
||||||
|
--exclude='result' \
|
||||||
|
--exclude='result-*' \
|
||||||
|
--exclude='bin/' \
|
||||||
|
--exclude='build/' \
|
||||||
|
"$REPO_ROOT/" "$REMOTE:$REMOTE_DIR/"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Commands ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
cmd_build() {
|
||||||
|
log "Building test VM ($PLATFORM)..."
|
||||||
|
sync_to_remote
|
||||||
|
|
||||||
|
local out_path
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
out_path="$(nix build ".#packages.x86_64-linux.test-vm" --print-out-paths 2>&1)"
|
||||||
|
echo "$out_path" > "$VM_RESULT_FILE"
|
||||||
|
VM_BIN_DIR="$out_path"
|
||||||
|
else
|
||||||
|
# Build on remote, save path there
|
||||||
|
out_path="$(ssh "$REMOTE" "cd '$REMOTE_DIR' && nix build .#packages.x86_64-linux.test-vm --print-out-paths 2>&1")"
|
||||||
|
ssh "$REMOTE" "echo '$out_path' > $VM_RESULT_FILE"
|
||||||
|
VM_BIN_DIR="$out_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ok "VM built: $VM_BIN_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_start() {
|
||||||
|
if ! resolve_vm_bin 2>/dev/null; then
|
||||||
|
err "VM not built yet. Run: $0 --build"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if vm_is_running; then
|
||||||
|
warn "VM already running (PID $(cat /tmp/radiantwave-test-vm.pid 2>/dev/null || echo '?'))"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local vm_script="$VM_BIN_DIR/bin/run-radiantwave-test-vm"
|
||||||
|
log "Starting VM in background..."
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
# Run locally with serial to a log file
|
||||||
|
nohup "$vm_script" \
|
||||||
|
-nographic \
|
||||||
|
-serial file:/tmp/radiantwave-test-vm-serial.log \
|
||||||
|
> /tmp/radiantwave-test-vm.log 2>&1 &
|
||||||
|
echo $! > /tmp/radiantwave-test-vm.pid
|
||||||
|
ok "VM started (PID $!)"
|
||||||
|
else
|
||||||
|
# Run on remote server — use nohup so the VM survives SSH disconnect
|
||||||
|
ssh "$REMOTE" "cd '$REMOTE_DIR' && \
|
||||||
|
nohup $vm_script -nographic \
|
||||||
|
-serial file:/tmp/radiantwave-test-vm-serial.log \
|
||||||
|
> /tmp/radiantwave-test-vm.log 2>&1 & \
|
||||||
|
echo \$! > /tmp/radiantwave-test-vm.pid && \
|
||||||
|
echo \"VM started on remote (PID \$(cat /tmp/radiantwave-test-vm.pid))\""
|
||||||
|
ok "VM started on remote server"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_stop() {
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
if [[ -f /tmp/radiantwave-test-vm.pid ]]; then
|
||||||
|
local pid
|
||||||
|
pid="$(cat /tmp/radiantwave-test-vm.pid)"
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
log "Stopping VM (PID $pid)..."
|
||||||
|
kill "$pid"
|
||||||
|
rm -f /tmp/radiantwave-test-vm.pid
|
||||||
|
ok "VM stopped"
|
||||||
|
else
|
||||||
|
warn "VM process $pid not running"
|
||||||
|
rm -f /tmp/radiantwave-test-vm.pid
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "No PID file found. VM may not be running."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "Stopping VM on remote server..."
|
||||||
|
ssh "$REMOTE" "
|
||||||
|
if [[ -f /tmp/radiantwave-test-vm.pid ]]; then
|
||||||
|
pid=\$(cat /tmp/radiantwave-test-vm.pid)
|
||||||
|
kill \$pid 2>/dev/null && echo 'VM stopped' || echo 'VM not running'
|
||||||
|
rm -f /tmp/radiantwave-test-vm.pid
|
||||||
|
else
|
||||||
|
echo 'No PID file found'
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_is_running() {
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
[[ -f /tmp/radiantwave-test-vm.pid ]] && \
|
||||||
|
kill -0 "$(cat /tmp/radiantwave-test-vm.pid)" 2>/dev/null
|
||||||
|
else
|
||||||
|
ssh "$REMOTE" "
|
||||||
|
[[ -f /tmp/radiantwave-test-vm.pid ]] && \
|
||||||
|
kill -0 \$(cat /tmp/radiantwave-test-vm.pid) 2>/dev/null
|
||||||
|
" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_ssh() {
|
||||||
|
# Run a command in the VM via SSH. Usage: vm_ssh "command"
|
||||||
|
local cmd="$1"
|
||||||
|
local ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
eval "$SSPASS -p '$VM_PASS' ssh $ssh_opts -p '$SSH_PORT' '${VM_USER}@localhost' '$cmd'"
|
||||||
|
else
|
||||||
|
# On macOS: find sshpass on the remote server, then SSH to the VM
|
||||||
|
ssh "$REMOTE" "
|
||||||
|
SSPASS=\$(command -v sshpass 2>/dev/null || nix-shell -p sshpass --run 'which sshpass' 2>/dev/null)
|
||||||
|
if [[ -z \"\$SSPASS\" ]]; then
|
||||||
|
echo 'sshpass not found on remote server' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
\$SSPASS -p '$VM_PASS' ssh $ssh_opts -p $SSH_PORT ${VM_USER}@localhost '$cmd'
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_wait_for_boot() {
|
||||||
|
local max_wait=90
|
||||||
|
local waited=0
|
||||||
|
|
||||||
|
log "Waiting for VM to boot (up to ${max_wait}s)..."
|
||||||
|
|
||||||
|
while (( waited < max_wait )); do
|
||||||
|
if vm_ssh "echo ok" &>/dev/null; then
|
||||||
|
ok "VM ready after ${waited}s"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
waited=$((waited + 2))
|
||||||
|
echo -ne "\r ${DIM}${waited}s / ${max_wait}s${NC}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
err "VM did not become ready within ${max_wait}s"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_diagnose() {
|
||||||
|
hr
|
||||||
|
echo -e "${BOLD} RadiantWave Test VM — Diagnostic Report${NC}"
|
||||||
|
hr
|
||||||
|
|
||||||
|
log "Connecting to VM..."
|
||||||
|
|
||||||
|
# System services
|
||||||
|
echo -e "\n${BOLD}── System Services ──${NC}"
|
||||||
|
vm_ssh "systemctl list-units --type=service --state=running --no-pager | grep -E 'NetworkManager|sshd|seatd|getty|cage|systemd-logind' || true" 2>/dev/null || warn "Could not query system services"
|
||||||
|
|
||||||
|
# User services (PipeWire stack)
|
||||||
|
echo -e "\n${BOLD}── User Services (PipeWire) ──${NC}"
|
||||||
|
vm_ssh "systemctl --user list-units --type=service --state=running --no-pager 2>/dev/null | grep -E 'pipewire|wireplumber|pulse' || echo 'No user services running'" 2>/dev/null || warn "Could not query user services"
|
||||||
|
|
||||||
|
# Audio devices via wpctl
|
||||||
|
echo -e "\n${BOLD}── PipeWire Devices (wpctl status) ──${NC}"
|
||||||
|
vm_ssh "wpctl status 2>/dev/null || echo 'wpctl not available'" 2>/dev/null || warn "Could not query wpctl"
|
||||||
|
|
||||||
|
# ALSA devices
|
||||||
|
echo -e "\n${BOLD}── ALSA Devices (aplay -l) ──${NC}"
|
||||||
|
vm_ssh "aplay -l 2>/dev/null || echo 'No ALSA devices'" 2>/dev/null || warn "Could not query ALSA"
|
||||||
|
|
||||||
|
# PulseAudio sinks
|
||||||
|
echo -e "\n${BOLD}── PulseAudio Sinks (pactl) ──${NC}"
|
||||||
|
vm_ssh "pactl list sinks short 2>/dev/null || echo 'pactl not available'" 2>/dev/null || warn "Could not query PulseAudio"
|
||||||
|
|
||||||
|
# Network devices
|
||||||
|
echo -e "\n${BOLD}── Network Devices ──${NC}"
|
||||||
|
vm_ssh "nmcli device status 2>/dev/null || echo 'nmcli not available'" 2>/dev/null || warn "Could not query network"
|
||||||
|
|
||||||
|
# Running processes
|
||||||
|
echo -e "\n${BOLD}── Key Processes ──${NC}"
|
||||||
|
vm_ssh "ps aux | grep -E 'rhemawave|cage|pipewire|wireplumber|NetworkManager' | grep -v grep || echo 'No matching processes'" 2>/dev/null || warn "Could not query processes"
|
||||||
|
|
||||||
|
# Kiosk session
|
||||||
|
echo -e "\n${BOLD}── Login Sessions ──${NC}"
|
||||||
|
vm_ssh "loginctl list-sessions --no-pager 2>/dev/null || echo 'loginctl not available'" 2>/dev/null || warn "Could not query sessions"
|
||||||
|
|
||||||
|
# App log (last 30 lines)
|
||||||
|
echo -e "\n${BOLD}── App Log (/tmp/kiosk.log, last 30 lines) ──${NC}"
|
||||||
|
vm_ssh "tail -30 /tmp/kiosk.log 2>/dev/null || echo 'No log file yet'" 2>/dev/null || warn "Could not read app log"
|
||||||
|
|
||||||
|
hr
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_shell() {
|
||||||
|
log "Connecting interactive shell to VM..."
|
||||||
|
local ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
eval "$SSPASS -p '$VM_PASS' ssh $ssh_opts -p '$SSH_PORT' '${VM_USER}@localhost'"
|
||||||
|
else
|
||||||
|
log "Connecting via remote server ($REMOTE)..."
|
||||||
|
ssh -t "$REMOTE" "
|
||||||
|
SSPASS=\$(command -v sshpass 2>/dev/null || nix-shell -p sshpass --run 'which sshpass' 2>/dev/null)
|
||||||
|
if [[ -z \"\$SSPASS\" ]]; then
|
||||||
|
echo 'sshpass not found on remote server' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
\$SSPASS -p '$VM_PASS' ssh $ssh_opts -t -p $SSH_PORT ${VM_USER}@localhost
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [COMMAND]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
(none) Build + start + run diagnostics
|
||||||
|
--build Build the test VM only
|
||||||
|
--start Start the VM (must already be built)
|
||||||
|
--stop Stop the running VM
|
||||||
|
--diagnose Run diagnostics on a running VM
|
||||||
|
--shell Interactive SSH shell into the VM
|
||||||
|
|
||||||
|
Platform:
|
||||||
|
macOS (ARM) Builds/runs on remote server: ${REMOTE}
|
||||||
|
Linux (x86_64) Builds/runs locally
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
RADIANTWAVE_REMOTE Remote server (default: nclack@192.168.1.203)
|
||||||
|
RADIANTWAVE_REMOTE_DIR Remote repo path (default: ~/radiantwave)
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
nix Nix package manager
|
||||||
|
sshpass Non-interactive SSH (for diagnostics)
|
||||||
|
Install: brew install sshpass (macOS) or nix-shell -p sshpass
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
check_deps
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
--build)
|
||||||
|
cmd_build
|
||||||
|
;;
|
||||||
|
--start)
|
||||||
|
cmd_start
|
||||||
|
;;
|
||||||
|
--stop)
|
||||||
|
cmd_stop
|
||||||
|
;;
|
||||||
|
--diagnose)
|
||||||
|
if ! vm_is_running; then
|
||||||
|
err "VM is not running. Start it first: $0 --start"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmd_diagnose
|
||||||
|
;;
|
||||||
|
--shell)
|
||||||
|
if ! vm_is_running; then
|
||||||
|
err "VM is not running. Start it first: $0 --start"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cmd_shell
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
cmd_build
|
||||||
|
cmd_start
|
||||||
|
sleep 5
|
||||||
|
if vm_wait_for_boot; then
|
||||||
|
cmd_diagnose
|
||||||
|
else
|
||||||
|
warn "VM boot timed out. Check logs:"
|
||||||
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
|
echo " tail -f /tmp/radiantwave-test-vm.log"
|
||||||
|
echo " tail -f /tmp/radiantwave-test-vm-serial.log"
|
||||||
|
else
|
||||||
|
echo " ssh $REMOTE 'tail -f /tmp/radiantwave-test-vm.log'"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
err "Unknown command: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user