Compare commits

..
1 Commits
Author SHA1 Message Date
hulthe 589afef4f5 Inject universal /slim/init instead of requiring container-specific setup
CI / build (pull_request) Successful in 12s
slim now works with any Containerfile by injecting distro-agnostic scripts
at build time:

- /slim/init: mounts special filesystems, configures networking (static
  QEMU slirp), sets up cgroup2, timezone, and rootless container prereqs.
  Parses slim.cmd=<base64> from the kernel cmdline for runtime overrides,
  otherwise execs /slim/exec.

- /slim/exec: generated from the image's CMD/ENTRYPOINT (via podman image
  inspect), overridable via --cmd at build and run time.

Changes:
- New src/inject.rs: inspect image config, infer command, generate
  /slim/exec script, inject /slim/ into mounted rootfs
- New src/scripts/slim-init.sh: the universal init script (include_str!)
- build.rs: rename --init to --cmd, inject scripts before packing,
  drop Meta::save, restructure to ensure unmount always runs
- qemu.rs: hardcode init=/slim/init, add --cmd (base64 on cmdline),
  drop Meta::load, add -no-reboot
- Remove src/meta.rs and meta.toml (no longer needed)
- Cargo.toml: add serde_json + base64, remove unused walkdir + cpio + toml
- Example Containerfiles simplified to plain FROM + CMD
- New example/test.sh for manual verification
- README updated for new workflow
2026-09-08 09:21:12 +02:00
3 changed files with 19 additions and 40 deletions
+4 -4
View File
@@ -51,14 +51,14 @@ test_distro() {
cat > "$work/Containerfile.infer" <<EOF
FROM $from
$extra_setup
CMD ["/bin/sh", "-c", "echo CMD_INFERRED_OK; wget -q -O /dev/null http://1.1.1.1 2>/dev/null && echo CONN_OK; wget -q -O /dev/null http://example.org 2>/dev/null && echo DNS_OK; poweroff -f"]
CMD ["/bin/sh", "-c", "echo CMD_INFERRED_OK; ping -c1 1.1.1.1 >/dev/null 2>&1 && echo PING_OK; ping -c1 example.org >/dev/null 2>&1 && echo DNS_OK; poweroff -f"]
EOF
img="slim-test-$distro-infer"
if podman build -t "$img" -f "$work/Containerfile.infer" >/dev/null 2>&1; then
"$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1
output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" 2>&1 || true)
check_output "$output" "CMD_INFERRED_OK" "$distro CMD-inferred"
check_output "$output" "CONN_OK" "$distro connect to 1.1.1.1"
check_output "$output" "PING_OK" "$distro ping 1.1.1.1"
check_output "$output" "DNS_OK" "$distro DNS lookup"
else
report fail "$distro CMD-inferred (podman build failed)"
@@ -110,7 +110,7 @@ test_service() {
work="$(mktemp -d)"
cat > "$work/Containerfile" <<'EOF'
FROM docker.io/library/nextcloud:32-apache
FROM nextcloud:32-apache
RUN apt-get update && apt-get install -y --no-install-recommends iproute2 && rm -rf /var/lib/apt/lists/*
EOF
@@ -168,7 +168,7 @@ echo "Building slim..."
cargo build 2>&1
test_distro "alpine" "alpine:latest" ""
test_distro "archlinux" "archlinux:latest" "RUN pacman -Sy --noconfirm iproute2 wget; pacman -Sc --noconfirm"
test_distro "archlinux" "archlinux:latest" "RUN pacman -Sy --noconfirm iputils iproute2 util-linux; pacman -Sc --noconfirm"
test_service
+14 -32
View File
@@ -41,28 +41,19 @@ pub(crate) fn build(
}: BuildCmd,
) -> Result<()> {
let image = &name;
let container = format!("slim-build-{}", image.replace(':', "-"));
let mount_path = mount_container(image, &container)?;
let mount_path = mount_image(image)?;
println!("Mounted at: {}", mount_path.display());
let result = build_inner(&kind, image, &mount_path, cmd_override);
let unmounted = cmd(&[
"podman",
"unshare",
"--",
"podman",
"container",
"unmount",
&container,
"podman", "unshare", "--", "podman", "image", "unmount", image,
])
.is_ok();
let removed = cmd(&["podman", "rm", &container]).is_ok();
if unmounted && removed {
println!("Unmounted and removed container.");
if unmounted {
println!("Unmounted image.");
} else {
eprintln!("warning: failed to clean up container '{container}'");
eprintln!("warning: failed to unmount image '{}'", image);
}
result
@@ -88,22 +79,13 @@ fn build_inner(
}
}
fn mount_container(image: &str, container: &str) -> Result<PathBuf> {
cmd(&["podman", "create", "--name", container, image, "/bin/true"])?;
let mount_path = cmd(&[
"podman",
"unshare",
"--",
"podman",
"container",
"mount",
container,
])?;
fn mount_image(image: &str) -> Result<PathBuf> {
let mount_path = cmd(&["podman", "unshare", "--", "podman", "image", "mount", image])?;
Ok(PathBuf::from(mount_path.trim()))
}
/// Copy a directory onto a new raw disk image with EXT4.
fn to_raw_ext4(dir: &Path, tmp_dir: &Path) -> Result<NamedTempFile> {
fn to_raw_ext4(dir: &Path) -> Result<NamedTempFile> {
let dir = dir.to_str().context("Invalid UTF-8")?;
let du_out = cmd(&["podman", "unshare", "--", "du", "-sk", dir])?;
@@ -118,10 +100,10 @@ fn to_raw_ext4(dir: &Path, tmp_dir: &Path) -> Result<NamedTempFile> {
let size = used
+ (used / 10) // Add 10%
// TODO: make configurable
+ 4 * gb; // Add some spare capacity for activities
+ 64 * gb; // Add some spare capacity for activities
let size = size.to_string();
let raw_file = NamedTempFile::new_in(tmp_dir)?;
let raw_file = NamedTempFile::new()?;
let raw_path = raw_file.path().to_str().context("Invalid UTF-8")?;
cmd(&["podman", "unshare", "--", "truncate", "-s", &size, raw_path])?;
cmd(&[
@@ -138,9 +120,9 @@ fn to_raw_ext4(dir: &Path, tmp_dir: &Path) -> Result<NamedTempFile> {
}
/// Copy a directory onto a new qcow2 disk image with EXT4.
fn to_qcow2_ext4(mount_path: &Path, tmp_dir: &Path) -> Result<NamedTempFile> {
let raw = to_raw_ext4(mount_path, tmp_dir)?;
let qcow2 = NamedTempFile::new_in(tmp_dir)?;
fn to_qcow2_ext4(mount_path: &Path) -> Result<NamedTempFile> {
let raw = to_raw_ext4(mount_path)?;
let qcow2 = NamedTempFile::new()?;
let raw_path = raw.path().to_str().context("Invalid UTF-8")?;
let qcow2_path = qcow2.path().to_str().context("Invalid UTF-8")?;
cmd(&[
@@ -154,7 +136,7 @@ fn build_qcow2(image: &str, mount_path: &Path) -> Result<()> {
fs::create_dir_all(&reg_dir)?;
println!("Registry: {}", reg_dir.display());
let qcow2 = to_qcow2_ext4(mount_path, &reg_dir)?;
let qcow2 = to_qcow2_ext4(mount_path)?;
fs::copy(qcow2.path(), reg_dir.join("image.qcow2"))
.context("Failed to copy qcow2 image to registry")?;
+1 -4
View File
@@ -43,10 +43,7 @@ pub(crate) fn run(
}: RunCmd,
) -> Result<()> {
let reg_dir = registry_dir(&name)?;
let vmlinuz_path = registry_base_dir()?
.parent()
.context("registry base dir has no parent")?
.join("vmlinuz");
let vmlinuz_path = registry_base_dir()?.join("vmlinuz");
let initrd_path = reg_dir.join("initrd");
let qcow2_path = reg_dir.join("image.qcow2");
if !vmlinuz_path.exists() {