summaryrefslogtreecommitdiffstats
path: root/recipes-containers/vcontainer/files/vxn-init.sh
diff options
context:
space:
mode:
Diffstat (limited to 'recipes-containers/vcontainer/files/vxn-init.sh')
-rwxr-xr-xrecipes-containers/vcontainer/files/vxn-init.sh262
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
30VCONTAINER_RUNTIME_NAME="vxn" 31VCONTAINER_RUNTIME_NAME="vxn"
@@ -32,7 +33,7 @@ VCONTAINER_RUNTIME_CMD="chroot"
32VCONTAINER_RUNTIME_PREFIX="docker" 33VCONTAINER_RUNTIME_PREFIX="docker"
33VCONTAINER_STATE_DIR="/var/lib/vxn" 34VCONTAINER_STATE_DIR="/var/lib/vxn"
34VCONTAINER_SHARE_NAME="vxn_share" 35VCONTAINER_SHARE_NAME="vxn_share"
35VCONTAINER_VERSION="1.0.0" 36VCONTAINER_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.
381exec_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.
384run_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)}" 414run_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).
527run_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
495parse_cmdline 641parse_cmdline
496 642
643# Parse vxn-specific kernel parameters
644ENTRYPOINT_GRACE_PERIOD="300"
645for param in $(cat /proc/cmdline); do
646 case "$param" in
647 docker_exit_grace=*) ENTRYPOINT_GRACE_PERIOD="${param#docker_exit_grace=}" ;;
648 esac
649done
650log "Entrypoint grace period: ${ENTRYPOINT_GRACE_PERIOD}s"
651
497# Detect and configure disks 652# Detect and configure disks
498detect_disks 653detect_disks
499 654
@@ -525,6 +680,33 @@ parse_oci_config
525setup_container_env 680setup_container_env
526 681
527if [ "$RUNTIME_DAEMON" = "1" ]; then 682if [ "$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
529else 711else
530 # Determine command to execute 712 # Determine command to execute