diff options
Diffstat (limited to 'recipes-containers/vcontainer/files/vrunner.sh')
| -rwxr-xr-x | recipes-containers/vcontainer/files/vrunner.sh | 433 |
1 files changed, 151 insertions, 282 deletions
diff --git a/recipes-containers/vcontainer/files/vrunner.sh b/recipes-containers/vcontainer/files/vrunner.sh index 22e9229a..8ad45afe 100755 --- a/recipes-containers/vcontainer/files/vrunner.sh +++ b/recipes-containers/vcontainer/files/vrunner.sh | |||
| @@ -4,29 +4,29 @@ | |||
| 4 | # SPDX-License-Identifier: GPL-2.0-only | 4 | # SPDX-License-Identifier: GPL-2.0-only |
| 5 | # | 5 | # |
| 6 | # vrunner.sh | 6 | # vrunner.sh |
| 7 | # Core runner for vdkr/vpdmn: execute container commands in QEMU-emulated environment | 7 | # Core runner for vdkr/vpdmn/vxn: execute container commands in a hypervisor VM |
| 8 | # | 8 | # |
| 9 | # This script is runtime-agnostic and supports both Docker and Podman via --runtime. | 9 | # This script is runtime-agnostic and supports both Docker and Podman via --runtime. |
| 10 | # It is also hypervisor-agnostic via pluggable backends (QEMU, Xen). | ||
| 10 | # | 11 | # |
| 11 | # Boot flow: | 12 | # Boot flow: |
| 12 | # 1. QEMU loads kernel + tiny initramfs (busybox + preinit) | 13 | # 1. Hypervisor boots kernel + tiny initramfs (busybox + preinit) |
| 13 | # 2. preinit mounts rootfs.img (/dev/vda) and does switch_root | 14 | # 2. preinit mounts rootfs.img and does switch_root |
| 14 | # 3. Real /init runs on actual ext4 filesystem | 15 | # 3. Real /init runs on actual filesystem |
| 15 | # 4. Container runtime starts, executes command, outputs results | 16 | # 4. Container runtime starts, executes command, outputs results |
| 16 | # | 17 | # |
| 17 | # This two-stage boot is required because runc needs pivot_root, | 18 | # This two-stage boot is required because runc needs pivot_root, |
| 18 | # which doesn't work from initramfs (rootfs isn't a mount point). | 19 | # which doesn't work from initramfs (rootfs isn't a mount point). |
| 19 | # | 20 | # |
| 20 | # Drive layout: | 21 | # Drive layout (device names vary by hypervisor): |
| 21 | # /dev/vda = rootfs.img (ro, ext4 with container tools) | 22 | # QEMU: /dev/vda, /dev/vdb, /dev/vdc (virtio-blk) |
| 22 | # /dev/vdb = input disk (optional, user data) | 23 | # Xen: /dev/xvda, /dev/xvdb, /dev/xvdc (xen-blkfront) |
| 23 | # /dev/vdc = state disk (optional, persistent container storage) | ||
| 24 | # | 24 | # |
| 25 | # Version: 3.4.0 | 25 | # Version: 3.5.0 |
| 26 | 26 | ||
| 27 | set -e | 27 | set -e |
| 28 | 28 | ||
| 29 | VERSION="3.4.0" | 29 | VERSION="3.5.0" |
| 30 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | 30 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 31 | 31 | ||
| 32 | # Runtime selection: docker or podman | 32 | # Runtime selection: docker or podman |
| @@ -42,19 +42,19 @@ VERBOSE="${VDKR_VERBOSE:-${VPDMN_VERBOSE:-false}}" | |||
| 42 | set_runtime_config() { | 42 | set_runtime_config() { |
| 43 | case "$RUNTIME" in | 43 | case "$RUNTIME" in |
| 44 | docker) | 44 | docker) |
| 45 | TOOL_NAME="vdkr" | 45 | TOOL_NAME="${VCONTAINER_RUNTIME_NAME:-vdkr}" |
| 46 | BLOB_SUBDIR="vdkr-blobs" | 46 | BLOB_SUBDIR="vdkr-blobs" |
| 47 | BLOB_SUBDIR_ALT="blobs" | 47 | BLOB_SUBDIR_ALT="blobs" |
| 48 | CMDLINE_PREFIX="docker" | 48 | CMDLINE_PREFIX="docker" |
| 49 | STATE_DIR_BASE="${VDKR_STATE_DIR:-$HOME/.vdkr}" | 49 | STATE_DIR_BASE="${VDKR_STATE_DIR:-$HOME/.${TOOL_NAME}}" |
| 50 | STATE_FILE="docker-state.img" | 50 | STATE_FILE="docker-state.img" |
| 51 | ;; | 51 | ;; |
| 52 | podman) | 52 | podman) |
| 53 | TOOL_NAME="vpdmn" | 53 | TOOL_NAME="${VCONTAINER_RUNTIME_NAME:-vpdmn}" |
| 54 | BLOB_SUBDIR="vpdmn-blobs" | 54 | BLOB_SUBDIR="vpdmn-blobs" |
| 55 | BLOB_SUBDIR_ALT="blobs/vpdmn" | 55 | BLOB_SUBDIR_ALT="blobs/vpdmn" |
| 56 | CMDLINE_PREFIX="podman" | 56 | CMDLINE_PREFIX="podman" |
| 57 | STATE_DIR_BASE="${VPDMN_STATE_DIR:-$HOME/.vpdmn}" | 57 | STATE_DIR_BASE="${VPDMN_STATE_DIR:-$HOME/.${TOOL_NAME}}" |
| 58 | STATE_FILE="podman-state.img" | 58 | STATE_FILE="podman-state.img" |
| 59 | ;; | 59 | ;; |
| 60 | *) | 60 | *) |
| @@ -417,6 +417,10 @@ while [ $# -gt 0 ]; do | |||
| 417 | DISABLE_KVM="true" | 417 | DISABLE_KVM="true" |
| 418 | shift | 418 | shift |
| 419 | ;; | 419 | ;; |
| 420 | --hypervisor) | ||
| 421 | VCONTAINER_HYPERVISOR="$2" | ||
| 422 | shift 2 | ||
| 423 | ;; | ||
| 420 | --batch-import) | 424 | --batch-import) |
| 421 | BATCH_IMPORT="true" | 425 | BATCH_IMPORT="true" |
| 422 | # Force storage output type for batch import | 426 | # Force storage output type for batch import |
| @@ -485,6 +489,18 @@ done | |||
| 485 | set_runtime_config | 489 | set_runtime_config |
| 486 | set_blob_dir | 490 | set_blob_dir |
| 487 | 491 | ||
| 492 | # Load hypervisor backend | ||
| 493 | VCONTAINER_HYPERVISOR="${VCONTAINER_HYPERVISOR:-qemu}" | ||
| 494 | VCONTAINER_LIBDIR="${VCONTAINER_LIBDIR:-$SCRIPT_DIR}" | ||
| 495 | HV_BACKEND="$VCONTAINER_LIBDIR/vrunner-backend-${VCONTAINER_HYPERVISOR}.sh" | ||
| 496 | if [ ! -f "$HV_BACKEND" ]; then | ||
| 497 | echo "ERROR: Hypervisor backend not found: $HV_BACKEND" >&2 | ||
| 498 | echo "Available backends:" >&2 | ||
| 499 | ls "$VCONTAINER_LIBDIR"/vrunner-backend-*.sh 2>/dev/null | sed 's/.*vrunner-backend-//;s/\.sh$//' >&2 | ||
| 500 | exit 1 | ||
| 501 | fi | ||
| 502 | source "$HV_BACKEND" | ||
| 503 | |||
| 488 | # Daemon mode handling | 504 | # Daemon mode handling |
| 489 | # Set default socket directory based on architecture | 505 | # Set default socket directory based on architecture |
| 490 | # If --state-dir was provided, use it for daemon files too | 506 | # If --state-dir was provided, use it for daemon files too |
| @@ -503,6 +519,12 @@ DAEMON_INPUT_SIZE_MB=2048 # 2GB input disk for daemon mode | |||
| 503 | 519 | ||
| 504 | # Daemon helper functions | 520 | # Daemon helper functions |
| 505 | daemon_is_running() { | 521 | daemon_is_running() { |
| 522 | # Use backend-specific check if available (e.g. Xen xl list) | ||
| 523 | if type hv_daemon_is_running >/dev/null 2>&1; then | ||
| 524 | hv_daemon_is_running | ||
| 525 | return $? | ||
| 526 | fi | ||
| 527 | # Default: check PID (works for QEMU) | ||
| 506 | if [ -f "$DAEMON_PID_FILE" ]; then | 528 | if [ -f "$DAEMON_PID_FILE" ]; then |
| 507 | local pid=$(cat "$DAEMON_PID_FILE" 2>/dev/null) | 529 | local pid=$(cat "$DAEMON_PID_FILE" 2>/dev/null) |
| 508 | if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then | 530 | if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then |
| @@ -514,8 +536,10 @@ daemon_is_running() { | |||
| 514 | 536 | ||
| 515 | daemon_status() { | 537 | daemon_status() { |
| 516 | if daemon_is_running; then | 538 | if daemon_is_running; then |
| 517 | local pid=$(cat "$DAEMON_PID_FILE") | 539 | local pid=$(cat "$DAEMON_PID_FILE" 2>/dev/null) |
| 518 | echo "Daemon running (PID: $pid)" | 540 | local vm_id |
| 541 | vm_id=$(hv_get_vm_id 2>/dev/null || echo "$pid") | ||
| 542 | echo "Daemon running (VM: $vm_id)" | ||
| 519 | echo "Socket: $DAEMON_SOCKET" | 543 | echo "Socket: $DAEMON_SOCKET" |
| 520 | echo "Architecture: $TARGET_ARCH" | 544 | echo "Architecture: $TARGET_ARCH" |
| 521 | return 0 | 545 | return 0 |
| @@ -531,6 +555,14 @@ daemon_stop() { | |||
| 531 | return 0 | 555 | return 0 |
| 532 | fi | 556 | fi |
| 533 | 557 | ||
| 558 | # Use backend-specific stop if available (e.g. Xen xl shutdown/destroy) | ||
| 559 | if type hv_daemon_stop >/dev/null 2>&1; then | ||
| 560 | hv_daemon_stop | ||
| 561 | rm -f "$DAEMON_PID_FILE" "$DAEMON_SOCKET" | ||
| 562 | return 0 | ||
| 563 | fi | ||
| 564 | |||
| 565 | # Default: PID-based stop (works for QEMU) | ||
| 534 | local pid=$(cat "$DAEMON_PID_FILE") | 566 | local pid=$(cat "$DAEMON_PID_FILE") |
| 535 | log "INFO" "Stopping daemon (PID: $pid)..." | 567 | log "INFO" "Stopping daemon (PID: $pid)..." |
| 536 | 568 | ||
| @@ -933,36 +965,16 @@ log "INFO" "Output type: $OUTPUT_TYPE" | |||
| 933 | [ "$NETWORK" = "true" ] && log "INFO" "Networking: enabled (slirp)" | 965 | [ "$NETWORK" = "true" ] && log "INFO" "Networking: enabled (slirp)" |
| 934 | [ "$INTERACTIVE" = "true" ] && log "INFO" "Interactive mode: enabled" | 966 | [ "$INTERACTIVE" = "true" ] && log "INFO" "Interactive mode: enabled" |
| 935 | 967 | ||
| 936 | # Find kernel, initramfs, and rootfs | 968 | # Initialize hypervisor backend: set arch-specific paths and commands |
| 937 | case "$TARGET_ARCH" in | 969 | hv_setup_arch |
| 938 | aarch64) | 970 | hv_check_accel |
| 939 | KERNEL_IMAGE="$BLOB_DIR/aarch64/Image" | 971 | hv_find_command |
| 940 | INITRAMFS="$BLOB_DIR/aarch64/initramfs.cpio.gz" | ||
| 941 | ROOTFS_IMG="$BLOB_DIR/aarch64/rootfs.img" | ||
| 942 | QEMU_CMD="qemu-system-aarch64" | ||
| 943 | QEMU_MACHINE="-M virt -cpu cortex-a57" | ||
| 944 | CONSOLE="ttyAMA0" | ||
| 945 | ;; | ||
| 946 | x86_64) | ||
| 947 | KERNEL_IMAGE="$BLOB_DIR/x86_64/bzImage" | ||
| 948 | INITRAMFS="$BLOB_DIR/x86_64/initramfs.cpio.gz" | ||
| 949 | ROOTFS_IMG="$BLOB_DIR/x86_64/rootfs.img" | ||
| 950 | QEMU_CMD="qemu-system-x86_64" | ||
| 951 | # Use q35 + Skylake-Client to match oe-core qemux86-64 machine | ||
| 952 | QEMU_MACHINE="-M q35 -cpu Skylake-Client" | ||
| 953 | CONSOLE="ttyS0" | ||
| 954 | ;; | ||
| 955 | *) | ||
| 956 | log "ERROR" "Unsupported architecture: $TARGET_ARCH" | ||
| 957 | exit 1 | ||
| 958 | ;; | ||
| 959 | esac | ||
| 960 | 972 | ||
| 961 | # Check for kernel | 973 | # Check for kernel |
| 962 | if [ ! -f "$KERNEL_IMAGE" ]; then | 974 | if [ ! -f "$KERNEL_IMAGE" ]; then |
| 963 | log "ERROR" "Kernel not found: $KERNEL_IMAGE" | 975 | log "ERROR" "Kernel not found: $KERNEL_IMAGE" |
| 964 | log "ERROR" "Set VDKR_BLOB_DIR or --blob-dir to location of vdkr blobs" | 976 | log "ERROR" "Set --blob-dir to location of blobs" |
| 965 | log "ERROR" "Build with: MACHINE=qemuarm64 bitbake vdkr-initramfs-build" | 977 | log "ERROR" "Build with: bitbake ${TOOL_NAME}-initramfs-create" |
| 966 | exit 1 | 978 | exit 1 |
| 967 | fi | 979 | fi |
| 968 | 980 | ||
| @@ -973,61 +985,20 @@ if [ ! -f "$INITRAMFS" ]; then | |||
| 973 | exit 1 | 985 | exit 1 |
| 974 | fi | 986 | fi |
| 975 | 987 | ||
| 976 | # Check for rootfs image (ext4 with Docker tools) | 988 | # Check for rootfs image |
| 977 | if [ ! -f "$ROOTFS_IMG" ]; then | 989 | if [ ! -f "$ROOTFS_IMG" ]; then |
| 978 | log "ERROR" "Rootfs image not found: $ROOTFS_IMG" | 990 | log "ERROR" "Rootfs image not found: $ROOTFS_IMG" |
| 979 | log "ERROR" "Build with: MACHINE=qemuarm64 bitbake vdkr-initramfs-create" | 991 | log "ERROR" "Build with: MACHINE=qemuarm64 bitbake vdkr-initramfs-create" |
| 980 | exit 1 | 992 | exit 1 |
| 981 | fi | 993 | fi |
| 982 | 994 | ||
| 983 | # Find QEMU - check PATH and common locations | 995 | log "DEBUG" "Using initramfs: $INITRAMFS" |
| 984 | if ! command -v "$QEMU_CMD" >/dev/null 2>&1; then | ||
| 985 | # Try common locations | ||
| 986 | for path in \ | ||
| 987 | "${STAGING_BINDIR_NATIVE:-}" \ | ||
| 988 | "/usr/bin"; do | ||
| 989 | if [ -n "$path" ] && [ -x "$path/$QEMU_CMD" ]; then | ||
| 990 | QEMU_CMD="$path/$QEMU_CMD" | ||
| 991 | break | ||
| 992 | fi | ||
| 993 | done | ||
| 994 | fi | ||
| 995 | |||
| 996 | if ! command -v "$QEMU_CMD" >/dev/null 2>&1 && [ ! -x "$QEMU_CMD" ]; then | ||
| 997 | log "ERROR" "QEMU not found: $QEMU_CMD" | ||
| 998 | exit 1 | ||
| 999 | fi | ||
| 1000 | |||
| 1001 | log "DEBUG" "Using QEMU: $QEMU_CMD" | ||
| 1002 | 996 | ||
| 1003 | # Check for KVM acceleration (when host matches target) | 997 | # Let backend prepare container image if needed (e.g., Xen pulls OCI via skopeo) |
| 1004 | USE_KVM="false" | 998 | if type hv_prepare_container >/dev/null 2>&1; then |
| 1005 | if [ "$DISABLE_KVM" = "true" ]; then | 999 | hv_prepare_container |
| 1006 | log "DEBUG" "KVM disabled by --no-kvm flag" | ||
| 1007 | else | ||
| 1008 | HOST_ARCH=$(uname -m) | ||
| 1009 | if [ "$HOST_ARCH" = "$TARGET_ARCH" ] || \ | ||
| 1010 | { [ "$HOST_ARCH" = "x86_64" ] && [ "$TARGET_ARCH" = "x86_64" ]; }; then | ||
| 1011 | if [ -w /dev/kvm ]; then | ||
| 1012 | USE_KVM="true" | ||
| 1013 | # Use host CPU for best performance with KVM | ||
| 1014 | case "$TARGET_ARCH" in | ||
| 1015 | x86_64) | ||
| 1016 | QEMU_MACHINE="-M q35 -cpu host" | ||
| 1017 | ;; | ||
| 1018 | aarch64) | ||
| 1019 | QEMU_MACHINE="-M virt -cpu host" | ||
| 1020 | ;; | ||
| 1021 | esac | ||
| 1022 | log "INFO" "KVM acceleration enabled" | ||
| 1023 | else | ||
| 1024 | log "DEBUG" "KVM not available (no write access to /dev/kvm)" | ||
| 1025 | fi | ||
| 1026 | fi | ||
| 1027 | fi | 1000 | fi |
| 1028 | 1001 | ||
| 1029 | log "DEBUG" "Using initramfs: $INITRAMFS" | ||
| 1030 | |||
| 1031 | # Create input disk image if needed | 1002 | # Create input disk image if needed |
| 1032 | DISK_OPTS="" | 1003 | DISK_OPTS="" |
| 1033 | if [ -n "$INPUT_PATH" ] && [ "$INPUT_TYPE" != "none" ]; then | 1004 | if [ -n "$INPUT_PATH" ] && [ "$INPUT_TYPE" != "none" ]; then |
| @@ -1062,8 +1033,10 @@ if [ -n "$INPUT_PATH" ] && [ "$INPUT_TYPE" != "none" ]; then | |||
| 1062 | fi | 1033 | fi |
| 1063 | 1034 | ||
| 1064 | # Create state disk for persistent storage (--state-dir) | 1035 | # Create state disk for persistent storage (--state-dir) |
| 1036 | # Xen backend skips this: DomU Docker storage lives in the guest's overlay | ||
| 1037 | # filesystem and persists as long as the domain is running (daemon mode). | ||
| 1065 | STATE_DISK_OPTS="" | 1038 | STATE_DISK_OPTS="" |
| 1066 | if [ -n "$STATE_DIR" ]; then | 1039 | if [ -n "$STATE_DIR" ] && ! type hv_skip_state_disk >/dev/null 2>&1; then |
| 1067 | mkdir -p "$STATE_DIR" | 1040 | mkdir -p "$STATE_DIR" |
| 1068 | STATE_IMG="$STATE_DIR/$STATE_FILE" | 1041 | STATE_IMG="$STATE_DIR/$STATE_FILE" |
| 1069 | 1042 | ||
| @@ -1090,6 +1063,10 @@ if [ -n "$STATE_DIR" ]; then | |||
| 1090 | # Combined with graceful shutdown wait, this ensures data integrity | 1063 | # Combined with graceful shutdown wait, this ensures data integrity |
| 1091 | STATE_DISK_OPTS="-drive file=$STATE_IMG,if=virtio,format=raw,cache=directsync" | 1064 | STATE_DISK_OPTS="-drive file=$STATE_IMG,if=virtio,format=raw,cache=directsync" |
| 1092 | log "DEBUG" "State disk: $(ls -lh "$STATE_IMG" | awk '{print $5}')" | 1065 | log "DEBUG" "State disk: $(ls -lh "$STATE_IMG" | awk '{print $5}')" |
| 1066 | elif [ -n "$STATE_DIR" ]; then | ||
| 1067 | # Backend skips state disk but we still need the directory for daemon files | ||
| 1068 | mkdir -p "$STATE_DIR" | ||
| 1069 | log "DEBUG" "State disk: skipped (${VCONTAINER_HYPERVISOR} backend manages guest storage)" | ||
| 1093 | fi | 1070 | fi |
| 1094 | 1071 | ||
| 1095 | # Create state disk from input-storage tar (--input-storage) | 1072 | # Create state disk from input-storage tar (--input-storage) |
| @@ -1147,9 +1124,9 @@ DOCKER_CMD_B64=$(echo -n "$DOCKER_CMD" | base64 -w0) | |||
| 1147 | # In interactive mode, use 'quiet' to suppress kernel boot messages | 1124 | # In interactive mode, use 'quiet' to suppress kernel boot messages |
| 1148 | # Use CMDLINE_PREFIX for runtime-specific parameters (docker_ or podman_) | 1125 | # Use CMDLINE_PREFIX for runtime-specific parameters (docker_ or podman_) |
| 1149 | if [ "$INTERACTIVE" = "true" ]; then | 1126 | if [ "$INTERACTIVE" = "true" ]; then |
| 1150 | KERNEL_APPEND="console=$CONSOLE,115200 quiet loglevel=0 init=/init" | 1127 | KERNEL_APPEND="console=$(hv_get_console_device),115200 quiet loglevel=0 init=/init" |
| 1151 | else | 1128 | else |
| 1152 | KERNEL_APPEND="console=$CONSOLE,115200 init=/init" | 1129 | KERNEL_APPEND="console=$(hv_get_console_device),115200 init=/init" |
| 1153 | fi | 1130 | fi |
| 1154 | # Tell init script which runtime we're using | 1131 | # Tell init script which runtime we're using |
| 1155 | KERNEL_APPEND="$KERNEL_APPEND runtime=$RUNTIME" | 1132 | KERNEL_APPEND="$KERNEL_APPEND runtime=$RUNTIME" |
| @@ -1198,66 +1175,24 @@ if [ "$INTERACTIVE" = "true" ]; then | |||
| 1198 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_interactive=1" | 1175 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_interactive=1" |
| 1199 | fi | 1176 | fi |
| 1200 | 1177 | ||
| 1201 | # Build QEMU command | 1178 | # Build VM configuration via hypervisor backend |
| 1202 | # Drive ordering is important: | 1179 | # Drive ordering is important: |
| 1203 | # /dev/vda = rootfs.img (read-only, ext4 with Docker tools) | 1180 | # rootfs.img (read-only), input disk (if any), state disk (if any) |
| 1204 | # /dev/vdb = input disk (if any) | 1181 | hv_build_disk_opts |
| 1205 | # /dev/vdc = state disk (if any) | 1182 | hv_build_network_opts |
| 1206 | # The preinit script in initramfs mounts /dev/vda and does switch_root | 1183 | hv_build_vm_cmd |
| 1207 | # Build QEMU options | ||
| 1208 | QEMU_OPTS="$QEMU_MACHINE -nographic -smp 2 -m 2048 -no-reboot" | ||
| 1209 | if [ "$USE_KVM" = "true" ]; then | ||
| 1210 | QEMU_OPTS="$QEMU_OPTS -enable-kvm" | ||
| 1211 | fi | ||
| 1212 | QEMU_OPTS="$QEMU_OPTS -kernel $KERNEL_IMAGE" | ||
| 1213 | QEMU_OPTS="$QEMU_OPTS -initrd $INITRAMFS" | ||
| 1214 | QEMU_OPTS="$QEMU_OPTS -drive file=$ROOTFS_IMG,if=virtio,format=raw,readonly=on" | ||
| 1215 | QEMU_OPTS="$QEMU_OPTS $DISK_OPTS" | ||
| 1216 | QEMU_OPTS="$QEMU_OPTS $STATE_DISK_OPTS" | ||
| 1217 | |||
| 1218 | # Add networking if enabled (slirp user-mode networking) | ||
| 1219 | if [ "$NETWORK" = "true" ]; then | ||
| 1220 | # Slirp provides NAT'd outbound connectivity without root privileges | ||
| 1221 | # Guest gets 10.0.2.15, gateway is 10.0.2.2, DNS is 10.0.2.3 | ||
| 1222 | NETDEV_OPTS="user,id=net0" | ||
| 1223 | |||
| 1224 | # Add port forwards - QEMU forwards host:port -> VM:port | ||
| 1225 | # Docker's iptables handles VM:port -> container:port | ||
| 1226 | for pf in "${PORT_FORWARDS[@]}"; do | ||
| 1227 | # Parse host_port:container_port or host_port:container_port/protocol | ||
| 1228 | HOST_PORT="${pf%%:*}" | ||
| 1229 | CONTAINER_PART="${pf#*:}" | ||
| 1230 | CONTAINER_PORT="${CONTAINER_PART%%/*}" | ||
| 1231 | |||
| 1232 | # Check for protocol suffix (default to tcp) | ||
| 1233 | if [[ "$CONTAINER_PART" == */* ]]; then | ||
| 1234 | PROTOCOL="${CONTAINER_PART##*/}" | ||
| 1235 | else | ||
| 1236 | PROTOCOL="tcp" | ||
| 1237 | fi | ||
| 1238 | |||
| 1239 | # Forward to HOST_PORT on VM; Docker -p handles container port mapping | ||
| 1240 | NETDEV_OPTS="$NETDEV_OPTS,hostfwd=$PROTOCOL::$HOST_PORT-:$HOST_PORT" | ||
| 1241 | log "INFO" "Port forward: host:$HOST_PORT -> VM:$HOST_PORT (Docker maps to container:$CONTAINER_PORT)" | ||
| 1242 | done | ||
| 1243 | |||
| 1244 | QEMU_OPTS="$QEMU_OPTS -netdev $NETDEV_OPTS -device virtio-net-pci,netdev=net0" | ||
| 1245 | else | ||
| 1246 | # Explicitly disable networking | ||
| 1247 | QEMU_OPTS="$QEMU_OPTS -nic none" | ||
| 1248 | fi | ||
| 1249 | 1184 | ||
| 1250 | # Batch-import mode: add virtio-9p for fast output (instead of slow console base64) | 1185 | # Batch-import mode: add 9p for fast output (instead of slow console base64) |
| 1251 | if [ "$BATCH_IMPORT" = "true" ]; then | 1186 | if [ "$BATCH_IMPORT" = "true" ]; then |
| 1252 | BATCH_SHARE_DIR="$TEMP_DIR/share" | 1187 | BATCH_SHARE_DIR="$TEMP_DIR/share" |
| 1253 | mkdir -p "$BATCH_SHARE_DIR" | 1188 | mkdir -p "$BATCH_SHARE_DIR" |
| 1254 | SHARE_TAG="${TOOL_NAME}_share" | 1189 | SHARE_TAG="${TOOL_NAME}_share" |
| 1255 | QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$BATCH_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,id=$SHARE_TAG" | 1190 | HV_OPTS="$HV_OPTS $(hv_build_9p_opts "$BATCH_SHARE_DIR" "$SHARE_TAG")" |
| 1256 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" | 1191 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" |
| 1257 | log "INFO" "Using virtio-9p for fast storage output" | 1192 | log "INFO" "Using 9p for fast storage output" |
| 1258 | fi | 1193 | fi |
| 1259 | 1194 | ||
| 1260 | # Daemon mode: add virtio-serial for command channel | 1195 | # Daemon mode: add serial channel for command I/O |
| 1261 | if [ "$DAEMON_MODE" = "start" ]; then | 1196 | if [ "$DAEMON_MODE" = "start" ]; then |
| 1262 | # Check for required tools | 1197 | # Check for required tools |
| 1263 | if ! command -v socat >/dev/null 2>&1; then | 1198 | if ! command -v socat >/dev/null 2>&1; then |
| @@ -1275,30 +1210,18 @@ if [ "$DAEMON_MODE" = "start" ]; then | |||
| 1275 | # Create socket directory | 1210 | # Create socket directory |
| 1276 | mkdir -p "$DAEMON_SOCKET_DIR" | 1211 | mkdir -p "$DAEMON_SOCKET_DIR" |
| 1277 | 1212 | ||
| 1278 | # Create shared directory for file I/O (virtio-9p) | 1213 | # Create shared directory for file I/O (9p) |
| 1279 | DAEMON_SHARE_DIR="$DAEMON_SOCKET_DIR/share" | 1214 | DAEMON_SHARE_DIR="$DAEMON_SOCKET_DIR/share" |
| 1280 | mkdir -p "$DAEMON_SHARE_DIR" | 1215 | mkdir -p "$DAEMON_SHARE_DIR" |
| 1281 | 1216 | ||
| 1282 | # Add virtio-9p for shared directory access | 1217 | # Add 9p for shared directory access |
| 1283 | # Host writes to $DAEMON_SHARE_DIR, guest mounts as /mnt/share | ||
| 1284 | # Use runtime-specific mount tag (vdkr_share or vpdmn_share) | ||
| 1285 | SHARE_TAG="${TOOL_NAME}_share" | 1218 | SHARE_TAG="${TOOL_NAME}_share" |
| 1286 | # Use security_model=none for simplest file sharing (no permission mapping) | 1219 | HV_OPTS="$HV_OPTS $(hv_build_9p_opts "$DAEMON_SHARE_DIR" "$SHARE_TAG")" |
| 1287 | # This allows writes from container (running as root) to propagate to host | ||
| 1288 | QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$DAEMON_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,id=$SHARE_TAG" | ||
| 1289 | # Tell init script to mount the share | ||
| 1290 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" | 1220 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" |
| 1291 | 1221 | ||
| 1292 | # Add virtio-serial device for command channel | 1222 | # Add daemon command channel (backend-specific: virtio-serial or PV console) |
| 1293 | # Using virtserialport creates /dev/vport0p1 in guest, host sees unix socket | 1223 | hv_build_daemon_opts |
| 1294 | # virtconsole would use hvc* but requires virtio_console kernel module | 1224 | HV_OPTS="$HV_OPTS $HV_DAEMON_OPTS" |
| 1295 | QEMU_OPTS="$QEMU_OPTS -chardev socket,id=vdkr,path=$DAEMON_SOCKET,server=on,wait=off" | ||
| 1296 | QEMU_OPTS="$QEMU_OPTS -device virtio-serial-pci" | ||
| 1297 | QEMU_OPTS="$QEMU_OPTS -device virtserialport,chardev=vdkr,name=vdkr" | ||
| 1298 | |||
| 1299 | # Add QMP socket for dynamic control (port forwarding, etc.) | ||
| 1300 | QMP_SOCKET="$DAEMON_SOCKET_DIR/qmp.sock" | ||
| 1301 | QEMU_OPTS="$QEMU_OPTS -qmp unix:$QMP_SOCKET,server,nowait" | ||
| 1302 | 1225 | ||
| 1303 | # Tell init script to run in daemon mode with idle timeout | 1226 | # Tell init script to run in daemon mode with idle timeout |
| 1304 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_daemon=1" | 1227 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_daemon=1" |
| @@ -1308,36 +1231,18 @@ if [ "$DAEMON_MODE" = "start" ]; then | |||
| 1308 | if [ "$NETWORK" != "true" ]; then | 1231 | if [ "$NETWORK" != "true" ]; then |
| 1309 | log "INFO" "Enabling networking for daemon mode" | 1232 | log "INFO" "Enabling networking for daemon mode" |
| 1310 | NETWORK="true" | 1233 | NETWORK="true" |
| 1311 | # Build netdev options with any port forwards | 1234 | hv_build_network_opts |
| 1312 | DAEMON_NETDEV="user,id=net0" | 1235 | # Re-add network opts (they were built without port forwards initially) |
| 1236 | # The rebuild includes port forwards since NETWORK is now true | ||
| 1237 | fi | ||
| 1238 | # Ensure port forwards are logged | ||
| 1239 | if [ ${#PORT_FORWARDS[@]} -gt 0 ]; then | ||
| 1313 | for pf in "${PORT_FORWARDS[@]}"; do | 1240 | for pf in "${PORT_FORWARDS[@]}"; do |
| 1314 | # Parse host_port:container_port or host_port:container_port/protocol | ||
| 1315 | HOST_PORT="${pf%%:*}" | 1241 | HOST_PORT="${pf%%:*}" |
| 1316 | CONTAINER_PART="${pf#*:}" | 1242 | CONTAINER_PART="${pf#*:}" |
| 1317 | CONTAINER_PORT="${CONTAINER_PART%%/*}" | 1243 | CONTAINER_PORT="${CONTAINER_PART%%/*}" |
| 1318 | if [[ "$CONTAINER_PART" == */* ]]; then | 1244 | log "INFO" "Port forward configured: $HOST_PORT -> $CONTAINER_PORT" |
| 1319 | PROTOCOL="${CONTAINER_PART##*/}" | ||
| 1320 | else | ||
| 1321 | PROTOCOL="tcp" | ||
| 1322 | fi | ||
| 1323 | # Forward to HOST_PORT on VM; Docker -p handles container port mapping | ||
| 1324 | DAEMON_NETDEV="$DAEMON_NETDEV,hostfwd=$PROTOCOL::$HOST_PORT-:$HOST_PORT" | ||
| 1325 | log "INFO" "Port forward: host:$HOST_PORT -> VM:$HOST_PORT (Docker maps to container:$CONTAINER_PORT)" | ||
| 1326 | done | 1245 | done |
| 1327 | QEMU_OPTS="$QEMU_OPTS -netdev $DAEMON_NETDEV -device virtio-net-pci,netdev=net0" | ||
| 1328 | else | ||
| 1329 | # NETWORK was already true, but check if we need to add port forwards | ||
| 1330 | # that weren't included in the earlier networking setup | ||
| 1331 | # (This happens when NETWORK was set to true before daemon mode was detected) | ||
| 1332 | if [ ${#PORT_FORWARDS[@]} -gt 0 ]; then | ||
| 1333 | # Port forwards should already be included from earlier networking setup | ||
| 1334 | for pf in "${PORT_FORWARDS[@]}"; do | ||
| 1335 | HOST_PORT="${pf%%:*}" | ||
| 1336 | CONTAINER_PART="${pf#*:}" | ||
| 1337 | CONTAINER_PORT="${CONTAINER_PART%%/*}" | ||
| 1338 | log "INFO" "Port forward configured: $HOST_PORT -> $CONTAINER_PORT" | ||
| 1339 | done | ||
| 1340 | fi | ||
| 1341 | fi | 1246 | fi |
| 1342 | 1247 | ||
| 1343 | # Copy CA certificate to shared folder (too large for kernel cmdline) | 1248 | # Copy CA certificate to shared folder (too large for kernel cmdline) |
| @@ -1349,23 +1254,23 @@ if [ "$DAEMON_MODE" = "start" ]; then | |||
| 1349 | log "INFO" "Starting daemon..." | 1254 | log "INFO" "Starting daemon..." |
| 1350 | log "DEBUG" "PID file: $DAEMON_PID_FILE" | 1255 | log "DEBUG" "PID file: $DAEMON_PID_FILE" |
| 1351 | log "DEBUG" "Socket: $DAEMON_SOCKET" | 1256 | log "DEBUG" "Socket: $DAEMON_SOCKET" |
| 1352 | log "DEBUG" "Command: $QEMU_CMD $QEMU_OPTS -append \"$KERNEL_APPEND\"" | ||
| 1353 | 1257 | ||
| 1354 | # Start QEMU in background | 1258 | # Start VM in background via backend |
| 1355 | $QEMU_CMD $QEMU_OPTS -append "$KERNEL_APPEND" > "$DAEMON_QEMU_LOG" 2>&1 & | 1259 | hv_start_vm_background "$KERNEL_APPEND" "$DAEMON_QEMU_LOG" "" |
| 1356 | QEMU_PID=$! | 1260 | echo "$HV_VM_PID" > "$DAEMON_PID_FILE" |
| 1357 | echo "$QEMU_PID" > "$DAEMON_PID_FILE" | ||
| 1358 | 1261 | ||
| 1359 | log "INFO" "QEMU started (PID: $QEMU_PID)" | 1262 | # Let backend save any extra state (e.g. Xen domain name) |
| 1263 | if type hv_daemon_save_state >/dev/null 2>&1; then | ||
| 1264 | hv_daemon_save_state | ||
| 1265 | fi | ||
| 1360 | 1266 | ||
| 1361 | # Wait for socket to appear (Docker starting) | 1267 | log "INFO" "VM started (PID: $HV_VM_PID)" |
| 1362 | # Docker can take 60+ seconds to start, so wait up to 120 seconds | 1268 | |
| 1269 | # Wait for socket to appear (container runtime starting) | ||
| 1363 | log "INFO" "Waiting for daemon to be ready..." | 1270 | log "INFO" "Waiting for daemon to be ready..." |
| 1364 | READY=false | 1271 | READY=false |
| 1365 | for i in $(seq 1 120); do | 1272 | for i in $(seq 1 120); do |
| 1366 | if [ -S "$DAEMON_SOCKET" ]; then | 1273 | if [ -S "$DAEMON_SOCKET" ]; then |
| 1367 | # Socket exists, try to connect | ||
| 1368 | # Keep stdin open for 3 seconds to allow response to arrive | ||
| 1369 | RESPONSE=$( { echo "===PING==="; sleep 3; } | timeout 10 socat - "UNIX-CONNECT:$DAEMON_SOCKET" 2>/dev/null || true) | 1274 | RESPONSE=$( { echo "===PING==="; sleep 3; } | timeout 10 socat - "UNIX-CONNECT:$DAEMON_SOCKET" 2>/dev/null || true) |
| 1370 | if echo "$RESPONSE" | grep -q "===PONG==="; then | 1275 | if echo "$RESPONSE" | grep -q "===PONG==="; then |
| 1371 | log "DEBUG" "Got PONG response" | 1276 | log "DEBUG" "Got PONG response" |
| @@ -1376,64 +1281,54 @@ if [ "$DAEMON_MODE" = "start" ]; then | |||
| 1376 | fi | 1281 | fi |
| 1377 | fi | 1282 | fi |
| 1378 | 1283 | ||
| 1379 | # Check if QEMU died | 1284 | # Check if VM died |
| 1380 | if ! kill -0 "$QEMU_PID" 2>/dev/null; then | 1285 | if ! hv_is_vm_running; then |
| 1381 | log "ERROR" "QEMU process died during startup" | 1286 | log "ERROR" "VM process died during startup" |
| 1382 | cat "$DAEMON_QEMU_LOG" >&2 | 1287 | cat "$DAEMON_QEMU_LOG" >&2 |
| 1383 | rm -f "$DAEMON_PID_FILE" | 1288 | rm -f "$DAEMON_PID_FILE" |
| 1384 | exit 1 | 1289 | exit 1 |
| 1385 | fi | 1290 | fi |
| 1386 | 1291 | ||
| 1387 | log "DEBUG" "Waiting... ($i/60)" | 1292 | log "DEBUG" "Waiting... ($i/120)" |
| 1388 | sleep 1 | 1293 | sleep 1 |
| 1389 | done | 1294 | done |
| 1390 | 1295 | ||
| 1391 | if [ "$READY" = "true" ]; then | 1296 | if [ "$READY" = "true" ]; then |
| 1392 | log "INFO" "Daemon is ready!" | 1297 | log "INFO" "Daemon is ready!" |
| 1393 | 1298 | ||
| 1299 | # Set up port forwards via backend (e.g., iptables for Xen) | ||
| 1300 | hv_setup_port_forwards | ||
| 1301 | |||
| 1394 | # Start host-side idle watchdog if timeout is set | 1302 | # Start host-side idle watchdog if timeout is set |
| 1395 | if [ "$IDLE_TIMEOUT" -gt 0 ] 2>/dev/null; then | 1303 | if [ "$IDLE_TIMEOUT" -gt 0 ] 2>/dev/null; then |
| 1396 | ACTIVITY_FILE="$DAEMON_SOCKET_DIR/activity" | 1304 | ACTIVITY_FILE="$DAEMON_SOCKET_DIR/activity" |
| 1397 | touch "$ACTIVITY_FILE" | 1305 | touch "$ACTIVITY_FILE" |
| 1398 | 1306 | ||
| 1399 | # Spawn background watchdog | ||
| 1400 | ( | 1307 | ( |
| 1401 | # Container status file - guest writes this via virtio-9p share | ||
| 1402 | # This avoids sending commands through daemon socket which corrupts output | ||
| 1403 | CONTAINER_STATUS_FILE="$DAEMON_SHARE_DIR/.containers_running" | 1308 | CONTAINER_STATUS_FILE="$DAEMON_SHARE_DIR/.containers_running" |
| 1404 | |||
| 1405 | # Scale check interval to idle timeout (check ~5 times before timeout) | ||
| 1406 | CHECK_INTERVAL=$((IDLE_TIMEOUT / 5)) | 1309 | CHECK_INTERVAL=$((IDLE_TIMEOUT / 5)) |
| 1407 | [ "$CHECK_INTERVAL" -lt 10 ] && CHECK_INTERVAL=10 | 1310 | [ "$CHECK_INTERVAL" -lt 10 ] && CHECK_INTERVAL=10 |
| 1408 | [ "$CHECK_INTERVAL" -gt 60 ] && CHECK_INTERVAL=60 | 1311 | [ "$CHECK_INTERVAL" -gt 60 ] && CHECK_INTERVAL=60 |
| 1409 | 1312 | ||
| 1410 | while true; do | 1313 | while true; do |
| 1411 | sleep "$CHECK_INTERVAL" | 1314 | sleep "$CHECK_INTERVAL" |
| 1412 | [ -f "$ACTIVITY_FILE" ] || exit 0 # Clean exit if file removed | 1315 | [ -f "$ACTIVITY_FILE" ] || exit 0 |
| 1413 | [ -f "$DAEMON_PID_FILE" ] || exit 0 # PID file gone | 1316 | [ -f "$DAEMON_PID_FILE" ] || exit 0 |
| 1414 | 1317 | ||
| 1415 | # Check if QEMU process is still running | 1318 | # Check if VM is still running (backend-aware) |
| 1416 | QEMU_PID=$(cat "$DAEMON_PID_FILE" 2>/dev/null) | 1319 | hv_is_vm_running || exit 0 |
| 1417 | [ -n "$QEMU_PID" ] && kill -0 "$QEMU_PID" 2>/dev/null || exit 0 | ||
| 1418 | 1320 | ||
| 1419 | LAST_ACTIVITY=$(stat -c %Y "$ACTIVITY_FILE" 2>/dev/null || echo 0) | 1321 | LAST_ACTIVITY=$(stat -c %Y "$ACTIVITY_FILE" 2>/dev/null || echo 0) |
| 1420 | NOW=$(date +%s) | 1322 | NOW=$(date +%s) |
| 1421 | IDLE_SECONDS=$((NOW - LAST_ACTIVITY)) | 1323 | IDLE_SECONDS=$((NOW - LAST_ACTIVITY)) |
| 1422 | 1324 | ||
| 1423 | if [ "$IDLE_SECONDS" -ge "$IDLE_TIMEOUT" ]; then | 1325 | if [ "$IDLE_SECONDS" -ge "$IDLE_TIMEOUT" ]; then |
| 1424 | # Check if any containers are running via shared file | ||
| 1425 | # Guest-side watchdog writes container IDs to this file | ||
| 1426 | if [ -f "$CONTAINER_STATUS_FILE" ] && [ -s "$CONTAINER_STATUS_FILE" ]; then | 1326 | if [ -f "$CONTAINER_STATUS_FILE" ] && [ -s "$CONTAINER_STATUS_FILE" ]; then |
| 1427 | # Containers are running - reset activity and skip shutdown | ||
| 1428 | touch "$ACTIVITY_FILE" | 1327 | touch "$ACTIVITY_FILE" |
| 1429 | continue | 1328 | continue |
| 1430 | fi | 1329 | fi |
| 1431 | 1330 | # Use backend-specific idle shutdown | |
| 1432 | # No containers running - send QMP quit to gracefully stop QEMU | 1331 | hv_idle_shutdown |
| 1433 | if [ -S "$QMP_SOCKET" ]; then | ||
| 1434 | echo '{"execute":"qmp_capabilities"}{"execute":"quit"}' | \ | ||
| 1435 | socat - "UNIX-CONNECT:$QMP_SOCKET" >/dev/null 2>&1 || true | ||
| 1436 | fi | ||
| 1437 | rm -f "$ACTIVITY_FILE" | 1332 | rm -f "$ACTIVITY_FILE" |
| 1438 | exit 0 | 1333 | exit 0 |
| 1439 | fi | 1334 | fi |
| @@ -1442,119 +1337,103 @@ if [ "$DAEMON_MODE" = "start" ]; then | |||
| 1442 | log "DEBUG" "Started host-side idle watchdog (timeout: ${IDLE_TIMEOUT}s)" | 1337 | log "DEBUG" "Started host-side idle watchdog (timeout: ${IDLE_TIMEOUT}s)" |
| 1443 | fi | 1338 | fi |
| 1444 | 1339 | ||
| 1445 | echo "Daemon running (PID: $QEMU_PID)" | 1340 | echo "Daemon running (PID: $HV_VM_PID)" |
| 1446 | echo "Socket: $DAEMON_SOCKET" | 1341 | echo "Socket: $DAEMON_SOCKET" |
| 1447 | exit 0 | 1342 | exit 0 |
| 1448 | else | 1343 | else |
| 1449 | log "ERROR" "Daemon failed to become ready within 120 seconds" | 1344 | log "ERROR" "Daemon failed to become ready within 120 seconds" |
| 1450 | cat "$DAEMON_QEMU_LOG" >&2 | 1345 | cat "$DAEMON_QEMU_LOG" >&2 |
| 1451 | kill "$QEMU_PID" 2>/dev/null || true | 1346 | hv_destroy_vm |
| 1452 | rm -f "$DAEMON_PID_FILE" "$DAEMON_SOCKET" | 1347 | rm -f "$DAEMON_PID_FILE" "$DAEMON_SOCKET" |
| 1453 | exit 1 | 1348 | exit 1 |
| 1454 | fi | 1349 | fi |
| 1455 | fi | 1350 | fi |
| 1456 | 1351 | ||
| 1457 | # For non-daemon mode with CA cert, we need virtio-9p to pass the cert | 1352 | # For non-daemon mode with CA cert, we need 9p to pass the cert |
| 1458 | # (kernel cmdline is too small for base64-encoded certs) | ||
| 1459 | if [ -n "$CA_CERT" ] && [ -f "$CA_CERT" ]; then | 1353 | if [ -n "$CA_CERT" ] && [ -f "$CA_CERT" ]; then |
| 1460 | # Create temp share dir for CA cert | ||
| 1461 | CA_SHARE_DIR="$TEMP_DIR/ca_share" | 1354 | CA_SHARE_DIR="$TEMP_DIR/ca_share" |
| 1462 | mkdir -p "$CA_SHARE_DIR" | 1355 | mkdir -p "$CA_SHARE_DIR" |
| 1463 | cp "$CA_CERT" "$CA_SHARE_DIR/ca.crt" | 1356 | cp "$CA_CERT" "$CA_SHARE_DIR/ca.crt" |
| 1464 | 1357 | ||
| 1465 | # Add virtio-9p mount for CA cert | ||
| 1466 | SHARE_TAG="${TOOL_NAME}_share" | 1358 | SHARE_TAG="${TOOL_NAME}_share" |
| 1467 | QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$CA_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,readonly=on,id=cashare" | 1359 | HV_OPTS="$HV_OPTS $(hv_build_9p_opts "$CA_SHARE_DIR" "$SHARE_TAG" "readonly=on")" |
| 1468 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" | 1360 | KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" |
| 1469 | log "DEBUG" "CA certificate available via virtio-9p" | 1361 | log "DEBUG" "CA certificate available via 9p" |
| 1470 | fi | 1362 | fi |
| 1471 | 1363 | ||
| 1472 | log "INFO" "Starting QEMU..." | 1364 | log "INFO" "Starting VM ($VCONTAINER_HYPERVISOR)..." |
| 1473 | log "DEBUG" "Command: $QEMU_CMD $QEMU_OPTS -append \"$KERNEL_APPEND\"" | ||
| 1474 | 1365 | ||
| 1475 | # Interactive mode runs QEMU in foreground with stdio connected | 1366 | # Interactive mode runs VM in foreground with stdio connected |
| 1476 | if [ "$INTERACTIVE" = "true" ]; then | 1367 | if [ "$INTERACTIVE" = "true" ]; then |
| 1477 | # Check if stdin is a terminal | ||
| 1478 | if [ ! -t 0 ]; then | 1368 | if [ ! -t 0 ]; then |
| 1479 | log "WARN" "Interactive mode requested but stdin is not a terminal" | 1369 | log "WARN" "Interactive mode requested but stdin is not a terminal" |
| 1480 | fi | 1370 | fi |
| 1481 | 1371 | ||
| 1482 | # Show a starting message | ||
| 1483 | # The init script will clear this line when the container is ready | ||
| 1484 | if [ -t 1 ]; then | 1372 | if [ -t 1 ]; then |
| 1485 | printf "\r\033[0;36m[vdkr]\033[0m Starting container... \r" | 1373 | printf "\r\033[0;36m[${TOOL_NAME}]\033[0m Starting container... \r" |
| 1486 | fi | 1374 | fi |
| 1487 | 1375 | ||
| 1488 | # Save terminal settings to restore later | ||
| 1489 | if [ -t 0 ]; then | 1376 | if [ -t 0 ]; then |
| 1490 | SAVED_STTY=$(stty -g) | 1377 | SAVED_STTY=$(stty -g) |
| 1491 | # Put terminal in raw mode so Ctrl+C etc go to guest | ||
| 1492 | stty raw -echo | 1378 | stty raw -echo |
| 1493 | fi | 1379 | fi |
| 1494 | 1380 | ||
| 1495 | # Run QEMU with stdio (not in background) | 1381 | hv_start_vm_foreground "$KERNEL_APPEND" |
| 1496 | # The -serial mon:stdio connects the serial console to our terminal | 1382 | VM_EXIT=$? |
| 1497 | $QEMU_CMD $QEMU_OPTS -append "$KERNEL_APPEND" | ||
| 1498 | QEMU_EXIT=$? | ||
| 1499 | 1383 | ||
| 1500 | # Restore terminal settings | ||
| 1501 | if [ -t 0 ]; then | 1384 | if [ -t 0 ]; then |
| 1502 | stty "$SAVED_STTY" | 1385 | stty "$SAVED_STTY" |
| 1503 | fi | 1386 | fi |
| 1504 | 1387 | ||
| 1505 | echo "" | 1388 | echo "" |
| 1506 | log "INFO" "Interactive session ended (exit code: $QEMU_EXIT)" | 1389 | log "INFO" "Interactive session ended (exit code: $VM_EXIT)" |
| 1507 | exit $QEMU_EXIT | 1390 | exit $VM_EXIT |
| 1508 | fi | 1391 | fi |
| 1509 | 1392 | ||
| 1510 | # Non-interactive mode: run QEMU in background and capture output | 1393 | # Non-interactive mode: run VM in background and capture output |
| 1511 | QEMU_OUTPUT="$TEMP_DIR/qemu_output.txt" | 1394 | VM_OUTPUT="$TEMP_DIR/vm_output.txt" |
| 1512 | timeout $TIMEOUT $QEMU_CMD $QEMU_OPTS -append "$KERNEL_APPEND" > "$QEMU_OUTPUT" 2>&1 & | 1395 | hv_start_vm_background "$KERNEL_APPEND" "$VM_OUTPUT" "$TIMEOUT" |
| 1513 | QEMU_PID=$! | ||
| 1514 | 1396 | ||
| 1515 | # Monitor for completion | 1397 | # Monitor for completion |
| 1516 | COMPLETE=false | 1398 | COMPLETE=false |
| 1517 | for i in $(seq 1 $TIMEOUT); do | 1399 | for i in $(seq 1 $TIMEOUT); do |
| 1518 | if [ ! -d "/proc/$QEMU_PID" ]; then | 1400 | if ! hv_is_vm_running; then |
| 1519 | log "DEBUG" "QEMU ended after $i seconds" | 1401 | log "DEBUG" "VM ended after $i seconds" |
| 1520 | break | 1402 | break |
| 1521 | fi | 1403 | fi |
| 1522 | 1404 | ||
| 1523 | # Check for completion markers based on output type | 1405 | # Check for completion markers based on output type |
| 1524 | case "$OUTPUT_TYPE" in | 1406 | case "$OUTPUT_TYPE" in |
| 1525 | text) | 1407 | text) |
| 1526 | if grep -q "===OUTPUT_END===" "$QEMU_OUTPUT" 2>/dev/null; then | 1408 | if grep -q "===OUTPUT_END===" "$VM_OUTPUT" 2>/dev/null; then |
| 1527 | COMPLETE=true | 1409 | COMPLETE=true |
| 1528 | break | 1410 | break |
| 1529 | fi | 1411 | fi |
| 1530 | ;; | 1412 | ;; |
| 1531 | tar) | 1413 | tar) |
| 1532 | if grep -q "===TAR_END===" "$QEMU_OUTPUT" 2>/dev/null; then | 1414 | if grep -q "===TAR_END===" "$VM_OUTPUT" 2>/dev/null; then |
| 1533 | COMPLETE=true | 1415 | COMPLETE=true |
| 1534 | break | 1416 | break |
| 1535 | fi | 1417 | fi |
| 1536 | ;; | 1418 | ;; |
| 1537 | storage) | 1419 | storage) |
| 1538 | # Check for both console (STORAGE_END) and virtio-9p (9P_STORAGE_DONE) markers | 1420 | if grep -qE "===STORAGE_END===|===9P_STORAGE_DONE===" "$VM_OUTPUT" 2>/dev/null; then |
| 1539 | if grep -qE "===STORAGE_END===|===9P_STORAGE_DONE===" "$QEMU_OUTPUT" 2>/dev/null; then | ||
| 1540 | COMPLETE=true | 1421 | COMPLETE=true |
| 1541 | break | 1422 | break |
| 1542 | fi | 1423 | fi |
| 1543 | ;; | 1424 | ;; |
| 1544 | esac | 1425 | esac |
| 1545 | 1426 | ||
| 1546 | # Check for error | 1427 | if grep -q "===ERROR===" "$VM_OUTPUT" 2>/dev/null; then |
| 1547 | if grep -q "===ERROR===" "$QEMU_OUTPUT" 2>/dev/null; then | 1428 | log "ERROR" "Error in VM:" |
| 1548 | log "ERROR" "Error in QEMU:" | 1429 | grep -A10 "===ERROR===" "$VM_OUTPUT" |
| 1549 | grep -A10 "===ERROR===" "$QEMU_OUTPUT" | ||
| 1550 | break | 1430 | break |
| 1551 | fi | 1431 | fi |
| 1552 | 1432 | ||
| 1553 | # Progress indicator | ||
| 1554 | if [ $((i % 30)) -eq 0 ]; then | 1433 | if [ $((i % 30)) -eq 0 ]; then |
| 1555 | if grep -q "Docker daemon is ready" "$QEMU_OUTPUT" 2>/dev/null; then | 1434 | if grep -q "Docker daemon is ready" "$VM_OUTPUT" 2>/dev/null; then |
| 1556 | log "INFO" "Docker is running, executing command..." | 1435 | log "INFO" "Docker is running, executing command..." |
| 1557 | elif grep -q "Starting Docker" "$QEMU_OUTPUT" 2>/dev/null; then | 1436 | elif grep -q "Starting Docker" "$VM_OUTPUT" 2>/dev/null; then |
| 1558 | log "INFO" "Docker is starting..." | 1437 | log "INFO" "Docker is starting..." |
| 1559 | fi | 1438 | fi |
| 1560 | fi | 1439 | fi |
| @@ -1562,38 +1441,28 @@ for i in $(seq 1 $TIMEOUT); do | |||
| 1562 | sleep 1 | 1441 | sleep 1 |
| 1563 | done | 1442 | done |
| 1564 | 1443 | ||
| 1565 | # Wait for QEMU to exit gracefully (poweroff from inside flushes disks properly) | 1444 | # Wait for VM to exit gracefully (poweroff from inside flushes disks properly) |
| 1566 | # Only kill if it hangs after seeing completion marker | 1445 | if [ "$COMPLETE" = "true" ] && hv_is_vm_running; then |
| 1567 | if [ "$COMPLETE" = "true" ] && [ -d "/proc/$QEMU_PID" ]; then | 1446 | log "DEBUG" "Waiting for VM to complete graceful shutdown..." |
| 1568 | log "DEBUG" "Waiting for QEMU to complete graceful shutdown..." | 1447 | hv_wait_vm_exit 30 && log "DEBUG" "VM shutdown complete" |
| 1569 | # Give QEMU up to 30 seconds to poweroff after command completes | ||
| 1570 | for wait_i in $(seq 1 30); do | ||
| 1571 | if [ ! -d "/proc/$QEMU_PID" ]; then | ||
| 1572 | log "DEBUG" "QEMU shutdown complete" | ||
| 1573 | break | ||
| 1574 | fi | ||
| 1575 | sleep 1 | ||
| 1576 | done | ||
| 1577 | fi | 1448 | fi |
| 1578 | 1449 | ||
| 1579 | # Force kill QEMU only if still running after grace period | 1450 | # Force kill VM only if still running after grace period |
| 1580 | if [ -d "/proc/$QEMU_PID" ]; then | 1451 | if hv_is_vm_running; then |
| 1581 | log "WARN" "QEMU still running, forcing termination..." | 1452 | hv_stop_vm |
| 1582 | kill $QEMU_PID 2>/dev/null || true | ||
| 1583 | wait $QEMU_PID 2>/dev/null || true | ||
| 1584 | fi | 1453 | fi |
| 1585 | 1454 | ||
| 1586 | # Extract results | 1455 | # Extract results |
| 1587 | if [ "$COMPLETE" = "true" ]; then | 1456 | if [ "$COMPLETE" = "true" ]; then |
| 1588 | # Get exit code | 1457 | # Get exit code |
| 1589 | EXIT_CODE=$(grep -oP '===EXIT_CODE=\K[0-9]+' "$QEMU_OUTPUT" | head -1) | 1458 | EXIT_CODE=$(grep -oP '===EXIT_CODE=\K[0-9]+' "$VM_OUTPUT" | head -1) |
| 1590 | EXIT_CODE="${EXIT_CODE:-0}" | 1459 | EXIT_CODE="${EXIT_CODE:-0}" |
| 1591 | 1460 | ||
| 1592 | case "$OUTPUT_TYPE" in | 1461 | case "$OUTPUT_TYPE" in |
| 1593 | text) | 1462 | text) |
| 1594 | log "INFO" "=== Command Output ===" | 1463 | log "INFO" "=== Command Output ===" |
| 1595 | # Use awk for precise extraction between markers | 1464 | # Use awk for precise extraction between markers |
| 1596 | awk '/===OUTPUT_START===/{capture=1; next} /===OUTPUT_END===/{capture=0} capture' "$QEMU_OUTPUT" | 1465 | awk '/===OUTPUT_START===/{capture=1; next} /===OUTPUT_END===/{capture=0} capture' "$VM_OUTPUT" |
| 1597 | log "INFO" "=== Exit Code: $EXIT_CODE ===" | 1466 | log "INFO" "=== Exit Code: $EXIT_CODE ===" |
| 1598 | ;; | 1467 | ;; |
| 1599 | 1468 | ||
| @@ -1601,7 +1470,7 @@ if [ "$COMPLETE" = "true" ]; then | |||
| 1601 | log "INFO" "Extracting tar output..." | 1470 | log "INFO" "Extracting tar output..." |
| 1602 | # Use awk for precise extraction between markers | 1471 | # Use awk for precise extraction between markers |
| 1603 | # Strip ANSI escape codes and non-base64 characters from serial console output | 1472 | # Strip ANSI escape codes and non-base64 characters from serial console output |
| 1604 | awk '/===TAR_START===/{capture=1; next} /===TAR_END===/{capture=0} capture' "$QEMU_OUTPUT" | \ | 1473 | awk '/===TAR_START===/{capture=1; next} /===TAR_END===/{capture=0} capture' "$VM_OUTPUT" | \ |
| 1605 | tr -d '\r' | sed 's/\x1b\[[0-9;]*m//g' | tr -cd 'A-Za-z0-9+/=\n' | base64 -d > "$OUTPUT_FILE" 2>"${TEMP_DIR}/b64_errors.txt" | 1474 | tr -d '\r' | sed 's/\x1b\[[0-9;]*m//g' | tr -cd 'A-Za-z0-9+/=\n' | base64 -d > "$OUTPUT_FILE" 2>"${TEMP_DIR}/b64_errors.txt" |
| 1606 | 1475 | ||
| 1607 | if [ -s "${TEMP_DIR}/b64_errors.txt" ]; then | 1476 | if [ -s "${TEMP_DIR}/b64_errors.txt" ]; then |
| @@ -1634,7 +1503,7 @@ if [ "$COMPLETE" = "true" ]; then | |||
| 1634 | # 3. sed: remove ANSI escape codes | 1503 | # 3. sed: remove ANSI escape codes |
| 1635 | # 4. grep -v: remove kernel log messages (lines starting with [ followed by timestamp) | 1504 | # 4. grep -v: remove kernel log messages (lines starting with [ followed by timestamp) |
| 1636 | # 5. tr -cd: keep only valid base64 characters | 1505 | # 5. tr -cd: keep only valid base64 characters |
| 1637 | awk '/===STORAGE_START===/{capture=1; next} /===STORAGE_END===/{capture=0} capture' "$QEMU_OUTPUT" | \ | 1506 | awk '/===STORAGE_START===/{capture=1; next} /===STORAGE_END===/{capture=0} capture' "$VM_OUTPUT" | \ |
| 1638 | tr -d '\r' | \ | 1507 | tr -d '\r' | \ |
| 1639 | sed 's/\x1b\[[0-9;]*m//g' | \ | 1508 | sed 's/\x1b\[[0-9;]*m//g' | \ |
| 1640 | grep -v '^\[[[:space:]]*[0-9]' | \ | 1509 | grep -v '^\[[[:space:]]*[0-9]' | \ |
| @@ -1675,11 +1544,11 @@ if [ "$COMPLETE" = "true" ]; then | |||
| 1675 | exit "${EXIT_CODE:-0}" | 1544 | exit "${EXIT_CODE:-0}" |
| 1676 | else | 1545 | else |
| 1677 | log "ERROR" "Command execution failed or timed out" | 1546 | log "ERROR" "Command execution failed or timed out" |
| 1678 | log "ERROR" "QEMU output saved to: $QEMU_OUTPUT" | 1547 | log "ERROR" "QEMU output saved to: $VM_OUTPUT" |
| 1679 | 1548 | ||
| 1680 | if [ "$VERBOSE" = "true" ]; then | 1549 | if [ "$VERBOSE" = "true" ]; then |
| 1681 | log "DEBUG" "=== Last 50 lines of QEMU output ===" | 1550 | log "DEBUG" "=== Last 50 lines of QEMU output ===" |
| 1682 | tail -50 "$QEMU_OUTPUT" | 1551 | tail -50 "$VM_OUTPUT" |
| 1683 | fi | 1552 | fi |
| 1684 | 1553 | ||
| 1685 | exit 1 | 1554 | exit 1 |
