Make spawned VMs easier to use and restore from the host. Add shared DNS and runtime helpers, publish <vm-name>.vm records through mapdns, and teach run/customize/interactive/restore to persist the metadata needed for SSH, DNS cleanup, and clean restores. Seed per-VM /home and /var disks from the rootfs snapshot so package state is present on first boot, add an interactive customization entrypoint plus ssh.sh and human-friendly list output, and let stop/kill/rm operate on multiple VM identifiers. Tear down stale TAP, dm, and loop state when VMs stop so restore can recreate them safely, and validate the updated scripts with bash -n plus targeted dry-run harnesses for teardown and restore paths.
125 lines
3 KiB
Bash
Executable file
125 lines
3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
log() {
|
|
printf '[kill] %s\n' "$*"
|
|
}
|
|
|
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$DIR/dns.sh"
|
|
STATE="$DIR/state"
|
|
VM_ROOT="$STATE/vms"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: ./kill.sh <id-or-name-prefix>... [--signal SIGTERM|SIGKILL|...]
|
|
|
|
Sends a signal to the Firecracker process.
|
|
EOF
|
|
}
|
|
|
|
find_vm_json() {
|
|
local query="$1"
|
|
local vm_json match_count=0 match=""
|
|
|
|
for vm_json in "$VM_ROOT"/*/vm.json; do
|
|
[[ -f "$vm_json" ]] || continue
|
|
local id name
|
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
|
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
|
match="$vm_json"
|
|
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"
|
|
QUERIES=()
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--signal)
|
|
SIGNAL="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
QUERIES+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if (( ${#QUERIES[@]} == 0 )); then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
VM_JSONS=()
|
|
for query in "${QUERIES[@]}"; do
|
|
VM_JSON="$(find_vm_json "$query")"
|
|
already_added=0
|
|
for existing_vm_json in "${VM_JSONS[@]}"; do
|
|
if [[ "$existing_vm_json" == "$VM_JSON" ]]; then
|
|
already_added=1
|
|
break
|
|
fi
|
|
done
|
|
if (( already_added == 0 )); then
|
|
VM_JSONS+=("$VM_JSON")
|
|
fi
|
|
done
|
|
|
|
for vm_json in "${VM_JSONS[@]}"; do
|
|
vm_id="$(jq -r '.meta.id // empty' "$vm_json")"
|
|
vm_name="$(jq -r '.meta.name // empty' "$vm_json")"
|
|
pid="$(jq -r '.meta.pid // empty' "$vm_json")"
|
|
tap="$(jq -r '.meta.tap // empty' "$vm_json")"
|
|
api_sock="$(jq -r '.meta.api_sock // empty' "$vm_json")"
|
|
base_loop="$(jq -r '.meta.base_loop // empty' "$vm_json")"
|
|
cow_loop="$(jq -r '.meta.cow_loop // empty' "$vm_json")"
|
|
dm_dev="$(jq -r '.meta.dm_dev // empty' "$vm_json")"
|
|
dm_name="$(jq -r '.meta.dm_name // empty' "$vm_json")"
|
|
dns_name="$(jq -r '.meta.dns_name // empty' "$vm_json")"
|
|
if [[ -z "$dns_name" ]]; then
|
|
dns_name="$(banger_dns_name "$vm_name")"
|
|
fi
|
|
if [[ -z "$pid" ]]; then
|
|
log "pid not found in $vm_json"
|
|
exit 1
|
|
fi
|
|
if [[ -z "$api_sock" ]]; then
|
|
log "api_sock not found in $vm_json"
|
|
exit 1
|
|
fi
|
|
|
|
if ! ps -p "$pid" -o comm=,args= 2>/dev/null | rg -q "firecracker.*--api-sock $api_sock"; then
|
|
log "pid $pid does not match a running VM for ${vm_name:-$vm_json}"
|
|
exit 1
|
|
fi
|
|
|
|
log "sending SIG$SIGNAL to ${vm_name:-$vm_json} (pid $pid)"
|
|
sudo kill "-$SIGNAL" "$pid"
|
|
if ! banger_wait_for_vm_exit "$pid" "$api_sock" 30; then
|
|
log "timed out waiting for ${vm_name:-$vm_json} to exit"
|
|
exit 1
|
|
fi
|
|
banger_teardown_vm_runtime "$tap" "$api_sock" "$dm_name" "$dm_dev" "$cow_loop" "$base_loop"
|
|
banger_mark_vm_stopped "$vm_json"
|
|
banger_dns_remove_record_name "$dns_name"
|
|
log "signal sent to ${vm_name:-$vm_json}"
|
|
done
|