diff --git a/kill.sh b/kill.sh new file mode 100755 index 0000000..0ced0bc --- /dev/null +++ b/kill.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { + printf '[kill] %s\n' "$*" +} + +usage() { + cat <<'EOF' +Usage: ./kill.sh [--signal SIGTERM|SIGKILL|...] + +Sends a signal to the Firecracker process. +EOF +} + +get_prop() { + local info="$1" + local key="$2" + awk -F= -v k="$key" '$1==k {print $2}' "$info" +} + +find_vm_info() { + local query="$1" + local info match_count=0 match="" + + for info in state/vm-*/info; do + [[ -f "$info" ]] || continue + local id name + id="$(get_prop "$info" "id")" + name="$(get_prop "$info" "name")" + if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then + match="$info" + match_count=$((match_count + 1)) + fi + done + + if (( match_count == 0 )); then + log "no VM found for prefix: $query" + exit 1 + fi + if (( match_count > 1 )); then + log "multiple VMs found for prefix: $query" + exit 1 + fi + + printf '%s' "$match" +} + +SIGNAL="TERM" +while [[ $# -gt 0 ]]; do + case "$1" in + --signal) + SIGNAL="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + if [[ -z "${QUERY:-}" ]]; then + QUERY="$1" + shift + continue + fi + log "unknown option: $1" + usage + exit 1 + ;; + esac +done + +if [[ -z "$QUERY" || "$QUERY" == "-h" || "$QUERY" == "--help" ]]; then + usage + exit 1 +fi + +INFO_FILE="$(find_vm_info "$QUERY")" +PID="$(get_prop "$INFO_FILE" "pid")" +if [[ -z "$PID" ]]; then + log "pid not found in $INFO_FILE" + exit 1 +fi + +log "sending SIG$SIGNAL to pid $PID" +sudo kill "-$SIGNAL" "$PID" +log "signal sent" diff --git a/list.sh b/list.sh index d3df0f2..8f73d22 100755 --- a/list.sh +++ b/list.sh @@ -3,14 +3,20 @@ set -euo pipefail shopt -s nullglob +get_prop() { + local info="$1" + local key="$2" + awk -F= -v k="$key" '$1==k {print $2}' "$info" +} + for info in state/vm-*/info; do - id="$(awk -F= '$1=="id"{print $2}' "$info")" - name="$(awk -F= '$1=="name"{print $2}' "$info")" - created_at="$(awk -F= '$1=="created_at"{print $2}' "$info")" - guest_ip="$(awk -F= '$1=="guest_ip"{print $2}' "$info")" - pid="$(awk -F= '$1=="pid"{print $2}' "$info")" - tap="$(awk -F= '$1=="tap"{print $2}' "$info")" - api_sock="$(awk -F= '$1=="api_sock"{print $2}' "$info")" + id="$(get_prop "$info" "id")" + name="$(get_prop "$info" "name")" + created_at="$(get_prop "$info" "created_at")" + guest_ip="$(get_prop "$info" "guest_ip")" + pid="$(get_prop "$info" "pid")" + tap="$(get_prop "$info" "tap")" + api_sock="$(get_prop "$info" "api_sock")" status="stale" if [[ -n "$pid" && -n "$api_sock" ]]; then diff --git a/ps.sh b/ps.sh index bc13c8b..aad6d59 100755 --- a/ps.sh +++ b/ps.sh @@ -3,14 +3,20 @@ set -euo pipefail shopt -s nullglob +get_prop() { + local info="$1" + local key="$2" + awk -F= -v k="$key" '$1==k {print $2}' "$info" +} + for info in state/vm-*/info; do - id="$(awk -F= '$1=="id"{print $2}' "$info")" - name="$(awk -F= '$1=="name"{print $2}' "$info")" - created_at="$(awk -F= '$1=="created_at"{print $2}' "$info")" - guest_ip="$(awk -F= '$1=="guest_ip"{print $2}' "$info")" - pid="$(awk -F= '$1=="pid"{print $2}' "$info")" - tap="$(awk -F= '$1=="tap"{print $2}' "$info")" - api_sock="$(awk -F= '$1=="api_sock"{print $2}' "$info")" + id="$(get_prop "$info" "id")" + name="$(get_prop "$info" "name")" + created_at="$(get_prop "$info" "created_at")" + guest_ip="$(get_prop "$info" "guest_ip")" + pid="$(get_prop "$info" "pid")" + tap="$(get_prop "$info" "tap")" + api_sock="$(get_prop "$info" "api_sock")" if [[ -z "$pid" || -z "$api_sock" ]]; then continue diff --git a/rm.sh b/rm.sh new file mode 100755 index 0000000..84630e6 --- /dev/null +++ b/rm.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { + printf '[rm] %s\n' "$*" +} + +usage() { + cat <<'EOF' +Usage: ./rm.sh + +Removes VM artifacts from state/ and cleans up TAP and mapped devices. +EOF +} + +get_prop() { + local info="$1" + local key="$2" + awk -F= -v k="$key" '$1==k {print $2}' "$info" +} + +find_vm_info() { + local query="$1" + local info match_count=0 match="" + + for info in state/vm-*/info; do + [[ -f "$info" ]] || continue + local id name + id="$(get_prop "$info" "id")" + name="$(get_prop "$info" "name")" + if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then + match="$info" + match_count=$((match_count + 1)) + fi + done + + if (( match_count == 0 )); then + log "no VM found for prefix: $query" + exit 1 + fi + if (( match_count > 1 )); then + log "multiple VMs found for prefix: $query" + exit 1 + fi + + printf '%s' "$match" +} + +QUERY="${1:-}" +if [[ -z "$QUERY" || "$QUERY" == "-h" || "$QUERY" == "--help" ]]; then + usage + exit 1 +fi + +INFO_FILE="$(find_vm_info "$QUERY")" +VM_DIR="${INFO_FILE%/info}" +PID="$(get_prop "$INFO_FILE" "pid")" +TAP="$(get_prop "$INFO_FILE" "tap")" +API_SOCK="$(get_prop "$INFO_FILE" "api_sock")" +BASE_LOOP="$(get_prop "$INFO_FILE" "base_loop")" +COW_LOOP="$(get_prop "$INFO_FILE" "cow_loop")" +DM_DEV="$(get_prop "$INFO_FILE" "dm_dev")" +DM_NAME="$(get_prop "$INFO_FILE" "dm_name")" + +if [[ -n "$PID" ]]; then + sudo kill "$PID" 2>/dev/null || true +fi +if [[ -n "$TAP" ]]; then + sudo ip link del "$TAP" 2>/dev/null || true +fi +if [[ -n "$API_SOCK" ]]; then + rm -f "$API_SOCK" +fi +if [[ -n "$DM_DEV" || -n "$DM_NAME" ]]; then + sudo dmsetup remove "${DM_NAME:-$DM_DEV}" 2>/dev/null || true +fi +if [[ -n "$COW_LOOP" ]]; then + sudo losetup -d "$COW_LOOP" 2>/dev/null || true +fi +if [[ -n "$BASE_LOOP" ]]; then + sudo losetup -d "$BASE_LOOP" 2>/dev/null || true +fi +if [[ -d "$VM_DIR" ]]; then + rm -rf "$VM_DIR" +fi + +log "removed $VM_DIR" diff --git a/run.sh b/run.sh index 4a0694c..9832eab 100755 --- a/run.sh +++ b/run.sh @@ -36,7 +36,9 @@ CIDR="24" DEFAULT_VCPU=2 DEFAULT_RAM=1024 +MIN_VCPU=1 MAX_VCPU=16 +MIN_RAM=256 MAX_RAM=32768 MAX_DISK_BYTES=$((128 * 1024 * 1024 * 1024)) @@ -108,7 +110,7 @@ if ! [[ "$VCPU_COUNT" =~ ^[0-9]+$ ]]; then log "invalid --vcpu value: $VCPU_COUNT" exit 1 fi -if (( VCPU_COUNT < 1 || VCPU_COUNT > MAX_VCPU )); then +if (( VCPU_COUNT < MIN_VCPU || VCPU_COUNT > MAX_VCPU )); then log "vcpu must be between 1 and $MAX_VCPU" exit 1 fi @@ -117,7 +119,7 @@ if ! [[ "$RAM_MIB" =~ ^[0-9]+$ ]]; then log "invalid --ram value: $RAM_MIB" exit 1 fi -if (( RAM_MIB < 256 || RAM_MIB > MAX_RAM )); then +if (( RAM_MIB < MIN_RAM || RAM_MIB > MAX_RAM )); then log "ram must be between 256 and $MAX_RAM MiB" exit 1 fi diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..c5b32b9 --- /dev/null +++ b/stop.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { + printf '[stop] %s\n' "$*" +} + +usage() { + cat <<'EOF' +Usage: ./stop.sh + +Sends Ctrl+Alt+Del to the guest via the Firecracker API socket. +EOF +} + +get_prop() { + local info="$1" + local key="$2" + awk -F= -v k="$key" '$1==k {print $2}' "$info" +} + +find_vm_info() { + local query="$1" + local info match_count=0 match="" + + for info in state/vm-*/info; do + [[ -f "$info" ]] || continue + local id name + id="$(get_prop "$info" "id")" + name="$(get_prop "$info" "name")" + if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then + match="$info" + match_count=$((match_count + 1)) + fi + done + + if (( match_count == 0 )); then + log "no VM found for prefix: $query" + exit 1 + fi + if (( match_count > 1 )); then + log "multiple VMs found for prefix: $query" + exit 1 + fi + + printf '%s' "$match" +} + +QUERY="${1:-}" +if [[ -z "$QUERY" || "$QUERY" == "-h" || "$QUERY" == "--help" ]]; then + usage + exit 1 +fi + +INFO_FILE="$(find_vm_info "$QUERY")" +API_SOCK="$(get_prop "$INFO_FILE" "api_sock")" +if [[ -z "$API_SOCK" || ! -S "$API_SOCK" ]]; then + log "api socket not found: $API_SOCK" + exit 1 +fi + +log "sending Ctrl+Alt+Del to guest" +sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/actions \ + -H "Content-Type: application/json" \ + -d '{ "action_type": "SendCtrlAltDel" }' >/dev/null +log "requested shutdown"