Add qvm clean; make rebuild produce VM runner; default qvm run to shell

This commit is contained in:
Joshua Bell 2026-01-26 10:14:23 -06:00
parent e766c8466d
commit 601b4ab15e
7 changed files with 395 additions and 249 deletions

View file

@ -23,6 +23,7 @@ readonly QVM_SSH_PORT_FILE="$QVM_STATE_DIR/ssh.port"
readonly QVM_SERIAL_LOG="$QVM_STATE_DIR/serial.log"
readonly QVM_WORKSPACES_FILE="$QVM_STATE_DIR/workspaces.json"
readonly QVM_USER_FLAKE="$QVM_CONFIG_DIR/flake"
readonly QVM_VM_RUNNER="$QVM_DATA_DIR/run-vm"
# Cache directories for 9p mounts (shared between host and VM)
readonly QVM_CARGO_HOME="$QVM_CACHE_DIR/cargo-home"
@ -30,17 +31,20 @@ readonly QVM_CARGO_TARGET="$QVM_CACHE_DIR/cargo-target"
readonly QVM_PNPM_STORE="$QVM_CACHE_DIR/pnpm-store"
readonly QVM_SCCACHE="$QVM_CACHE_DIR/sccache"
# Host config directories to mount in VM (read-write for tools that need it)
readonly QVM_HOST_OPENCODE_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
# Color codes (only used if stdout is a TTY)
if [[ -t 1 ]]; then
readonly COLOR_INFO='\033[0;36m' # Cyan
readonly COLOR_WARN='\033[0;33m' # Yellow
readonly COLOR_ERROR='\033[0;31m' # Red
readonly COLOR_RESET='\033[0m' # Reset
readonly COLOR_INFO='\033[0;36m' # Cyan
readonly COLOR_WARN='\033[0;33m' # Yellow
readonly COLOR_ERROR='\033[0;31m' # Red
readonly COLOR_RESET='\033[0m' # Reset
else
readonly COLOR_INFO=''
readonly COLOR_WARN=''
readonly COLOR_ERROR=''
readonly COLOR_RESET=''
readonly COLOR_INFO=''
readonly COLOR_WARN=''
readonly COLOR_ERROR=''
readonly COLOR_RESET=''
fi
#
@ -48,7 +52,7 @@ fi
# Usage: log_info "message"
#
log_info() {
echo -e "${COLOR_INFO}[INFO]${COLOR_RESET} $*" >&2
echo -e "${COLOR_INFO}[INFO]${COLOR_RESET} $*" >&2
}
#
@ -56,7 +60,7 @@ log_info() {
# Usage: log_warn "message"
#
log_warn() {
echo -e "${COLOR_WARN}[WARN]${COLOR_RESET} $*" >&2
echo -e "${COLOR_WARN}[WARN]${COLOR_RESET} $*" >&2
}
#
@ -64,7 +68,7 @@ log_warn() {
# Usage: log_error "message"
#
log_error() {
echo -e "${COLOR_ERROR}[ERROR]${COLOR_RESET} $*" >&2
echo -e "${COLOR_ERROR}[ERROR]${COLOR_RESET} $*" >&2
}
#
@ -72,8 +76,8 @@ log_error() {
# Usage: die "error message"
#
die() {
log_error "$@"
exit 1
log_error "$@"
exit 1
}
#
@ -81,14 +85,14 @@ die() {
# Usage: ensure_dirs
#
ensure_dirs() {
mkdir -p "$QVM_DATA_DIR" \
"$QVM_STATE_DIR" \
"$QVM_CACHE_DIR" \
"$QVM_CONFIG_DIR" \
"$QVM_CARGO_HOME" \
"$QVM_CARGO_TARGET" \
"$QVM_PNPM_STORE" \
"$QVM_SCCACHE"
mkdir -p "$QVM_DATA_DIR" \
"$QVM_STATE_DIR" \
"$QVM_CACHE_DIR" \
"$QVM_CONFIG_DIR" \
"$QVM_CARGO_HOME" \
"$QVM_CARGO_TARGET" \
"$QVM_PNPM_STORE" \
"$QVM_SCCACHE"
}
#
@ -97,21 +101,21 @@ ensure_dirs() {
# Usage: if is_vm_running; then ... fi
#
is_vm_running() {
if [[ ! -f "$QVM_PID_FILE" ]]; then
return 1
fi
local pid
pid=$(cat "$QVM_PID_FILE")
# Check if process exists and is a QEMU process
if kill -0 "$pid" 2>/dev/null; then
return 0
else
# Stale PID file, remove it
rm -f "$QVM_PID_FILE"
return 1
fi
if [[ ! -f "$QVM_PID_FILE" ]]; then
return 1
fi
local pid
pid=$(cat "$QVM_PID_FILE")
# Check if process exists and is a QEMU process
if kill -0 "$pid" 2>/dev/null; then
return 0
else
# Stale PID file, remove it
rm -f "$QVM_PID_FILE"
return 1
fi
}
#
@ -120,10 +124,10 @@ is_vm_running() {
# Usage: port=$(get_ssh_port)
#
get_ssh_port() {
if [[ ! -f "$QVM_SSH_PORT_FILE" ]]; then
die "SSH port file not found. Is the VM running?"
fi
cat "$QVM_SSH_PORT_FILE"
if [[ ! -f "$QVM_SSH_PORT_FILE" ]]; then
die "SSH port file not found. Is the VM running?"
fi
cat "$QVM_SSH_PORT_FILE"
}
#
@ -133,8 +137,8 @@ get_ssh_port() {
# Usage: hash=$(workspace_hash "/path/to/workspace")
#
workspace_hash() {
local path="$1"
echo -n "$path" | sha256sum | cut -c1-8
local path="$1"
echo -n "$path" | sha256sum | cut -c1-8
}
#
@ -145,25 +149,36 @@ workspace_hash() {
# Usage: wait_for_ssh "$port" 30
#
wait_for_ssh() {
local port="${1:-}"
local timeout="${2:-60}"
local elapsed=0
if [[ -z "$port" ]]; then
die "wait_for_ssh requires port argument"
fi
log_info "Waiting for SSH on port $port (timeout: ${timeout}s)..."
while (( elapsed < timeout )); do
if nc -z -w 1 localhost "$port" 2>/dev/null; then
log_info "SSH is ready"
return 0
fi
sleep 1
(( elapsed++ ))
done
log_error "SSH did not become available within ${timeout}s"
return 1
local port="${1:-}"
local timeout="${2:-60}"
local elapsed=0
if [[ -z "$port" ]]; then
die "wait_for_ssh requires port argument"
fi
log_info "Waiting for SSH on port $port (timeout: ${timeout}s)..."
while ((elapsed < timeout)); do
# Actually attempt SSH connection to verify sshd is responding
# nc -z only checks if port is open (QEMU opens it immediately)
# We need to verify sshd is actually ready to accept connections
if timeout 2 sshpass -p root ssh \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
-o PubkeyAuthentication=no \
-o PasswordAuthentication=yes \
-o ConnectTimeout=1 \
-p "$port" \
root@localhost "true" 2>/dev/null; then
log_info "SSH is ready"
return 0
fi
sleep 1
((elapsed++))
done
log_error "SSH did not become available within ${timeout}s"
return 1
}