Store VM metadata as JSON
This commit is contained in:
parent
bbd57d8dd2
commit
7af04b7535
11 changed files with 188 additions and 178 deletions
21
README.md
21
README.md
|
|
@ -4,7 +4,7 @@ Minimal Firecracker launcher.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- Linux host with KVM (`/dev/kvm` access)
|
- Linux host with KVM (`/dev/kvm` access)
|
||||||
- `sudo`, `ip`, `curl`, `ssh`
|
- `sudo`, `ip`, `curl`, `ssh`, `jq`
|
||||||
- `dmsetup`, `losetup`, `blockdev` (device-mapper snapshot for rootfs)
|
- `dmsetup`, `losetup`, `blockdev` (device-mapper snapshot for rootfs)
|
||||||
- `e2cp`, `e2rm` (writes hostname and resolv.conf into rootfs snapshot)
|
- `e2cp`, `e2rm` (writes hostname and resolv.conf into rootfs snapshot)
|
||||||
|
|
||||||
|
|
@ -63,22 +63,9 @@ reboot
|
||||||
```
|
```
|
||||||
|
|
||||||
## VM Info File
|
## VM Info File
|
||||||
Each VM writes a metadata file at `state/vms/<id>/info` with the following fields:
|
Each VM writes `state/vms/<id>/vm.json` with:
|
||||||
- `id`: unique identifier for the VM instance.
|
- `meta`: local metadata (id, name, pid, created_at, guest_ip, tap, api_sock, log, rootfs, kernel, snapshot info).
|
||||||
- `name`: VM name.
|
- `config`: full `/vm/config` response from Firecracker.
|
||||||
- `pid`: Firecracker process ID.
|
|
||||||
- `created_at`: timestamp when the VM was launched.
|
|
||||||
- `rootfs`: root filesystem image path used to launch the VM.
|
|
||||||
- `kernel`: kernel image path used to launch the VM.
|
|
||||||
- `guest_ip`: IP address assigned to the guest.
|
|
||||||
- `tap`: host TAP interface name attached to the bridge.
|
|
||||||
- `api_sock`: path to the Firecracker API socket (stored under `$XDG_RUNTIME_DIR/banger/` when available).
|
|
||||||
- `log`: path to the Firecracker log file.
|
|
||||||
- `base_loop`: loop device backing the base rootfs (if present).
|
|
||||||
- `cow_file`: copy-on-write image file (if present).
|
|
||||||
- `cow_loop`: loop device for the COW image (if present).
|
|
||||||
- `dm_name`: device-mapper name for the merged rootfs (if present).
|
|
||||||
- `dm_dev`: device-mapper device path for the merged rootfs (if present).
|
|
||||||
|
|
||||||
## Log Notes
|
## Log Notes
|
||||||
- `PCI: Fatal: No config space access function found` and `MissingAddressRange` lines are expected with `pci=off` in `run.sh`.
|
- `PCI: Fatal: No config space access function found` and `MissingAddressRange` lines are expected with `pci=off` in `run.sh`.
|
||||||
|
|
|
||||||
43
customize.sh
43
customize.sh
|
|
@ -89,6 +89,10 @@ if ! command -v resize2fs >/dev/null 2>&1; then
|
||||||
log "resize2fs required"
|
log "resize2fs required"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
log "jq required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
log "copying base rootfs to $OUT_ROOTFS"
|
log "copying base rootfs to $OUT_ROOTFS"
|
||||||
cp --reflink=auto "$BASE_ROOTFS" "$OUT_ROOTFS"
|
cp --reflink=auto "$BASE_ROOTFS" "$OUT_ROOTFS"
|
||||||
|
|
@ -165,16 +169,22 @@ for _ in $(seq 1 200); do
|
||||||
done
|
done
|
||||||
[[ -S "$API_SOCK" ]] || { log "firecracker api socket not ready"; exit 1; }
|
[[ -S "$API_SOCK" ]] || { log "firecracker api socket not ready"; exit 1; }
|
||||||
|
|
||||||
cat > "$VM_DIR/info" <<EOF
|
VM_CONFIG_JSON="$(sudo -E curl --unix-socket "$API_SOCK" -sS http://localhost/vm/config)"
|
||||||
id=$VM_ID
|
CREATED_AT="$(date -Iseconds)"
|
||||||
name=$VM_NAME
|
jq -n \
|
||||||
pid=$FC_PID
|
--arg id "$VM_ID" \
|
||||||
created_at=$(date -Iseconds)
|
--arg name "$VM_NAME" \
|
||||||
guest_ip=$GUEST_IP
|
--arg pid "$FC_PID" \
|
||||||
tap=$TAP_DEV
|
--arg created_at "$CREATED_AT" \
|
||||||
api_sock=$API_SOCK
|
--arg guest_ip "$GUEST_IP" \
|
||||||
log=$LOG_FILE
|
--arg tap "$TAP_DEV" \
|
||||||
EOF
|
--arg api_sock "$API_SOCK" \
|
||||||
|
--arg log "$LOG_FILE" \
|
||||||
|
--arg rootfs "$OUT_ROOTFS" \
|
||||||
|
--arg kernel "$KERNEL" \
|
||||||
|
--argjson config "$VM_CONFIG_JSON" \
|
||||||
|
'{meta:{id:$id,name:$name,pid:$pid,created_at:$created_at,guest_ip:$guest_ip,tap:$tap,api_sock:$api_sock,log:$log,rootfs:$rootfs,kernel:$kernel},config:$config}' \
|
||||||
|
> "$VM_DIR/vm.json"
|
||||||
|
|
||||||
log "configuring machine"
|
log "configuring machine"
|
||||||
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/machine-config \
|
sudo -E curl --unix-socket "$API_SOCK" -X PUT http://localhost/machine-config \
|
||||||
|
|
@ -232,6 +242,19 @@ ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||||
printf 'nameserver %s\n' \"$DNS_SERVER\" > /etc/resolv.conf
|
printf 'nameserver %s\n' \"$DNS_SERVER\" > /etc/resolv.conf
|
||||||
echo \"$VM_NAME\" > /etc/hostname
|
echo \"$VM_NAME\" > /etc/hostname
|
||||||
printf '127.0.0.1 localhost\n127.0.1.1 %s\n' \"$VM_NAME\" > /etc/hosts
|
printf '127.0.0.1 localhost\n127.0.1.1 %s\n' \"$VM_NAME\" > /etc/hosts
|
||||||
|
mkdir -p /home /var
|
||||||
|
if ! grep -q '^/dev/vdb ' /etc/fstab; then
|
||||||
|
echo '/dev/vdb /home ext4 defaults 0 2' >> /etc/fstab
|
||||||
|
fi
|
||||||
|
if ! grep -q '^/dev/vdc ' /etc/fstab; then
|
||||||
|
echo '/dev/vdc /var ext4 defaults 0 2' >> /etc/fstab
|
||||||
|
fi
|
||||||
|
if ! grep -q '^tmpfs /run ' /etc/fstab; then
|
||||||
|
echo 'tmpfs /run tmpfs defaults,nodev,nosuid,mode=0755 0 0' >> /etc/fstab
|
||||||
|
fi
|
||||||
|
if ! grep -q '^tmpfs /tmp ' /etc/fstab; then
|
||||||
|
echo 'tmpfs /tmp tmpfs defaults,nodev,nosuid,mode=1777 0 0' >> /etc/fstab
|
||||||
|
fi
|
||||||
apt-get update
|
apt-get update
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
|
DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -y install git less tree ca-certificates curl
|
DEBIAN_FRONTEND=noninteractive apt-get -y install git less tree ca-certificates curl
|
||||||
|
|
|
||||||
30
kill.sh
30
kill.sh
|
|
@ -13,23 +13,17 @@ Sends a signal to the Firecracker process.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
get_prop() {
|
find_vm_json() {
|
||||||
local info="$1"
|
|
||||||
local key="$2"
|
|
||||||
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
|
||||||
}
|
|
||||||
|
|
||||||
find_vm_info() {
|
|
||||||
local query="$1"
|
local query="$1"
|
||||||
local info match_count=0 match=""
|
local vm_json match_count=0 match=""
|
||||||
|
|
||||||
for info in state/vms/*/info; do
|
for vm_json in state/vms/*/vm.json; do
|
||||||
[[ -f "$info" ]] || continue
|
[[ -f "$vm_json" ]] || continue
|
||||||
local id name
|
local id name
|
||||||
id="$(get_prop "$info" "id")"
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
||||||
name="$(get_prop "$info" "name")"
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
||||||
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
||||||
match="$info"
|
match="$vm_json"
|
||||||
match_count=$((match_count + 1))
|
match_count=$((match_count + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -75,15 +69,15 @@ if [[ -z "$QUERY" || "$QUERY" == "-h" || "$QUERY" == "--help" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO_FILE="$(find_vm_info "$QUERY")"
|
VM_JSON="$(find_vm_json "$QUERY")"
|
||||||
PID="$(get_prop "$INFO_FILE" "pid")"
|
PID="$(jq -r '.meta.pid // empty' "$VM_JSON")"
|
||||||
API_SOCK="$(get_prop "$INFO_FILE" "api_sock")"
|
API_SOCK="$(jq -r '.meta.api_sock // empty' "$VM_JSON")"
|
||||||
if [[ -z "$PID" ]]; then
|
if [[ -z "$PID" ]]; then
|
||||||
log "pid not found in $INFO_FILE"
|
log "pid not found in $VM_JSON"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ -z "$API_SOCK" ]]; then
|
if [[ -z "$API_SOCK" ]]; then
|
||||||
log "api_sock not found in $INFO_FILE"
|
log "api_sock not found in $VM_JSON"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
55
list.sh
55
list.sh
|
|
@ -3,28 +3,51 @@ set -euo pipefail
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
|
||||||
get_prop() {
|
statuses=()
|
||||||
local info="$1"
|
ids=()
|
||||||
local key="$2"
|
names=()
|
||||||
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
ips=()
|
||||||
}
|
created=()
|
||||||
|
max_name=4
|
||||||
|
max_ip=2
|
||||||
|
GREEN='\033[32m'
|
||||||
|
YELLOW='\033[33m'
|
||||||
|
RESET='\033[0m'
|
||||||
|
|
||||||
for info in state/vms/*/info; do
|
for vm_json in state/vms/*/vm.json; do
|
||||||
id="$(get_prop "$info" "id")"
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
||||||
name="$(get_prop "$info" "name")"
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
||||||
created_at="$(get_prop "$info" "created_at")"
|
created_at="$(jq -r '.meta.created_at // empty' "$vm_json")"
|
||||||
guest_ip="$(get_prop "$info" "guest_ip")"
|
guest_ip="$(jq -r '.meta.guest_ip // empty' "$vm_json")"
|
||||||
pid="$(get_prop "$info" "pid")"
|
pid="$(jq -r '.meta.pid // empty' "$vm_json")"
|
||||||
tap="$(get_prop "$info" "tap")"
|
api_sock="$(jq -r '.meta.api_sock // empty' "$vm_json")"
|
||||||
api_sock="$(get_prop "$info" "api_sock")"
|
|
||||||
|
|
||||||
status="stale"
|
status="stopped"
|
||||||
if [[ -n "$pid" && -n "$api_sock" ]]; then
|
if [[ -n "$pid" && -n "$api_sock" ]]; then
|
||||||
if ps -p "$pid" -o comm=,args= 2>/dev/null | rg -q "firecracker.*--api-sock $api_sock"; then
|
if ps -p "$pid" -o comm=,args= 2>/dev/null | rg -q "firecracker.*--api-sock $api_sock"; then
|
||||||
status="running"
|
status="running"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf 'status=%s id=%s name=%s created_at=%s guest_ip=%s pid=%s tap=%s\n' \
|
short_id="${id:0:12}"
|
||||||
"$status" "$id" "$name" "$created_at" "$guest_ip" "$pid" "$tap"
|
statuses+=("$status")
|
||||||
|
ids+=("$short_id")
|
||||||
|
names+=("$name")
|
||||||
|
ips+=("$guest_ip")
|
||||||
|
created+=("$created_at")
|
||||||
|
if (( ${#name} > max_name )); then
|
||||||
|
max_name=${#name}
|
||||||
|
fi
|
||||||
|
if (( ${#guest_ip} > max_ip )); then
|
||||||
|
max_ip=${#guest_ip}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for i in "${!ids[@]}"; do
|
||||||
|
color="$YELLOW"
|
||||||
|
if [[ "${statuses[$i]}" == "running" ]]; then
|
||||||
|
color="$GREEN"
|
||||||
|
fi
|
||||||
|
printf '%-10b %-12s %-*s %-*s %s\n' \
|
||||||
|
"${color}[${statuses[$i]}]${RESET}" "${ids[$i]}" "$max_name" "${names[$i]}" "$max_ip" "${ips[$i]}" "${created[$i]}"
|
||||||
done
|
done
|
||||||
|
|
|
||||||
24
logs.sh
24
logs.sh
|
|
@ -13,23 +13,17 @@ Prints the Firecracker log for a VM. Use --follow to tail -f.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
get_prop() {
|
find_vm_json() {
|
||||||
local info="$1"
|
|
||||||
local key="$2"
|
|
||||||
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
|
||||||
}
|
|
||||||
|
|
||||||
find_vm_info() {
|
|
||||||
local query="$1"
|
local query="$1"
|
||||||
local info match_count=0 match=""
|
local vm_json match_count=0 match=""
|
||||||
|
|
||||||
for info in state/vms/*/info; do
|
for vm_json in state/vms/*/vm.json; do
|
||||||
[[ -f "$info" ]] || continue
|
[[ -f "$vm_json" ]] || continue
|
||||||
local id name
|
local id name
|
||||||
id="$(get_prop "$info" "id")"
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
||||||
name="$(get_prop "$info" "name")"
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
||||||
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
||||||
match="$info"
|
match="$vm_json"
|
||||||
match_count=$((match_count + 1))
|
match_count=$((match_count + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -76,8 +70,8 @@ if [[ -z "$QUERY" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO_FILE="$(find_vm_info "$QUERY")"
|
VM_JSON="$(find_vm_json "$QUERY")"
|
||||||
LOG_FILE="$(get_prop "$INFO_FILE" "log")"
|
LOG_FILE="$(jq -r '.meta.log // empty' "$VM_JSON")"
|
||||||
|
|
||||||
if [[ -z "$LOG_FILE" || ! -f "$LOG_FILE" ]]; then
|
if [[ -z "$LOG_FILE" || ! -f "$LOG_FILE" ]]; then
|
||||||
log "log file not found: $LOG_FILE"
|
log "log file not found: $LOG_FILE"
|
||||||
|
|
|
||||||
28
nat.sh
28
nat.sh
|
|
@ -13,23 +13,17 @@ Manage per-VM NAT rules for internet access.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
get_prop() {
|
find_vm_json() {
|
||||||
local info="$1"
|
|
||||||
local key="$2"
|
|
||||||
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
|
||||||
}
|
|
||||||
|
|
||||||
find_vm_info() {
|
|
||||||
local query="$1"
|
local query="$1"
|
||||||
local info match_count=0 match=""
|
local vm_json match_count=0 match=""
|
||||||
|
|
||||||
for info in state/vms/*/info; do
|
for vm_json in state/vms/*/vm.json; do
|
||||||
[[ -f "$info" ]] || continue
|
[[ -f "$vm_json" ]] || continue
|
||||||
local id name
|
local id name
|
||||||
id="$(get_prop "$info" "id")"
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
||||||
name="$(get_prop "$info" "name")"
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
||||||
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
||||||
match="$info"
|
match="$vm_json"
|
||||||
match_count=$((match_count + 1))
|
match_count=$((match_count + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -64,12 +58,12 @@ if [[ -z "$ACTION" || -z "$QUERY" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO_FILE="$(find_vm_info "$QUERY")"
|
VM_JSON="$(find_vm_json "$QUERY")"
|
||||||
GUEST_IP="$(get_prop "$INFO_FILE" "guest_ip")"
|
GUEST_IP="$(jq -r '.meta.guest_ip // empty' "$VM_JSON")"
|
||||||
TAP="$(get_prop "$INFO_FILE" "tap")"
|
TAP="$(jq -r '.meta.tap // empty' "$VM_JSON")"
|
||||||
|
|
||||||
if [[ -z "$GUEST_IP" || -z "$TAP" ]]; then
|
if [[ -z "$GUEST_IP" || -z "$TAP" ]]; then
|
||||||
log "missing guest_ip or tap in $INFO_FILE"
|
log "missing guest_ip or tap in $VM_JSON"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
22
ps.sh
22
ps.sh
|
|
@ -3,20 +3,14 @@ set -euo pipefail
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
|
||||||
get_prop() {
|
for vm_json in state/vms/*/vm.json; do
|
||||||
local info="$1"
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
||||||
local key="$2"
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
||||||
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
created_at="$(jq -r '.meta.created_at // empty' "$vm_json")"
|
||||||
}
|
guest_ip="$(jq -r '.meta.guest_ip // empty' "$vm_json")"
|
||||||
|
pid="$(jq -r '.meta.pid // empty' "$vm_json")"
|
||||||
for info in state/vms/*/info; do
|
tap="$(jq -r '.meta.tap // empty' "$vm_json")"
|
||||||
id="$(get_prop "$info" "id")"
|
api_sock="$(jq -r '.meta.api_sock // empty' "$vm_json")"
|
||||||
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
|
if [[ -z "$pid" || -z "$api_sock" ]]; then
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
38
rm.sh
38
rm.sh
|
|
@ -13,23 +13,17 @@ Removes VM artifacts from state/ and cleans up TAP and mapped devices.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
get_prop() {
|
find_vm_json() {
|
||||||
local info="$1"
|
|
||||||
local key="$2"
|
|
||||||
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
|
||||||
}
|
|
||||||
|
|
||||||
find_vm_info() {
|
|
||||||
local query="$1"
|
local query="$1"
|
||||||
local info match_count=0 match=""
|
local vm_json match_count=0 match=""
|
||||||
|
|
||||||
for info in state/vms/*/info; do
|
for vm_json in state/vms/*/vm.json; do
|
||||||
[[ -f "$info" ]] || continue
|
[[ -f "$vm_json" ]] || continue
|
||||||
local id name
|
local id name
|
||||||
id="$(get_prop "$info" "id")"
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
||||||
name="$(get_prop "$info" "name")"
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
||||||
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
||||||
match="$info"
|
match="$vm_json"
|
||||||
match_count=$((match_count + 1))
|
match_count=$((match_count + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -52,15 +46,15 @@ if [[ -z "$QUERY" || "$QUERY" == "-h" || "$QUERY" == "--help" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO_FILE="$(find_vm_info "$QUERY")"
|
VM_JSON="$(find_vm_json "$QUERY")"
|
||||||
VM_DIR="${INFO_FILE%/info}"
|
VM_DIR="$(dirname "$VM_JSON")"
|
||||||
PID="$(get_prop "$INFO_FILE" "pid")"
|
PID="$(jq -r '.meta.pid // empty' "$VM_JSON")"
|
||||||
TAP="$(get_prop "$INFO_FILE" "tap")"
|
TAP="$(jq -r '.meta.tap // empty' "$VM_JSON")"
|
||||||
API_SOCK="$(get_prop "$INFO_FILE" "api_sock")"
|
API_SOCK="$(jq -r '.meta.api_sock // empty' "$VM_JSON")"
|
||||||
BASE_LOOP="$(get_prop "$INFO_FILE" "base_loop")"
|
BASE_LOOP="$(jq -r '.meta.base_loop // empty' "$VM_JSON")"
|
||||||
COW_LOOP="$(get_prop "$INFO_FILE" "cow_loop")"
|
COW_LOOP="$(jq -r '.meta.cow_loop // empty' "$VM_JSON")"
|
||||||
DM_DEV="$(get_prop "$INFO_FILE" "dm_dev")"
|
DM_DEV="$(jq -r '.meta.dm_dev // empty' "$VM_JSON")"
|
||||||
DM_NAME="$(get_prop "$INFO_FILE" "dm_name")"
|
DM_NAME="$(jq -r '.meta.dm_name // empty' "$VM_JSON")"
|
||||||
|
|
||||||
if [[ -n "$PID" ]]; then
|
if [[ -n "$PID" ]]; then
|
||||||
sudo kill "$PID" 2>/dev/null || true
|
sudo kill "$PID" 2>/dev/null || true
|
||||||
|
|
|
||||||
51
run.sh
51
run.sh
|
|
@ -62,9 +62,9 @@ shopt -s nullglob
|
||||||
|
|
||||||
name_taken() {
|
name_taken() {
|
||||||
local candidate="$1"
|
local candidate="$1"
|
||||||
local info existing_name
|
local vm_json existing_name
|
||||||
for info in "$VM_ROOT"/*/info; do
|
for vm_json in "$VM_ROOT"/*/vm.json; do
|
||||||
existing_name="$(awk -F= '$1=="name"{print $2}' "$info")"
|
existing_name="$(jq -r '.meta.name // empty' "$vm_json" 2>/dev/null)"
|
||||||
if [[ "$existing_name" == "$candidate" ]]; then
|
if [[ "$existing_name" == "$candidate" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
@ -311,6 +311,10 @@ if ! command -v e2cp >/dev/null 2>&1 || ! command -v e2rm >/dev/null 2>&1; then
|
||||||
log "e2cp and e2rm are required to set hostname and resolv.conf"
|
log "e2cp and e2rm are required to set hostname and resolv.conf"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
log "jq is required to persist VM metadata"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
COW_BYTES="$(parse_disk_size "$COW_SIZE")"
|
COW_BYTES="$(parse_disk_size "$COW_SIZE")"
|
||||||
if [[ -z "$COW_BYTES" ]]; then
|
if [[ -z "$COW_BYTES" ]]; then
|
||||||
|
|
@ -469,24 +473,29 @@ log "starting virtual machine"
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{ "action_type": "InstanceStart" }' >/dev/null
|
-d '{ "action_type": "InstanceStart" }' >/dev/null
|
||||||
VM_STARTED=1
|
VM_STARTED=1
|
||||||
|
VM_CONFIG_JSON="$("${CURL_CMD[@]}" --unix-socket "$API_SOCK" -sS http://localhost/vm/config)"
|
||||||
cat > "$VM_DIR/info" <<EOF
|
CREATED_AT="$(date -Iseconds)"
|
||||||
id=$VM_ID
|
jq -n \
|
||||||
name=$VM_NAME
|
--arg id "$VM_ID" \
|
||||||
pid=$FC_PID
|
--arg name "$VM_NAME" \
|
||||||
created_at=$(date -Iseconds)
|
--arg pid "$FC_PID" \
|
||||||
rootfs=$ROOTFS
|
--arg created_at "$CREATED_AT" \
|
||||||
kernel=$KERNEL
|
--arg guest_ip "$GUEST_IP" \
|
||||||
guest_ip=$GUEST_IP
|
--arg tap "$TAP_DEV" \
|
||||||
tap=$TAP_DEV
|
--arg api_sock "$API_SOCK" \
|
||||||
api_sock=$API_SOCK
|
--arg log "$LOG_FILE" \
|
||||||
log=$LOG_FILE
|
--arg rootfs "$ROOTFS" \
|
||||||
base_loop=$BASE_LOOP
|
--arg kernel "$KERNEL" \
|
||||||
cow_file=$COW_FILE
|
--arg home_path "$HOME_PATH" \
|
||||||
cow_loop=$COW_LOOP
|
--arg var_path "$VAR_PATH" \
|
||||||
dm_name=$DM_NAME
|
--arg base_loop "$BASE_LOOP" \
|
||||||
dm_dev=$DM_DEV
|
--arg cow_file "$COW_FILE" \
|
||||||
EOF
|
--arg cow_loop "$COW_LOOP" \
|
||||||
|
--arg dm_name "$DM_NAME" \
|
||||||
|
--arg dm_dev "$DM_DEV" \
|
||||||
|
--argjson config "$VM_CONFIG_JSON" \
|
||||||
|
'{meta:{id:$id,name:$name,pid:$pid,created_at:$created_at,guest_ip:$guest_ip,tap:$tap,api_sock:$api_sock,log:$log,rootfs:$rootfs,kernel:$kernel,home_path:$home_path,var_path:$var_path,base_loop:$base_loop,cow_file:$cow_file,cow_loop:$cow_loop,dm_name:$dm_name,dm_dev:$dm_dev},config:$config}' \
|
||||||
|
> "$VM_DIR/vm.json"
|
||||||
|
|
||||||
log "vm started successfully"
|
log "vm started successfully"
|
||||||
log "guest ip: $GUEST_IP"
|
log "guest ip: $GUEST_IP"
|
||||||
|
|
|
||||||
24
stop.sh
24
stop.sh
|
|
@ -13,23 +13,17 @@ Sends Ctrl+Alt+Del to the guest via the Firecracker API socket.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
get_prop() {
|
find_vm_json() {
|
||||||
local info="$1"
|
|
||||||
local key="$2"
|
|
||||||
awk -F= -v k="$key" '$1==k {print $2}' "$info"
|
|
||||||
}
|
|
||||||
|
|
||||||
find_vm_info() {
|
|
||||||
local query="$1"
|
local query="$1"
|
||||||
local info match_count=0 match=""
|
local vm_json match_count=0 match=""
|
||||||
|
|
||||||
for info in state/vms/*/info; do
|
for vm_json in state/vms/*/vm.json; do
|
||||||
[[ -f "$info" ]] || continue
|
[[ -f "$vm_json" ]] || continue
|
||||||
local id name
|
local id name
|
||||||
id="$(get_prop "$info" "id")"
|
id="$(jq -r '.meta.id // empty' "$vm_json")"
|
||||||
name="$(get_prop "$info" "name")"
|
name="$(jq -r '.meta.name // empty' "$vm_json")"
|
||||||
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
if [[ "$id" == "$query"* || "$name" == "$query"* ]]; then
|
||||||
match="$info"
|
match="$vm_json"
|
||||||
match_count=$((match_count + 1))
|
match_count=$((match_count + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -52,8 +46,8 @@ if [[ -z "$QUERY" || "$QUERY" == "-h" || "$QUERY" == "--help" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INFO_FILE="$(find_vm_info "$QUERY")"
|
VM_JSON="$(find_vm_json "$QUERY")"
|
||||||
API_SOCK="$(get_prop "$INFO_FILE" "api_sock")"
|
API_SOCK="$(jq -r '.meta.api_sock // empty' "$VM_JSON")"
|
||||||
if [[ -z "$API_SOCK" || ! -S "$API_SOCK" ]]; then
|
if [[ -z "$API_SOCK" || ! -S "$API_SOCK" ]]; then
|
||||||
log "api socket not found: $API_SOCK"
|
log "api socket not found: $API_SOCK"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
30
verify.sh
30
verify.sh
|
|
@ -6,18 +6,19 @@ log() {
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
if [[ -z "${VM_INFO:-}" || ! -f "$VM_INFO" ]]; then
|
if [[ -z "${VM_JSON:-}" || ! -f "$VM_JSON" ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
# shellcheck disable=SC1090
|
pid="$(jq -r '.meta.pid // empty' "$VM_JSON")"
|
||||||
source "$VM_INFO"
|
tap="$(jq -r '.meta.tap // empty' "$VM_JSON")"
|
||||||
if [[ -n "${pid:-}" ]]; then
|
vm_dir="$(dirname "$VM_JSON")"
|
||||||
|
if [[ -n "$pid" ]]; then
|
||||||
sudo kill "$pid" 2>/dev/null || true
|
sudo kill "$pid" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
if [[ -n "${tap:-}" ]]; then
|
if [[ -n "$tap" ]]; then
|
||||||
sudo ip link del "$tap" 2>/dev/null || true
|
sudo ip link del "$tap" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
if [[ -n "${vm_dir:-}" ]]; then
|
if [[ -n "$vm_dir" ]]; then
|
||||||
rm -rf "$vm_dir"
|
rm -rf "$vm_dir"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
@ -35,18 +36,21 @@ if [[ -z "$VM_DIR" ]]; then
|
||||||
log "no VM state directory found"
|
log "no VM state directory found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
VM_INFO="$VM_DIR/info"
|
VM_JSON="$VM_DIR/vm.json"
|
||||||
if [[ ! -f "$VM_INFO" ]]; then
|
if [[ ! -f "$VM_JSON" ]]; then
|
||||||
log "info file not found: $VM_INFO"
|
log "vm.json not found: $VM_JSON"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC1090
|
name="$(jq -r '.meta.name // empty' "$VM_JSON")"
|
||||||
source "$VM_INFO"
|
created_at="$(jq -r '.meta.created_at // empty' "$VM_JSON")"
|
||||||
|
guest_ip="$(jq -r '.meta.guest_ip // empty' "$VM_JSON")"
|
||||||
|
tap="$(jq -r '.meta.tap // empty' "$VM_JSON")"
|
||||||
|
pid="$(jq -r '.meta.pid // empty' "$VM_JSON")"
|
||||||
vm_dir="$VM_DIR"
|
vm_dir="$VM_DIR"
|
||||||
|
|
||||||
if [[ -z "${name:-}" || -z "${created_at:-}" || -z "${guest_ip:-}" ]]; then
|
if [[ -z "$name" || -z "$created_at" || -z "$guest_ip" ]]; then
|
||||||
log "missing name or created_at in info file"
|
log "missing name or created_at in vm.json"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue