Streamline VM overlays and rootfs packages
Move the default guest package list into a repo manifest and record a hash beside built rootfs images so run/make-rootfs can warn when the docker-ready image is stale. Switch the Firecracker launch path to a single sparse root overlay per VM instead of separate /home and /var disks, so many VMs can share the same base image while still installing packages under /var and working from /root. Keep older images bootable by masking stale home.mount and var.mount units at boot, and scrub those obsolete fstab entries when customize.sh rebuilds an image. Verified with bash -n on the updated scripts; no live VM boot was run in this environment.
This commit is contained in:
parent
9191b7e370
commit
3cf33d1e0a
8 changed files with 206 additions and 204 deletions
141
run.sh
141
run.sh
|
|
@ -13,11 +13,10 @@ Options:
|
|||
--name <name> VM name (lowercase letters, digits, -)
|
||||
--vcpu <count> vCPU count (default: 2)
|
||||
--ram <mib> RAM in MiB (default: 1024)
|
||||
--rootfs <path> Root filesystem image (default: ./rootfs.ext4)
|
||||
--overlay-size <size> Writable overlay size (e.g. 8G, 16384M)
|
||||
--rootfs <path> Root filesystem image (default: ./rootfs-docker.ext4)
|
||||
--kernel <path> Kernel image (default: ./vmlinux)
|
||||
--initrd <path> Initrd image (optional)
|
||||
--home-size <size> Home disk size (e.g. 4G, 10240M)
|
||||
--var-size <size> Var disk size (e.g. 4G, 10240M)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
|
@ -26,6 +25,7 @@ log "starting"
|
|||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$DIR/dns.sh"
|
||||
source "$DIR/packages.sh"
|
||||
STATE="$DIR/state"
|
||||
VM_ROOT="$STATE/vms"
|
||||
mkdir -p "$VM_ROOT"
|
||||
|
|
@ -43,20 +43,17 @@ CIDR="24"
|
|||
|
||||
DEFAULT_VCPU=2
|
||||
DEFAULT_RAM=1024
|
||||
DEFAULT_HOME_SIZE="2G"
|
||||
DEFAULT_VAR_SIZE="2G"
|
||||
DEFAULT_OVERLAY_SIZE="8G"
|
||||
MIN_VCPU=1
|
||||
MAX_VCPU=16
|
||||
MIN_RAM=256
|
||||
MAX_RAM=32768
|
||||
MAX_DISK_BYTES=$((128 * 1024 * 1024 * 1024))
|
||||
DNS_SERVER="1.1.1.1"
|
||||
COW_SIZE="2G"
|
||||
|
||||
VCPU_COUNT="$DEFAULT_VCPU"
|
||||
RAM_MIB="$DEFAULT_RAM"
|
||||
HOME_SIZE="$DEFAULT_HOME_SIZE"
|
||||
VAR_SIZE="$DEFAULT_VAR_SIZE"
|
||||
OVERLAY_SIZE="$DEFAULT_OVERLAY_SIZE"
|
||||
KERNEL="$DEFAULT_KERNEL"
|
||||
ROOTFS="$DEFAULT_ROOTFS"
|
||||
INITRD="$DEFAULT_INITRD"
|
||||
|
|
@ -105,6 +102,10 @@ while [[ $# -gt 0 ]]; do
|
|||
RAM_MIB="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--overlay-size)
|
||||
OVERLAY_SIZE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--rootfs)
|
||||
ROOTFS="${2:-}"
|
||||
shift 2
|
||||
|
|
@ -117,14 +118,6 @@ while [[ $# -gt 0 ]]; do
|
|||
INITRD="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--home-size)
|
||||
HOME_SIZE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--var-size)
|
||||
VAR_SIZE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
|
|
@ -155,23 +148,12 @@ if (( RAM_MIB < MIN_RAM || RAM_MIB > MAX_RAM )); then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
HOME_BYTES=""
|
||||
if ! HOME_BYTES="$(parse_disk_size "$HOME_SIZE")"; then
|
||||
log "invalid --home-size value: $HOME_SIZE"
|
||||
if ! OVERLAY_BYTES="$(parse_disk_size "$OVERLAY_SIZE")"; then
|
||||
log "invalid --overlay-size value: $OVERLAY_SIZE"
|
||||
exit 1
|
||||
fi
|
||||
if (( HOME_BYTES > MAX_DISK_BYTES )); then
|
||||
log "home-size exceeds max of $((MAX_DISK_BYTES / 1024 / 1024 / 1024))G"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAR_BYTES=""
|
||||
if ! VAR_BYTES="$(parse_disk_size "$VAR_SIZE")"; then
|
||||
log "invalid --var-size value: $VAR_SIZE"
|
||||
exit 1
|
||||
fi
|
||||
if (( VAR_BYTES > MAX_DISK_BYTES )); then
|
||||
log "var-size exceeds max of $((MAX_DISK_BYTES / 1024 / 1024 / 1024))G"
|
||||
if (( OVERLAY_BYTES > MAX_DISK_BYTES )); then
|
||||
log "overlay-size exceeds max of $((MAX_DISK_BYTES / 1024 / 1024 / 1024))G"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -207,6 +189,12 @@ if [[ ! -f "$ROOTFS" ]]; then
|
|||
log "rootfs not found: $ROOTFS"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$ROOTFS" == "$DEFAULT_ROOTFS" ]]; then
|
||||
ROOTFS_WARNING="$(banger_rootfs_manifest_warning "$ROOTFS" || true)"
|
||||
if [[ -n "$ROOTFS_WARNING" ]]; then
|
||||
log "warning: $ROOTFS_WARNING"
|
||||
fi
|
||||
fi
|
||||
if [[ ! -f "$KERNEL" ]]; then
|
||||
log "kernel not found: $KERNEL"
|
||||
exit 1
|
||||
|
|
@ -248,49 +236,13 @@ sudo -v
|
|||
VM_STARTED=0
|
||||
CLEANUP_ON_EXIT=0
|
||||
KEEP_VM_DIR_ON_FAIL=1
|
||||
HOME_PATH="$VM_DIR/home.ext4"
|
||||
VAR_PATH="$VM_DIR/var.ext4"
|
||||
COW_FILE="$VM_DIR/cow.ext4"
|
||||
BASE_LOOP=""
|
||||
COW_LOOP=""
|
||||
DM_NAME="fc-rootfs-$VM_TAG"
|
||||
DM_DEV=""
|
||||
SEED_ROOT_MNT="$VM_DIR/mnt-root"
|
||||
SEED_HOME_MNT="$VM_DIR/mnt-home"
|
||||
SEED_VAR_MNT="$VM_DIR/mnt-var"
|
||||
DNS_NAME=""
|
||||
|
||||
unmount_if_needed() {
|
||||
local mount_path="$1"
|
||||
[[ -n "$mount_path" ]] || return 0
|
||||
sudo umount "$mount_path" 2>/dev/null || true
|
||||
}
|
||||
|
||||
seed_volume_from_rootfs() {
|
||||
local source_dir="$1"
|
||||
local target_image="$2"
|
||||
local target_mount="$3"
|
||||
local label="$4"
|
||||
|
||||
if [[ ! -d "$source_dir" ]]; then
|
||||
log "source directory missing in rootfs snapshot: $label"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$target_mount"
|
||||
sudo mount "$target_image" "$target_mount"
|
||||
sudo cp -a "$source_dir/." "$target_mount/"
|
||||
sudo umount "$target_mount"
|
||||
}
|
||||
|
||||
populate_data_disks() {
|
||||
mkdir -p "$SEED_ROOT_MNT" "$SEED_HOME_MNT" "$SEED_VAR_MNT"
|
||||
sudo mount -o ro "$DM_DEV" "$SEED_ROOT_MNT"
|
||||
seed_volume_from_rootfs "$SEED_ROOT_MNT/home" "$HOME_PATH" "$SEED_HOME_MNT" "/home"
|
||||
seed_volume_from_rootfs "$SEED_ROOT_MNT/var" "$VAR_PATH" "$SEED_VAR_MNT" "/var"
|
||||
sudo umount "$SEED_ROOT_MNT"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
if [[ "$VM_STARTED" -eq 1 && "$CLEANUP_ON_EXIT" -eq 0 ]]; then
|
||||
|
|
@ -304,9 +256,6 @@ cleanup() {
|
|||
sudo ip link del "$TAP_DEV" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "${API_SOCK:-}"
|
||||
unmount_if_needed "${SEED_VAR_MNT:-}"
|
||||
unmount_if_needed "${SEED_HOME_MNT:-}"
|
||||
unmount_if_needed "${SEED_ROOT_MNT:-}"
|
||||
banger_dns_remove_record_name "${DNS_NAME:-}"
|
||||
if [[ -n "${DM_NAME:-}" ]]; then
|
||||
sudo dmsetup remove "$DM_NAME" 2>/dev/null || true
|
||||
|
|
@ -351,15 +300,6 @@ else
|
|||
log "setcap not available; firecracker may need root to open TAP"
|
||||
fi
|
||||
|
||||
if ! command -v mkfs.ext4 >/dev/null 2>&1; then
|
||||
log "mkfs.ext4 required to create home/var disks"
|
||||
exit 1
|
||||
fi
|
||||
truncate -s "$HOME_BYTES" "$HOME_PATH"
|
||||
mkfs.ext4 -F "$HOME_PATH" >/dev/null
|
||||
truncate -s "$VAR_BYTES" "$VAR_PATH"
|
||||
mkfs.ext4 -F "$VAR_PATH" >/dev/null
|
||||
|
||||
if ! command -v dmsetup >/dev/null 2>&1 || ! command -v losetup >/dev/null 2>&1 || ! command -v blockdev >/dev/null 2>&1; then
|
||||
log "dmsetup, losetup, and blockdev are required for rootfs snapshots"
|
||||
exit 1
|
||||
|
|
@ -368,23 +308,13 @@ 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"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v mount >/dev/null 2>&1 || ! command -v umount >/dev/null 2>&1; then
|
||||
log "mount and umount are required to populate home/var disks"
|
||||
exit 1
|
||||
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")"
|
||||
if [[ -z "$COW_BYTES" ]]; then
|
||||
log "invalid COW size: $COW_SIZE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_LOOP="$(sudo losetup -f --show --read-only "$ROOTFS")"
|
||||
truncate -s "$COW_BYTES" "$COW_FILE"
|
||||
truncate -s "$OVERLAY_BYTES" "$COW_FILE"
|
||||
COW_LOOP="$(sudo losetup -f --show "$COW_FILE")"
|
||||
SECTORS="$(sudo blockdev --getsz "$BASE_LOOP")"
|
||||
sudo dmsetup create "$DM_NAME" --table "0 $SECTORS snapshot $BASE_LOOP $COW_LOOP P 8"
|
||||
|
|
@ -412,9 +342,6 @@ sudo e2cp "$HOSTS_TMP" "$DM_DEV:/etc/hosts" >/dev/null 2>&1 || {
|
|||
exit 1
|
||||
}
|
||||
|
||||
log "populating /home and /var disks from rootfs snapshot"
|
||||
populate_data_disks
|
||||
|
||||
# Host bridge
|
||||
if ! ip link show "$BR_DEV" >/dev/null 2>&1; then
|
||||
log "creating host bridge $BR_DEV ($BR_IP/$CIDR)"
|
||||
|
|
@ -480,7 +407,7 @@ log "configuring machine"
|
|||
|
||||
# Boot source
|
||||
log "configuring boot source"
|
||||
KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0::eth0:off:${DNS_SERVER} hostname=${VM_NAME}"
|
||||
KCMD="console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw ip=${GUEST_IP}::${BR_IP}:255.255.255.0::eth0:off:${DNS_SERVER} hostname=${VM_NAME} systemd.mask=home.mount systemd.mask=var.mount"
|
||||
|
||||
INITRD_JSON=""
|
||||
if [[ -n "$INITRD" ]]; then
|
||||
|
|
@ -505,28 +432,6 @@ log "attaching root filesystem"
|
|||
\"is_read_only\": false
|
||||
}" >/dev/null
|
||||
|
||||
# Home filesystem
|
||||
log "attaching home filesystem"
|
||||
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/drives/home \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"drive_id\": \"home\",
|
||||
\"path_on_host\": \"$HOME_PATH\",
|
||||
\"is_root_device\": false,
|
||||
\"is_read_only\": false
|
||||
}" >/dev/null
|
||||
|
||||
# Var filesystem
|
||||
log "attaching var filesystem"
|
||||
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/drives/var \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"drive_id\": \"var\",
|
||||
\"path_on_host\": \"$VAR_PATH\",
|
||||
\"is_root_device\": false,
|
||||
\"is_read_only\": false
|
||||
}" >/dev/null
|
||||
|
||||
# Network interface
|
||||
log "configuring network interface"
|
||||
"${CURL_CMD[@]}" --unix-socket "$API_SOCK" -X PUT http://localhost/network-interfaces/eth0 \
|
||||
|
|
@ -556,8 +461,6 @@ jq -n \
|
|||
--arg log "$LOG_FILE" \
|
||||
--arg rootfs "$ROOTFS" \
|
||||
--arg kernel "$KERNEL" \
|
||||
--arg home_path "$HOME_PATH" \
|
||||
--arg var_path "$VAR_PATH" \
|
||||
--arg base_loop "$BASE_LOOP" \
|
||||
--arg cow_file "$COW_FILE" \
|
||||
--arg cow_loop "$COW_LOOP" \
|
||||
|
|
@ -565,7 +468,7 @@ jq -n \
|
|||
--arg dm_dev "$DM_DEV" \
|
||||
--arg dns_name "$DNS_NAME" \
|
||||
--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,dns_name:$dns_name},config:$config}' \
|
||||
'{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,base_loop:$base_loop,cow_file:$cow_file,cow_loop:$cow_loop,dm_name:$dm_name,dm_dev:$dm_dev,dns_name:$dns_name},config:$config}' \
|
||||
> "$VM_DIR/vm.json"
|
||||
VM_STARTED=1
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue