Improve VM lifecycle tooling

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.
This commit is contained in:
Thales Maciel 2026-03-15 17:48:47 -03:00
parent a8078f2393
commit 9191b7e370
No known key found for this signature in database
GPG key ID: 33112E6833C34679
11 changed files with 966 additions and 144 deletions

92
kill.sh
View file

@ -5,9 +5,14 @@ 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|...]
Usage: ./kill.sh <id-or-name-prefix>... [--signal SIGTERM|SIGKILL|...]
Sends a signal to the Firecracker process.
EOF
@ -17,7 +22,7 @@ find_vm_json() {
local query="$1"
local vm_json match_count=0 match=""
for vm_json in state/vms/*/vm.json; do
for vm_json in "$VM_ROOT"/*/vm.json; do
[[ -f "$vm_json" ]] || continue
local id name
id="$(jq -r '.meta.id // empty' "$vm_json")"
@ -41,6 +46,7 @@ find_vm_json() {
}
SIGNAL="TERM"
QUERIES=()
while [[ $# -gt 0 ]]; do
case "$1" in
--signal)
@ -52,40 +58,68 @@ while [[ $# -gt 0 ]]; do
exit 0
;;
*)
if [[ -z "${QUERY:-}" ]]; then
QUERY="$1"
shift
continue
fi
log "unknown option: $1"
usage
exit 1
QUERIES+=("$1")
shift
;;
esac
done
if [[ -z "$QUERY" || "$QUERY" == "-h" || "$QUERY" == "--help" ]]; then
if (( ${#QUERIES[@]} == 0 )); then
usage
exit 1
fi
VM_JSON="$(find_vm_json "$QUERY")"
PID="$(jq -r '.meta.pid // empty' "$VM_JSON")"
API_SOCK="$(jq -r '.meta.api_sock // empty' "$VM_JSON")"
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
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
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"
exit 1
fi
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
log "sending SIG$SIGNAL to pid $PID"
sudo kill "-$SIGNAL" "$PID"
log "signal sent"
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