#!/usr/bin/env bash # # common.sh - Shared functions and configuration for QVM CLI tool # # This file defines XDG-compliant directory paths, constants, and utility # functions used across all qvm-* commands. It should be sourced by each # command script via: source "${QVM_LIB_DIR}/common.sh" # set -euo pipefail # XDG-compliant directory paths readonly QVM_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/qvm" readonly QVM_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/qvm" readonly QVM_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/qvm" readonly QVM_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/qvm" # Path constants for VM artifacts readonly QVM_BASE_IMAGE="$QVM_DATA_DIR/base.qcow2" readonly QVM_OVERLAY="$QVM_STATE_DIR/overlay.qcow2" readonly QVM_PID_FILE="$QVM_STATE_DIR/vm.pid" 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" readonly QVM_CONFIG_FILE="$QVM_CONFIG_DIR/qvm.conf" # Cache directories for 9p mounts (shared between host and VM) readonly QVM_CARGO_HOME="$QVM_CACHE_DIR/cargo-home" 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 else readonly COLOR_INFO='' readonly COLOR_WARN='' readonly COLOR_ERROR='' readonly COLOR_RESET='' fi # # log_info - Print informational message in cyan # Usage: log_info "message" # log_info() { echo -e "${COLOR_INFO}[INFO]${COLOR_RESET} $*" >&2 } # # log_warn - Print warning message in yellow # Usage: log_warn "message" # log_warn() { echo -e "${COLOR_WARN}[WARN]${COLOR_RESET} $*" >&2 } # # log_error - Print error message in red # Usage: log_error "message" # log_error() { echo -e "${COLOR_ERROR}[ERROR]${COLOR_RESET} $*" >&2 } # # die - Print error message and exit with status 1 # Usage: die "error message" # die() { log_error "$@" exit 1 } # # ensure_dirs - Create all required QVM directories # 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" } # # is_vm_running - Check if VM process is running # Returns: 0 if running, 1 if not # 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 } # # get_ssh_port - Read SSH port from state file # Returns: SSH port number on stdout # 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" } # # workspace_hash - Generate short hash from absolute path # Args: $1 - absolute path to workspace # Returns: 8-character hash on stdout # Usage: hash=$(workspace_hash "/path/to/workspace") # workspace_hash() { local path="$1" echo -n "$path" | sha256sum | cut -c1-8 } # # wait_for_ssh - Wait for SSH to become available on VM # Args: $1 - SSH port number # $2 - timeout in seconds (default: 60) # Returns: 0 if SSH is available, 1 on timeout # 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 # 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 }