diff options
Diffstat (limited to 'recipes-containers/vcontainer/files/vxn-init.sh')
| -rwxr-xr-x | recipes-containers/vcontainer/files/vxn-init.sh | 262 |
1 files changed, 222 insertions, 40 deletions
diff --git a/recipes-containers/vcontainer/files/vxn-init.sh b/recipes-containers/vcontainer/files/vxn-init.sh index 93e631e1..4b9b630a 100755 --- a/recipes-containers/vcontainer/files/vxn-init.sh +++ b/recipes-containers/vcontainer/files/vxn-init.sh | |||
| @@ -22,9 +22,10 @@ | |||
| 22 | # docker_output=<type> Output type: text (default: text) | 22 | # docker_output=<type> Output type: text (default: text) |
| 23 | # docker_network=1 Enable networking | 23 | # docker_network=1 Enable networking |
| 24 | # docker_interactive=1 Interactive mode (suppress boot messages) | 24 | # docker_interactive=1 Interactive mode (suppress boot messages) |
| 25 | # docker_daemon=1 Daemon mode (command loop on hvc1) | 25 | # docker_daemon=1 Daemon mode (command loop on hvc0 via serial PTY) |
| 26 | # docker_exit_grace=<s> Grace period (seconds) after entrypoint exits [default: 300] | ||
| 26 | # | 27 | # |
| 27 | # Version: 1.0.0 | 28 | # Version: 1.2.0 |
| 28 | 29 | ||
| 29 | # Set runtime-specific parameters before sourcing common code | 30 | # Set runtime-specific parameters before sourcing common code |
| 30 | VCONTAINER_RUNTIME_NAME="vxn" | 31 | VCONTAINER_RUNTIME_NAME="vxn" |
| @@ -32,7 +33,7 @@ VCONTAINER_RUNTIME_CMD="chroot" | |||
| 32 | VCONTAINER_RUNTIME_PREFIX="docker" | 33 | VCONTAINER_RUNTIME_PREFIX="docker" |
| 33 | VCONTAINER_STATE_DIR="/var/lib/vxn" | 34 | VCONTAINER_STATE_DIR="/var/lib/vxn" |
| 34 | VCONTAINER_SHARE_NAME="vxn_share" | 35 | VCONTAINER_SHARE_NAME="vxn_share" |
| 35 | VCONTAINER_VERSION="1.0.0" | 36 | VCONTAINER_VERSION="1.2.0" |
| 36 | 37 | ||
| 37 | # Source common init functions | 38 | # Source common init functions |
| 38 | . /vcontainer-init-common.sh | 39 | . /vcontainer-init-common.sh |
| @@ -344,7 +345,7 @@ exec_in_container() { | |||
| 344 | export TERM=linux | 345 | export TERM=linux |
| 345 | printf '\r\033[K' | 346 | printf '\r\033[K' |
| 346 | if [ "$use_sh" = "true" ]; then | 347 | if [ "$use_sh" = "true" ]; then |
| 347 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; exec $cmd" | 348 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; $cmd" |
| 348 | else | 349 | else |
| 349 | chroot "$rootfs" $cmd | 350 | chroot "$rootfs" $cmd |
| 350 | fi | 351 | fi |
| @@ -354,7 +355,7 @@ exec_in_container() { | |||
| 354 | EXEC_OUTPUT="/tmp/container_output.txt" | 355 | EXEC_OUTPUT="/tmp/container_output.txt" |
| 355 | EXEC_EXIT_CODE=0 | 356 | EXEC_EXIT_CODE=0 |
| 356 | if [ "$use_sh" = "true" ]; then | 357 | if [ "$use_sh" = "true" ]; then |
| 357 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; exec $cmd" \ | 358 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; $cmd" \ |
| 358 | > "$EXEC_OUTPUT" 2>&1 || EXEC_EXIT_CODE=$? | 359 | > "$EXEC_OUTPUT" 2>&1 || EXEC_EXIT_CODE=$? |
| 359 | else | 360 | else |
| 360 | chroot "$rootfs" $cmd \ | 361 | chroot "$rootfs" $cmd \ |
| @@ -375,38 +376,158 @@ exec_in_container() { | |||
| 375 | umount "$rootfs/dev" 2>/dev/null || true | 376 | umount "$rootfs/dev" 2>/dev/null || true |
| 376 | } | 377 | } |
| 377 | 378 | ||
| 379 | # Execute a command inside the container rootfs in the background. | ||
| 380 | # Used by detached mode: start entrypoint, then enter daemon loop. | ||
| 381 | exec_in_container_background() { | ||
| 382 | local rootfs="$1" | ||
| 383 | local cmd="$2" | ||
| 384 | local workdir="${OCI_WORKDIR:-/}" | ||
| 385 | |||
| 386 | # Mount essential filesystems inside the container rootfs | ||
| 387 | mkdir -p "$rootfs/proc" "$rootfs/sys" "$rootfs/dev" "$rootfs/tmp" 2>/dev/null || true | ||
| 388 | mount -t proc proc "$rootfs/proc" 2>/dev/null || true | ||
| 389 | mount -t sysfs sysfs "$rootfs/sys" 2>/dev/null || true | ||
| 390 | mount --bind /dev "$rootfs/dev" 2>/dev/null || true | ||
| 391 | |||
| 392 | # Copy resolv.conf for DNS | ||
| 393 | if [ -f /etc/resolv.conf ]; then | ||
| 394 | mkdir -p "$rootfs/etc" 2>/dev/null || true | ||
| 395 | cp /etc/resolv.conf "$rootfs/etc/resolv.conf" 2>/dev/null || true | ||
| 396 | fi | ||
| 397 | |||
| 398 | # Run in background, save PID | ||
| 399 | # Note: no 'exec' — compound commands (&&, ||, ;) need the wrapper shell | ||
| 400 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; $cmd" > /tmp/entrypoint.log 2>&1 & | ||
| 401 | ENTRYPOINT_PID=$! | ||
| 402 | echo "$ENTRYPOINT_PID" > /tmp/entrypoint.pid | ||
| 403 | log "Entrypoint PID: $ENTRYPOINT_PID" | ||
| 404 | } | ||
| 405 | |||
| 378 | # ============================================================================ | 406 | # ============================================================================ |
| 379 | # Daemon Mode (vxn-specific) | 407 | # Memres: Run Container from Hot-Plugged Disk |
| 380 | # ============================================================================ | 408 | # ============================================================================ |
| 381 | 409 | ||
| 382 | # In daemon mode, commands come via the hvc1 console channel | 410 | # Run a container from a hot-plugged /dev/xvdb block device. |
| 383 | # and are executed in the container rootfs via chroot. | 411 | # Called by the daemon loop in response to ===RUN_CONTAINER=== command. |
| 384 | run_vxn_daemon_mode() { | 412 | # Mounts the device, finds rootfs, executes entrypoint, unmounts. |
| 385 | log "=== vxn Daemon Mode ===" | 413 | # Output follows the daemon command protocol (===OUTPUT_START/END/EXIT_CODE/END===). |
| 386 | log "Container rootfs: ${CONTAINER_ROOT:-(none)}" | 414 | run_container_from_disk() { |
| 387 | log "Idle timeout: ${RUNTIME_IDLE_TIMEOUT}s" | 415 | local user_cmd="$1" |
| 388 | 416 | ||
| 389 | # Find the command channel (prefer hvc1 for Xen) | 417 | # Wait for hot-plugged block device |
| 390 | DAEMON_PORT="" | 418 | log "Waiting for input device..." |
| 391 | for port in /dev/hvc1 /dev/vport0p1 /dev/vport1p1 /dev/virtio-ports/vxn; do | 419 | local found=false |
| 392 | if [ -c "$port" ]; then | 420 | for i in $(seq 1 30); do |
| 393 | DAEMON_PORT="$port" | 421 | if [ -b /dev/xvdb ]; then |
| 394 | log "Found command channel: $port" | 422 | found=true |
| 395 | break | 423 | break |
| 396 | fi | 424 | fi |
| 425 | sleep 0.5 | ||
| 397 | done | 426 | done |
| 398 | 427 | ||
| 399 | if [ -z "$DAEMON_PORT" ]; then | 428 | if [ "$found" != "true" ]; then |
| 400 | log "ERROR: No command channel for daemon mode" | 429 | echo "===ERROR===" |
| 401 | ls -la /dev/hvc* /dev/vport* /dev/virtio-ports/ 2>/dev/null || true | 430 | echo "Input device /dev/xvdb not found after 15s" |
| 402 | sleep 5 | 431 | echo "===END===" |
| 403 | reboot -f | 432 | return |
| 404 | fi | 433 | fi |
| 405 | 434 | ||
| 406 | # Open bidirectional FD | 435 | # Mount input disk |
| 407 | exec 3<>"$DAEMON_PORT" | 436 | mkdir -p /mnt/input |
| 437 | if ! mount /dev/xvdb /mnt/input 2>/dev/null; then | ||
| 438 | echo "===ERROR===" | ||
| 439 | echo "Failed to mount /dev/xvdb" | ||
| 440 | echo "===END===" | ||
| 441 | return | ||
| 442 | fi | ||
| 443 | log "Input disk mounted" | ||
| 408 | 444 | ||
| 409 | log "Daemon ready, waiting for commands..." | 445 | # Find container rootfs |
| 446 | if ! find_container_rootfs; then | ||
| 447 | echo "===ERROR===" | ||
| 448 | echo "No container rootfs found on input disk" | ||
| 449 | echo "===END===" | ||
| 450 | umount /mnt/input 2>/dev/null || true | ||
| 451 | return | ||
| 452 | fi | ||
| 453 | |||
| 454 | # Parse OCI config for entrypoint/env/workdir | ||
| 455 | parse_oci_config | ||
| 456 | setup_container_env | ||
| 457 | |||
| 458 | # Determine command: user-supplied takes priority, then OCI config | ||
| 459 | if [ -n "$user_cmd" ]; then | ||
| 460 | RUNTIME_CMD="$user_cmd" | ||
| 461 | else | ||
| 462 | RUNTIME_CMD="" | ||
| 463 | fi | ||
| 464 | local exec_cmd | ||
| 465 | exec_cmd=$(determine_exec_command) | ||
| 466 | |||
| 467 | log "Executing container: $exec_cmd" | ||
| 468 | |||
| 469 | # Execute in container rootfs (blocking) | ||
| 470 | local rootfs="$CONTAINER_ROOT" | ||
| 471 | local workdir="${OCI_WORKDIR:-/}" | ||
| 472 | local exit_code=0 | ||
| 473 | |||
| 474 | # Mount essential filesystems inside container | ||
| 475 | mkdir -p "$rootfs/proc" "$rootfs/sys" "$rootfs/dev" "$rootfs/tmp" 2>/dev/null || true | ||
| 476 | mount -t proc proc "$rootfs/proc" 2>/dev/null || true | ||
| 477 | mount -t sysfs sysfs "$rootfs/sys" 2>/dev/null || true | ||
| 478 | mount --bind /dev "$rootfs/dev" 2>/dev/null || true | ||
| 479 | [ -f /etc/resolv.conf ] && { | ||
| 480 | mkdir -p "$rootfs/etc" 2>/dev/null || true | ||
| 481 | cp /etc/resolv.conf "$rootfs/etc/resolv.conf" 2>/dev/null || true | ||
| 482 | } | ||
| 483 | |||
| 484 | local output_file="/tmp/container_run_output.txt" | ||
| 485 | if [ -x "$rootfs/bin/sh" ]; then | ||
| 486 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; $exec_cmd" \ | ||
| 487 | > "$output_file" 2>&1 || exit_code=$? | ||
| 488 | else | ||
| 489 | chroot "$rootfs" $exec_cmd \ | ||
| 490 | > "$output_file" 2>&1 || exit_code=$? | ||
| 491 | fi | ||
| 492 | |||
| 493 | echo "===OUTPUT_START===" | ||
| 494 | cat "$output_file" | ||
| 495 | echo "===OUTPUT_END===" | ||
| 496 | echo "===EXIT_CODE=$exit_code===" | ||
| 497 | |||
| 498 | # Clean up container mounts | ||
| 499 | umount "$rootfs/proc" 2>/dev/null || true | ||
| 500 | umount "$rootfs/sys" 2>/dev/null || true | ||
| 501 | umount "$rootfs/dev" 2>/dev/null || true | ||
| 502 | umount /mnt/input 2>/dev/null || true | ||
| 503 | rm -f "$output_file" | ||
| 504 | |||
| 505 | # Reset container state for next run | ||
| 506 | CONTAINER_ROOT="" | ||
| 507 | OCI_ENTRYPOINT="" | ||
| 508 | OCI_CMD="" | ||
| 509 | OCI_ENV="" | ||
| 510 | OCI_WORKDIR="" | ||
| 511 | RUNTIME_CMD="" | ||
| 512 | # Clean up extracted OCI rootfs (if any) | ||
| 513 | rm -rf /mnt/container 2>/dev/null || true | ||
| 514 | |||
| 515 | echo "===END===" | ||
| 516 | log "Container finished (exit code: $exit_code)" | ||
| 517 | } | ||
| 518 | |||
| 519 | # ============================================================================ | ||
| 520 | # Daemon Mode (vxn-specific) | ||
| 521 | # ============================================================================ | ||
| 522 | |||
| 523 | # Daemon mode: command loop on hvc0 (stdin/stdout). | ||
| 524 | # The host bridges the domain's console PTY to a Unix socket via socat. | ||
| 525 | # Commands arrive as base64-encoded lines on stdin, responses go to stdout. | ||
| 526 | # This is the same model runx used (serial='pty' + serial_start). | ||
| 527 | run_vxn_daemon_mode() { | ||
| 528 | log "=== vxn Daemon Mode ===" | ||
| 529 | log "Container rootfs: ${CONTAINER_ROOT:-(none)}" | ||
| 530 | log "Idle timeout: ${RUNTIME_IDLE_TIMEOUT}s" | ||
| 410 | 531 | ||
| 411 | ACTIVITY_FILE="/tmp/.daemon_activity" | 532 | ACTIVITY_FILE="/tmp/.daemon_activity" |
| 412 | touch "$ACTIVITY_FILE" | 533 | touch "$ACTIVITY_FILE" |
| @@ -415,10 +536,17 @@ run_vxn_daemon_mode() { | |||
| 415 | trap 'log "Shutdown signal"; sync; reboot -f' TERM | 536 | trap 'log "Shutdown signal"; sync; reboot -f' TERM |
| 416 | trap 'rm -f "$ACTIVITY_FILE"; exit' INT | 537 | trap 'rm -f "$ACTIVITY_FILE"; exit' INT |
| 417 | 538 | ||
| 418 | # Command loop | 539 | log "Using hvc0 console for daemon IPC" |
| 540 | log "Daemon ready, waiting for commands..." | ||
| 541 | |||
| 542 | # Emit readiness marker so the host can detect daemon is ready | ||
| 543 | # without needing to send PING first (host reads PTY for this) | ||
| 544 | echo "===PONG===" | ||
| 545 | |||
| 546 | # Command loop: read from stdin (hvc0), write to stdout (hvc0) | ||
| 419 | while true; do | 547 | while true; do |
| 420 | CMD_B64="" | 548 | CMD_B64="" |
| 421 | read -r CMD_B64 <&3 | 549 | read -r CMD_B64 |
| 422 | READ_EXIT=$? | 550 | READ_EXIT=$? |
| 423 | 551 | ||
| 424 | if [ $READ_EXIT -eq 0 ] && [ -n "$CMD_B64" ]; then | 552 | if [ $READ_EXIT -eq 0 ] && [ -n "$CMD_B64" ]; then |
| @@ -426,20 +554,41 @@ run_vxn_daemon_mode() { | |||
| 426 | 554 | ||
| 427 | case "$CMD_B64" in | 555 | case "$CMD_B64" in |
| 428 | "===PING===") | 556 | "===PING===") |
| 429 | echo "===PONG===" | cat >&3 | 557 | echo "===PONG===" |
| 558 | continue | ||
| 559 | ;; | ||
| 560 | "===STATUS===") | ||
| 561 | if [ -f /tmp/entrypoint.exit_code ]; then | ||
| 562 | echo "===EXITED=$(cat /tmp/entrypoint.exit_code)===" | ||
| 563 | else | ||
| 564 | echo "===RUNNING===" | ||
| 565 | fi | ||
| 430 | continue | 566 | continue |
| 431 | ;; | 567 | ;; |
| 432 | "===SHUTDOWN===") | 568 | "===SHUTDOWN===") |
| 433 | log "Received shutdown command" | 569 | log "Received shutdown command" |
| 434 | echo "===SHUTTING_DOWN===" | cat >&3 | 570 | echo "===SHUTTING_DOWN===" |
| 435 | break | 571 | break |
| 436 | ;; | 572 | ;; |
| 573 | "===RUN_CONTAINER==="*) | ||
| 574 | # Memres: run a container from a hot-plugged disk | ||
| 575 | _rc_cmd_b64="${CMD_B64#===RUN_CONTAINER===}" | ||
| 576 | _rc_cmd="" | ||
| 577 | if [ -n "$_rc_cmd_b64" ]; then | ||
| 578 | _rc_cmd=$(echo "$_rc_cmd_b64" | base64 -d 2>/dev/null) | ||
| 579 | fi | ||
| 580 | log "RUN_CONTAINER: cmd='$_rc_cmd'" | ||
| 581 | run_container_from_disk "$_rc_cmd" | ||
| 582 | continue | ||
| 583 | ;; | ||
| 437 | esac | 584 | esac |
| 438 | 585 | ||
| 439 | # Decode command | 586 | # Decode command |
| 440 | CMD=$(echo "$CMD_B64" | base64 -d 2>/dev/null) | 587 | CMD=$(echo "$CMD_B64" | base64 -d 2>/dev/null) |
| 441 | if [ -z "$CMD" ]; then | 588 | if [ -z "$CMD" ]; then |
| 442 | printf "===ERROR===\nFailed to decode command\n===END===\n" | cat >&3 | 589 | echo "===ERROR===" |
| 590 | echo "Failed to decode command" | ||
| 591 | echo "===END===" | ||
| 443 | continue | 592 | continue |
| 444 | fi | 593 | fi |
| 445 | 594 | ||
| @@ -455,13 +604,11 @@ run_vxn_daemon_mode() { | |||
| 455 | eval "$CMD" > "$EXEC_OUTPUT" 2>&1 || EXEC_EXIT_CODE=$? | 604 | eval "$CMD" > "$EXEC_OUTPUT" 2>&1 || EXEC_EXIT_CODE=$? |
| 456 | fi | 605 | fi |
| 457 | 606 | ||
| 458 | { | 607 | echo "===OUTPUT_START===" |
| 459 | echo "===OUTPUT_START===" | 608 | cat "$EXEC_OUTPUT" |
| 460 | cat "$EXEC_OUTPUT" | 609 | echo "===OUTPUT_END===" |
| 461 | echo "===OUTPUT_END===" | 610 | echo "===EXIT_CODE=$EXEC_EXIT_CODE===" |
| 462 | echo "===EXIT_CODE=$EXEC_EXIT_CODE===" | 611 | echo "===END===" |
| 463 | echo "===END===" | ||
| 464 | } | cat >&3 | ||
| 465 | 612 | ||
| 466 | log "Command completed (exit code: $EXEC_EXIT_CODE)" | 613 | log "Command completed (exit code: $EXEC_EXIT_CODE)" |
| 467 | else | 614 | else |
| @@ -469,7 +616,6 @@ run_vxn_daemon_mode() { | |||
| 469 | fi | 616 | fi |
| 470 | done | 617 | done |
| 471 | 618 | ||
| 472 | exec 3>&- | ||
| 473 | log "Daemon shutting down..." | 619 | log "Daemon shutting down..." |
| 474 | } | 620 | } |
| 475 | 621 | ||
| @@ -494,6 +640,15 @@ setup_cgroups | |||
| 494 | # Parse kernel command line | 640 | # Parse kernel command line |
| 495 | parse_cmdline | 641 | parse_cmdline |
| 496 | 642 | ||
| 643 | # Parse vxn-specific kernel parameters | ||
| 644 | ENTRYPOINT_GRACE_PERIOD="300" | ||
| 645 | for param in $(cat /proc/cmdline); do | ||
| 646 | case "$param" in | ||
| 647 | docker_exit_grace=*) ENTRYPOINT_GRACE_PERIOD="${param#docker_exit_grace=}" ;; | ||
| 648 | esac | ||
| 649 | done | ||
| 650 | log "Entrypoint grace period: ${ENTRYPOINT_GRACE_PERIOD}s" | ||
| 651 | |||
| 497 | # Detect and configure disks | 652 | # Detect and configure disks |
| 498 | detect_disks | 653 | detect_disks |
| 499 | 654 | ||
| @@ -525,6 +680,33 @@ parse_oci_config | |||
| 525 | setup_container_env | 680 | setup_container_env |
| 526 | 681 | ||
| 527 | if [ "$RUNTIME_DAEMON" = "1" ]; then | 682 | if [ "$RUNTIME_DAEMON" = "1" ]; then |
| 683 | # If we also have a command, run it in background first (detached container) | ||
| 684 | if [ -n "$RUNTIME_CMD" ] && [ "$RUNTIME_CMD" != "1" ]; then | ||
| 685 | EXEC_CMD=$(determine_exec_command) | ||
| 686 | if [ -n "$EXEC_CMD" ] && [ -n "$CONTAINER_ROOT" ]; then | ||
| 687 | log "Starting entrypoint in background: $EXEC_CMD" | ||
| 688 | exec_in_container_background "$CONTAINER_ROOT" "$EXEC_CMD" | ||
| 689 | |||
| 690 | # Monitor entrypoint: when it exits, record exit code and | ||
| 691 | # schedule DomU shutdown after grace period. | ||
| 692 | # During the grace period, exec and logs still work. | ||
| 693 | if [ -n "$ENTRYPOINT_PID" ]; then | ||
| 694 | ( | ||
| 695 | wait $ENTRYPOINT_PID 2>/dev/null | ||
| 696 | EP_EXIT=$? | ||
| 697 | echo "$EP_EXIT" > /tmp/entrypoint.exit_code | ||
| 698 | log "Entrypoint exited (code: $EP_EXIT), grace period: ${ENTRYPOINT_GRACE_PERIOD}s" | ||
| 699 | if [ "$ENTRYPOINT_GRACE_PERIOD" -gt 0 ] 2>/dev/null; then | ||
| 700 | sleep "$ENTRYPOINT_GRACE_PERIOD" | ||
| 701 | fi | ||
| 702 | log "Grace period expired, shutting down" | ||
| 703 | reboot -f | ||
| 704 | ) & | ||
| 705 | ENTRYPOINT_MONITOR_PID=$! | ||
| 706 | log "Entrypoint monitor started (PID: $ENTRYPOINT_MONITOR_PID)" | ||
| 707 | fi | ||
| 708 | fi | ||
| 709 | fi | ||
| 528 | run_vxn_daemon_mode | 710 | run_vxn_daemon_mode |
| 529 | else | 711 | else |
| 530 | # Determine command to execute | 712 | # Determine command to execute |
