#!/usr/bin/env bash # # qvm-status - Display VM state, configuration, and connection information # # Shows current VM status including: # - Running state (PID, uptime, SSH port) # - Mounted workspaces from workspaces.json # - Cache directory status # - Base image and overlay details # - Connection hints for SSH and run commands # # Exit codes: # 0 - VM is running # 1 - VM is stopped set -euo pipefail # Source common library for shared functions and constants readonly QVM_LIB_DIR="${QVM_LIB_DIR:-$(dirname "$0")/../lib}" source "${QVM_LIB_DIR}/common.sh" # Additional color codes for status display if [[ -t 1 ]]; then readonly COLOR_SUCCESS='\033[0;32m' # Green readonly COLOR_HEADER='\033[1;37m' # Bold White readonly COLOR_DIM='\033[0;90m' # Dim Gray else readonly COLOR_SUCCESS='' readonly COLOR_HEADER='' readonly COLOR_DIM='' fi # # format_bytes - Convert bytes to human-readable format # Args: $1 - size in bytes # Returns: formatted string (e.g., "1.5G", "256M", "4.0K") # format_bytes() { local bytes="$1" if (( bytes >= 1073741824 )); then printf "%.1fG" "$(echo "scale=1; $bytes / 1073741824" | bc)" elif (( bytes >= 1048576 )); then printf "%.1fM" "$(echo "scale=1; $bytes / 1048576" | bc)" elif (( bytes >= 1024 )); then printf "%.1fK" "$(echo "scale=1; $bytes / 1024" | bc)" else printf "%dB" "$bytes" fi } # # get_uptime - Calculate VM uptime from PID # Args: $1 - process PID # Returns: uptime string (e.g., "2h 15m", "45m", "30s") # get_uptime() { local pid="$1" # Get process start time in seconds since epoch local start_time start_time=$(ps -p "$pid" -o lstart= 2>/dev/null | xargs -I{} date -d "{}" +%s) if [[ -z "$start_time" ]]; then echo "unknown" return fi local current_time current_time=$(date +%s) local uptime_seconds=$((current_time - start_time)) # Format uptime local hours=$((uptime_seconds / 3600)) local minutes=$(( (uptime_seconds % 3600) / 60 )) local seconds=$((uptime_seconds % 60)) if (( hours > 0 )); then printf "%dh %dm" "$hours" "$minutes" elif (( minutes > 0 )); then printf "%dm %ds" "$minutes" "$seconds" else printf "%ds" "$seconds" fi } # # show_file_info - Display file status with size and modification time # Args: $1 - file path # $2 - label (e.g., "Base Image") # show_file_info() { local file="$1" local label="$2" if [[ -f "$file" ]]; then local size_bytes size_bytes=$(stat -c %s "$file" 2>/dev/null || echo "0") local size_human size_human=$(format_bytes "$size_bytes") local mod_time mod_time=$(stat -c %y "$file" 2>/dev/null | cut -d. -f1) echo -e " ${COLOR_SUCCESS}✓${COLOR_RESET} $label: ${COLOR_INFO}$size_human${COLOR_RESET} ${COLOR_DIM}(modified: $mod_time)${COLOR_RESET}" else echo -e " ${COLOR_WARN}✗${COLOR_RESET} $label: ${COLOR_WARN}missing${COLOR_RESET}" fi } # # show_dir_info - Display directory status # Args: $1 - directory path # $2 - label (e.g., "Cargo Home") # show_dir_info() { local dir="$1" local label="$2" if [[ -d "$dir" ]]; then echo -e " ${COLOR_SUCCESS}✓${COLOR_RESET} $label: ${COLOR_DIM}$dir${COLOR_RESET}" else echo -e " ${COLOR_WARN}✗${COLOR_RESET} $label: ${COLOR_WARN}not created${COLOR_RESET}" fi } # # show_workspaces - Display mounted workspaces from workspaces.json # show_workspaces() { if [[ ! -f "$QVM_WORKSPACES_FILE" ]]; then echo -e " ${COLOR_DIM}No workspaces mounted${COLOR_RESET}" return fi # Check if file is valid JSON and has workspaces local workspace_count workspace_count=$(jq 'length' "$QVM_WORKSPACES_FILE" 2>/dev/null || echo "0") if (( workspace_count == 0 )); then echo -e " ${COLOR_DIM}No workspaces mounted${COLOR_RESET}" return fi # Parse and display each workspace jq -r 'to_entries[] | "\(.key)|\(.value.host_path)|\(.value.guest_path)"' "$QVM_WORKSPACES_FILE" 2>/dev/null | while IFS='|' read -r hash host_path guest_path; do echo -e " ${COLOR_SUCCESS}✓${COLOR_RESET} $hash: ${COLOR_INFO}$host_path${COLOR_RESET} → ${COLOR_DIM}$guest_path${COLOR_RESET}" done } # # main - Main status display logic # main() { # Header echo -e "${COLOR_HEADER}QVM Status${COLOR_RESET}" echo "" # VM State echo -e "${COLOR_HEADER}VM State:${COLOR_RESET}" if is_vm_running; then local pid pid=$(cat "$QVM_PID_FILE") local ssh_port if [[ -f "$QVM_SSH_PORT_FILE" ]]; then ssh_port=$(cat "$QVM_SSH_PORT_FILE") else ssh_port="unknown" fi local uptime uptime=$(get_uptime "$pid") echo -e " ${COLOR_SUCCESS}✓${COLOR_RESET} Running" echo -e " ${COLOR_DIM}PID:${COLOR_RESET} $pid" echo -e " ${COLOR_DIM}SSH:${COLOR_RESET} localhost:$ssh_port" echo -e " ${COLOR_DIM}Uptime:${COLOR_RESET} $uptime" else echo -e " ${COLOR_WARN}✗${COLOR_RESET} Stopped" fi echo "" # Workspaces echo -e "${COLOR_HEADER}Mounted Workspaces:${COLOR_RESET}" show_workspaces echo "" # Cache Directories echo -e "${COLOR_HEADER}Cache Directories:${COLOR_RESET}" show_dir_info "$QVM_CARGO_HOME" "Cargo Home" show_dir_info "$QVM_CARGO_TARGET" "Cargo Target" show_dir_info "$QVM_PNPM_STORE" "PNPM Store" show_dir_info "$QVM_SCCACHE" "SCCache" echo "" # VM Images echo -e "${COLOR_HEADER}VM Images:${COLOR_RESET}" show_file_info "$QVM_BASE_IMAGE" "Base Image" show_file_info "$QVM_OVERLAY" "Overlay" echo "" # Connection Hints (only if VM is running) if is_vm_running; then local ssh_port ssh_port=$(cat "$QVM_SSH_PORT_FILE" 2>/dev/null || echo "unknown") echo -e "${COLOR_HEADER}Connection:${COLOR_RESET}" echo -e " ${COLOR_INFO}SSH:${COLOR_RESET} qvm ssh" echo -e " ${COLOR_INFO}Run cmd:${COLOR_RESET} qvm run " echo -e " ${COLOR_INFO}Direct:${COLOR_RESET} ssh -p $ssh_port root@localhost" echo "" # Exit success if running exit 0 else echo -e "${COLOR_HEADER}Quick Start:${COLOR_RESET}" echo -e " ${COLOR_INFO}Start VM:${COLOR_RESET} qvm start" echo "" # Exit failure if stopped exit 1 fi } # Execute main main "$@"