Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bc14cbeff | ||
|
|
6af8660f8d | ||
|
|
8b544ab688 |
@@ -0,0 +1,16 @@
|
||||
# slim
|
||||
|
||||
Turning containers into bootable VMs
|
||||
|
||||
## Example - Minimal Alpine Busybox
|
||||
|
||||
```sh
|
||||
# Build the container
|
||||
podman build ./example/alpine -t slim-alpine
|
||||
|
||||
# Make the container bootable by extracing initrd and vmlinuz (initial ramdisk and kernel)
|
||||
slim build slim-alpine
|
||||
|
||||
# Boot the image using QEMU, `./example/alpine/init` will drop you into an interactive shell.
|
||||
slim run slim-alpine
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM alpine:latest
|
||||
RUN mkdir -p /lib/apk/db /run
|
||||
RUN apk add --no-cache --initdb linux-virt busybox util-linux
|
||||
RUN cp /boot/vmlinuz-virt /vmlinuz && echo "Welcome to slim!" > /etc/motd
|
||||
COPY init /init
|
||||
RUN chmod +x /init
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Special folders
|
||||
mkdir -p /dev /proc /sys
|
||||
[ -c /dev/console ] || mknod -m 600 /dev/console c 5 1
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mount -t devtmpfs dev /dev
|
||||
|
||||
# Networking
|
||||
echo slim > /proc/sys/kernel/hostname
|
||||
modprobe virtio_net
|
||||
ip link set lo up 2>/dev/null
|
||||
ip link set eth0 up 2>/dev/null
|
||||
udhcpc -i eth0 -f -q # Get DHCP lease
|
||||
|
||||
echo
|
||||
echo 'Welcome to slim!'
|
||||
echo
|
||||
|
||||
# Run interactive shell
|
||||
/bin/sh </dev/console >/dev/console 2>&1
|
||||
|
||||
poweroff -f
|
||||
while true; do
|
||||
sleep 1
|
||||
poweroff -f
|
||||
done
|
||||
@@ -0,0 +1,34 @@
|
||||
use std::fs;
|
||||
|
||||
use crate::registry::{registry_base_dir, validate_image_name};
|
||||
use anyhow::Context;
|
||||
use clap::Subcommand;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum ImageCmd {
|
||||
Ls,
|
||||
Rm { image: String },
|
||||
}
|
||||
|
||||
pub fn run(cmd: ImageCmd) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
ImageCmd::Ls => ls(),
|
||||
ImageCmd::Rm { image } => rm(&image),
|
||||
}
|
||||
}
|
||||
|
||||
fn rm(image: &str) -> Result<(), anyhow::Error> {
|
||||
validate_image_name(image)?;
|
||||
let image_dir = registry_base_dir()?.join(image);
|
||||
fs::remove_dir_all(image_dir).context("Failed to remove image dir")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ls() -> anyhow::Result<()> {
|
||||
let dir = fs::read_dir(registry_base_dir()?)?;
|
||||
for entry in dir {
|
||||
let entry = entry?.file_name();
|
||||
println!(" {}", entry.to_string_lossy());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
mod build;
|
||||
mod image;
|
||||
mod qemu;
|
||||
mod registry;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::image::ImageCmd;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about = "Build bootable initrd VMs from container images")]
|
||||
struct Cli {
|
||||
@@ -27,6 +30,9 @@ enum Commands {
|
||||
/// Image tag / registry subdir name
|
||||
name: String,
|
||||
},
|
||||
/// List slim images in the registry
|
||||
#[command(subcommand)]
|
||||
Image(ImageCmd),
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -38,6 +44,9 @@ fn main() -> Result<()> {
|
||||
Commands::Run { name } => {
|
||||
qemu::run(&name)?;
|
||||
}
|
||||
Commands::Image(cmd) => {
|
||||
image::run(cmd)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ pub(crate) fn run(name: &str) -> Result<()> {
|
||||
.arg(initrd_path.as_os_str())
|
||||
.args(["-append", &cmdline])
|
||||
.args(["-nographic"])
|
||||
.args(["-nic", "user,model=virtio-net-pci"])
|
||||
.status()
|
||||
.context("Failed to launch qemu-system-x86_64")?;
|
||||
|
||||
|
||||
+18
-23
@@ -1,8 +1,8 @@
|
||||
//! Registry: local storage of built VM artifacts under
|
||||
//! `$XDG_DATA_HOME/slim-rs/registry/<image>/`.
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use std::path::PathBuf;
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use std::{fs, io, path::PathBuf};
|
||||
|
||||
/// Validate a user-supplied image name before it is used in a registry path.
|
||||
///
|
||||
@@ -26,30 +26,25 @@ pub(crate) fn validate_image_name(name: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn registry_base_dir() -> Result<PathBuf> {
|
||||
let base = xdg::BaseDirectories::with_prefix("slim-rs");
|
||||
base.create_data_directory("registry")
|
||||
.context("Failed to create XDG_DATA_HOME subdirectory")
|
||||
}
|
||||
|
||||
pub(crate) fn registry_dir(image: &str) -> Result<PathBuf> {
|
||||
validate_image_name(image)?;
|
||||
let base = xdg::BaseDirectories::with_prefix("slim-rs");
|
||||
let initrd_path = base
|
||||
.place_data_file(format!("registry/{image}/initrd"))
|
||||
.context("Failed to write to XDG_DATA_HOME")?;
|
||||
let reg_dir = initrd_path
|
||||
.parent()
|
||||
.expect("data file has a parent")
|
||||
.to_owned();
|
||||
let base = registry_base_dir()?;
|
||||
|
||||
let reg_dir = base.join(image);
|
||||
fs::create_dir(®_dir)
|
||||
.or_else(|e| {
|
||||
(e.kind() == io::ErrorKind::AlreadyExists)
|
||||
.then_some(())
|
||||
.ok_or(e)
|
||||
})
|
||||
.with_context(|| anyhow!("Failed to create {reg_dir:?}"))?;
|
||||
|
||||
// Defense in depth: the constructed path must stay inside the registry.
|
||||
let registry_root = base
|
||||
.place_data_file("registry/.root-check")
|
||||
.context("Failed to write to XDG_DATA_HOME")?
|
||||
.parent()
|
||||
.expect("data file has a parent")
|
||||
.to_owned();
|
||||
if !reg_dir.starts_with(®istry_root) {
|
||||
bail!(
|
||||
"invalid image name '{image}': registry path escapes {}",
|
||||
registry_root.display()
|
||||
);
|
||||
}
|
||||
Ok(reg_dir)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user