diff --git a/crates/kit/scripts/entrypoint.sh b/crates/kit/scripts/entrypoint.sh index ecb8094a8..3ce06b8e0 100644 --- a/crates/kit/scripts/entrypoint.sh +++ b/crates/kit/scripts/entrypoint.sh @@ -2,49 +2,52 @@ set -euo pipefail SELFEXE=/run/selfexe +TMPROOT=/run/tmproot # Check for required binaries early -if ! command -v bwrap &>/dev/null; then - echo "Error: bwrap (bubblewrap) is currently required in the target container image" >&2 - exit 1 -fi +for bin in mount chroot; do + if ! command -v "$bin" &>/dev/null; then + echo "Error: $bin (util-linux or coreutils) is required in the target container image" >&2 + exit 1 + fi +done # Shell script library init_tmproot() { if test -d /run/inner-shared; then return 0; fi # Should have been created by podman when initializing # the bind mount - cd /run/tmproot + cd "$TMPROOT" # Create essential symlinks ln -sf usr/bin bin ln -sf usr/lib lib ln -sf usr/lib64 lib64 ln -sf usr/sbin sbin - mkdir -p {etc,var,dev,proc,run,sys,tmp} + mkdir -p {etc,var/tmp,dev,proc,run,sys,tmp} # Ensure we have /etc/passwd as ssh-keygen wants it for bad reasons systemd-sysusers --root $(pwd) &>/dev/null # Copy DNS configuration from container's /etc/resolv.conf (configured by podman --dns) - # into the bwrap namespace so QEMU's slirp can use it for DNS resolution + # into the new root so QEMU's slirp can use it for DNS resolution if [ -f /etc/resolv.conf ]; then - cp /etc/resolv.conf /run/tmproot/etc/resolv.conf + cp /etc/resolv.conf "$TMPROOT/etc/resolv.conf" fi - # Shared directory between containers + # We're already privileged in the container's own mount namespace, + # which is torn down with it, so set up the hybrid root's mounts + # here; later `podman exec` invocations share them. Bind /run first, + # so its copy under the new root doesn't also pick up the mounts + # made below. /tmp needs nothing: /run is already a tmpfs. + mount --rbind /run "$TMPROOT/run" + mount -t proc proc "$TMPROOT/proc" + mount --rbind /dev "$TMPROOT/dev" + mount --rbind /var/tmp "$TMPROOT/var/tmp" + + # Shared directory between containers; also signals that the root is ready mkdir /run/inner-shared } -BWRAP_ARGS=( - --bind /run/tmproot / - --proc /proc - --dev-bind /dev /dev - --bind /var/tmp /var/tmp - --tmpfs /run - --tmpfs /tmp - --bind /run/inner-shared /run/inner-shared -) - # Pass ALL arguments to container-entrypoint # Default to "run-ephemeral" if no args if [[ $# -eq 0 ]]; then @@ -60,21 +63,6 @@ fi # Check systemd version from the container image (not host) export SYSTEMD_VERSION=$(systemctl --version 2>/dev/null) -# Execute with proper environment passing -# Set up signal handlers that will cleanly exit on INT or TERM -trap 'kill -TERM $BWRAP_PID 2>/dev/null; exit 0' INT TERM - -# Run bwrap in background so we can handle signals; xref -# https://github.com/containers/bubblewrap/pull/586 -# But probably really we should switch to systemd -# Bash normally gives a background command /dev/null on stdin, so -# keep stdin attached so QEMU can receive input. -bwrap --as-pid-1 --unshare-pid "${BWRAP_ARGS[@]}" --bind /run /run -- ${SELFEXE} container-entrypoint "$@" <&0 & -BWRAP_PID=$! - -# Wait for bwrap to complete -wait $BWRAP_PID -EXIT_CODE=$? - -# Exit with the same code as bwrap -exit $EXIT_CODE +# container-entrypoint handles SIGTERM/SIGINT itself, so it can just +# replace us (as PID 1 for the main invocation). +exec chroot "$TMPROOT" "$SELFEXE" container-entrypoint "$@" diff --git a/crates/kit/src/run_ephemeral.rs b/crates/kit/src/run_ephemeral.rs index 36bbaafe1..d3b6a3cbc 100644 --- a/crates/kit/src/run_ephemeral.rs +++ b/crates/kit/src/run_ephemeral.rs @@ -15,7 +15,7 @@ //! The execution follows this chain: //! 1. **Host Process**: `bcvk run-ephemeral` invoked on host //! 2. **Container Launch**: Podman privileged container with KVM and host mounts -//! 3. **Namespace Setup**: bwrap creates isolated namespace with hybrid rootfs +//! 3. **Root Setup**: `mount` and `chroot` into the hybrid rootfs //! 4. **Binary Re-execution**: Same binary re-executes with `container-entrypoint` //! 5. **VM Launch**: QEMU starts with VirtioFS root and additional mounts //! @@ -41,12 +41,12 @@ //! └── [other dirs created empty for container compatibility] //! ``` //! -//! ### Phase 3: Namespace Isolation (bwrap) -//! Uses bubblewrap to create isolated namespace: -//! - New mount namespace with `/run/tmproot` as root +//! ### Phase 3: Root Setup (mount + chroot) +//! The container is already privileged with its own mount namespace, so no +//! further namespaces are needed: +//! - `/run`, `/proc`, `/dev` and `/var/tmp` mounted into `/run/tmproot` //! - Shared `/run/inner-shared` for virtiofsd socket communication -//! - Proper `/proc`, `/dev`, `/tmp` mounts -//! - Re-executes binary: `bwrap ... -- /run/selfexe container-entrypoint` +//! - Re-executes binary: `exec chroot /run/tmproot /run/selfexe container-entrypoint` //! //! ### Phase 4: VM Execution (`run_impl`) //! - Runs inside the container after namespace setup @@ -1219,8 +1219,8 @@ fn parse_service_exit_code(status_content: &str) -> Result { fn check_required_container_binaries() -> Result<()> { // systemctl: used for checking cloud-init and other systemd operations // objcopy: for UKI kernel extraction (when using UKI images) - // NOTE: bwrap is checked earlier in entrypoint.sh, not here, because by the - // time run_impl() executes we're already inside the bwrap namespace + // NOTE: mount and chroot are checked earlier in entrypoint.sh, not here, because by the + // time run_impl() executes we're already inside the hybrid root let required_binaries = ["systemctl", "objcopy"]; let mut missing = Vec::new(); @@ -1934,7 +1934,7 @@ Options= // Check if disk file exists and is accessible if !Utf8Path::new(&disk_file).exists() { return Err(eyre!( - "Disk file does not exist in bwrap namespace: {} (serial: {})", + "Disk file does not exist in the hybrid root: {} (serial: {})", disk_file, serial )); diff --git a/docs/src/installation.md b/docs/src/installation.md index f793f21e4..729ce46f4 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -36,7 +36,7 @@ Optional: For `bcvk ephemeral` operations, the bootc container images you run must contain: - systemctl (systemd) - objcopy (binutils) -- bwrap (bubblewrap) +- mount (util-linux), chroot (coreutils) - ssh, ssh-keygen (openssh-clients) ## Development Binaries