diff --git a/docs/2-podman-docker.md b/docs/2-podman-docker.md index 7719b9f..8595683 100644 --- a/docs/2-podman-docker.md +++ b/docs/2-podman-docker.md @@ -111,6 +111,10 @@ $ podman run \ Internally, crun-vm generates a VM image from the bootable container and then boots it. +By default, the VM image is given a disk size roughly double the size of the +bootc container image. To change this, use the `--bootc-disk-size [KMGT]` +option. + ## First-boot customization ### cloud-init diff --git a/embed/bootc/prepare.sh b/embed/bootc/prepare.sh index 15eee6d..7b90905 100644 --- a/embed/bootc/prepare.sh +++ b/embed/bootc/prepare.sh @@ -7,6 +7,7 @@ engine=$1 container_id=$2 original_root=$3 priv_dir=$4 +disk_size=$5 __step() { printf "\033[36m%s\033[0m\n" "$*" @@ -79,7 +80,20 @@ else # run bootc-install under krun - truncate --size 10G "$bootc_dir/image.raw" # TODO: allow adjusting disk size + if [[ -z "$disk_size" ]]; then + container_image_size=$( + "$engine" image inspect --format '{{.VirtualSize}}' "$image_id" + ) + + # use double the container image size to allow for in-place updates + disk_size=$(( container_image_size * 2 )) + + # round up to 1 MiB + alignment=$(( 2**20 )) + disk_size=$(( (disk_size + alignment - 1) / alignment * alignment )) + fi + + truncate --size "$disk_size" "$bootc_dir/image.raw" trap 'krun delete --force "$container_name" >/dev/null 2>&1 || true' EXIT krun run --config "$bootc_dir/config.json" "$container_name" , + #[clap(long)] pub cloud_init: Option, diff --git a/src/commands/create/mod.rs b/src/commands/create/mod.rs index c56875e..bca9fd2 100644 --- a/src/commands/create/mod.rs +++ b/src/commands/create/mod.rs @@ -14,7 +14,9 @@ use std::process::{Command, Stdio}; use anyhow::{anyhow, bail, ensure, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; +use lazy_static::lazy_static; use nix::sys::stat::{major, makedev, minor, mknod, Mode, SFlag}; +use regex::Regex; use rust_embed::RustEmbed; use crate::commands::create::custom_opts::CustomOptions; @@ -38,12 +40,7 @@ pub fn create(args: &liboci_cli::Create, raw_args: &[impl AsRef]) -> Resu let engine = Engine::detect(&args.container_id, bundle_path, &spec, &original_root_path)?; let custom_options = CustomOptions::from_spec(&spec, engine)?; - let is_bootc_container = is_bootc_container(&original_root_path, engine)?; - - ensure!( - !is_bootc_container || !custom_options.emulated, - "--emulated is incompatible with bootable containers" - ); + let is_bootc_container = is_bootc_container(&original_root_path, &custom_options, engine)?; // We include container_id in our paths to ensure no overlap with the user container's contents. let priv_dir_path = original_root_path.join(format!("crun-vm-{}", args.container_id)); @@ -121,6 +118,7 @@ pub fn create(args: &liboci_cli::Create, raw_args: &[impl AsRef]) -> Resu .arg(&args.container_id) .arg(&original_root_path) .arg(&priv_dir_path) + .arg(custom_options.bootc_disk_size.unwrap_or_default()) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -152,7 +150,11 @@ fn ensure_unprivileged(spec: &oci_spec::runtime::Spec) -> Result<()> { Ok(()) } -fn is_bootc_container(original_root_path: &Utf8Path, engine: Engine) -> Result { +fn is_bootc_container( + original_root_path: &Utf8Path, + custom_options: &CustomOptions, + engine: Engine, +) -> Result { let is_bootc_container = original_root_path.join("usr/lib/bootc/install").is_dir(); ensure!( @@ -160,6 +162,31 @@ fn is_bootc_container(original_root_path: &Utf8Path, engine: Engine) -> Result