Add initial QVM CLI, Nix flake, scripts and README
This commit is contained in:
parent
25b1cca0e6
commit
8534f7efb9
14 changed files with 2359 additions and 0 deletions
228
bin/qvm-status
Executable file
228
bin/qvm-status
Executable file
|
|
@ -0,0 +1,228 @@
|
|||
#!/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 <command>"
|
||||
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 "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue