Progress
CI / build (push) Successful in 14s

This commit is contained in:
2026-09-04 17:01:46 +02:00
parent 286def05c1
commit 7dd818db68
7 changed files with 170 additions and 24 deletions
+5 -3
View File
@@ -1,11 +1,13 @@
#!/bin/sh
# Mount special filesystems
mkdir -p /dev /proc /sys
# Create console device
# TODO: is this necessary?
[ -c /dev/console ] || mknod -m 600 /dev/console c 5 1
# Mount special filesystems
mkdir -p /proc /sys
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs dev /dev
# Networking
echo slim > /proc/sys/kernel/hostname
-1
View File
@@ -5,7 +5,6 @@ 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
+13 -17
View File
@@ -43,10 +43,10 @@ pub(crate) fn build(BuildCmd { kind, name, init }: BuildCmd) -> Result<()> {
ImageKind::Qcow2 => build_qcow2(image, &mount_path),
};
let unmounted = Command::new("podman")
.args(["unshare", "--", "podman", "image", "unmount", image])
.output()
.is_ok_and(|out| out.status.success());
let unmounted = cmd(&[
"podman", "unshare", "--", "podman", "image", "unmount", image,
])
.is_ok();
if unmounted {
println!("Unmounted image.");
} else {
@@ -137,19 +137,15 @@ fn build_initrd(image: &str, mount_path: &Path) -> Result<()> {
println!("Registry: {}", reg_dir.display());
let vmlinuz_dst = reg_dir.join("vmlinuz");
let cp_status = Command::new("podman")
.args([
"unshare",
"--",
"cp",
&vmlinuz_src,
vmlinuz_dst.to_str().context("registry path is not UTF-8")?,
])
.status()
.context("Failed to copy vmlinuz out of the image mount")?;
if !cp_status.success() {
bail!("Failed to copy vmlinuz to registry");
}
cmd(&[
"podman",
"unshare",
"--",
"cp",
&vmlinuz_src,
vmlinuz_dst.to_str().context("registry path is not UTF-8")?,
])
.context("Failed to copy vmlinuz out of the image mount")?;
println!("Copied vmlinuz -> {}", vmlinuz_dst.display());
// Pack the rootfs as a gzipped newc cpio archive; vmlinuz is excluded
+4
View File
@@ -2,6 +2,10 @@ use std::process::Command;
use anyhow::{Context, anyhow, bail};
/// Spawn a subprocess with arguments.
///
/// Collects `stdout` and returns it on success.
/// Returns `Err` if the exit code isn't 0, or if the process failed to spawn.
pub fn cmd(cmd: &[&str]) -> anyhow::Result<String> {
let [cmd, args @ ..] = cmd else {
bail!("missing command");
+121
View File
@@ -0,0 +1,121 @@
use std::{
fmt::{self, Display},
net::{Ipv4Addr, SocketAddr},
str::FromStr,
};
use anyhow::{Context, anyhow, bail};
use serde::{Deserialize, Serialize, de::Error};
#[derive(Clone, Copy, Debug)]
pub enum Protocol {
Tcp,
Udp,
}
#[derive(Clone, Copy, Debug)]
pub struct PortForward {
pub protocol: Protocol,
pub from: SocketAddr,
pub to_addr: Option<Ipv4Addr>,
pub to_port: u16,
}
impl Display for Protocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Protocol::Tcp => "tcp",
Protocol::Udp => "udp",
})
}
}
impl Display for PortForward {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}-", self.protocol, self.from)?;
if let Some(to_addr) = self.to_addr {
write!(f, ":{}", to_addr)?;
}
write!(f, ":{}", self.to_port)
}
}
impl FromStr for PortForward {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let result = || {
let (protocol, s) = s.split_once(':').context("Missing ':'")?;
let (from, to) = s.split_once('-').context("Missing '-'")?;
let (to_addr, to_port) = to.split_once(':').context("Missing ':'")?;
let from = if let Some(from) = from.strip_prefix(':') {
let port: u16 = from.parse().context("Invalid port number")?;
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port)
} else {
from.parse()?
};
let to_addr = if to_addr.is_empty() {
None
} else {
Some(to_addr.parse()?)
};
Ok(PortForward {
from,
to_addr,
to_port: to_port.parse()?,
protocol: match protocol {
"udp" => Protocol::Udp,
"tcp" => Protocol::Tcp,
_ => bail!("Invalid protocol, expected 'udp' or 'tcp'"),
},
})
};
result()
.with_context(|| anyhow!("Expected '(tcp|udp):<address>-<address>', got {s:?}"))
.context("Malformed PortForward string")
}
}
impl Serialize for PortForward {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
format_args!("{}", self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for PortForward {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
s.parse().map_err(D::Error::custom)
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::PortForward;
#[test]
fn parse_port_forwards() {
let valid = [
"tcp:0.0.0.0:80-:8080",
"udp::1234-:1234",
"tcp:0.0.0.0:80-1.2.3.4:8080",
"udp::1234-255.255.255.255:1234",
];
for valid in valid {
PortForward::from_str(valid).expect("Failed to parse a valid port forward string");
}
}
}
+1
View File
@@ -1,5 +1,6 @@
mod build;
mod command;
mod forward;
mod image;
mod meta;
mod qemu;
+26 -3
View File
@@ -3,9 +3,10 @@
use anyhow::{Context, Result, bail};
use clap::Args;
use std::process::Command;
use std::{fmt::Write as _, process::Command};
use crate::{
forward::PortForward,
meta::Meta,
registry::{registry_base_dir, registry_dir},
};
@@ -18,9 +19,19 @@ pub struct RunCmd {
/// Amount of memory to give the VM, in qemu's format.
#[clap(short, long, default_value = "1024M")]
memory: String,
/// Forward ports from host to guest. Example: `tcp:0.0.0.0:80-:8080`
#[clap(long)]
forward: Vec<PortForward>,
}
pub(crate) fn run(RunCmd { name, memory }: RunCmd) -> Result<()> {
pub(crate) fn run(
RunCmd {
name,
memory,
forward,
}: RunCmd,
) -> Result<()> {
let reg_dir = registry_dir(&name)?;
let vmlinuz_path = registry_base_dir()?.join("vmlinuz");
let initrd_path = reg_dir.join("initrd");
@@ -41,6 +52,7 @@ pub(crate) fn run(RunCmd { name, memory }: RunCmd) -> Result<()> {
"earlyprintk=serial",
"nokaslr",
&init_arg,
"devtmpfs.mount=1", // Automatically mount /dev at boot
];
let qcow2_arg = format!("file={},format=qcow2,if=virtio", qcow2_path.display());
@@ -63,15 +75,26 @@ pub(crate) fn run(RunCmd { name, memory }: RunCmd) -> Result<()> {
let cmdline = cmdline.join(" ");
// add NIC and forward any user specified ports
let mut network = "user,model=virtio-net-pci".to_string();
for forward in &forward {
_ = write!(&mut network, ",hostfwd={forward}");
}
let mut command = Command::new("qemu-system-x86_64");
// TODO: make a lot of this configurable. especially -cpus
command
.args(["-accel", "kvm"])
.args(["-cpu", "host", "-smp", "cpus=8"])
.args(["-m", &memory])
.arg("-kernel")
.arg(vmlinuz_path.as_os_str())
.args(fs_args)
.args(["-snapshot"])
.args(["-append", &cmdline])
.args(["-nographic"])
.args(["-nic", "user,model=virtio-net-pci"]);
.args(["-nic", &network]);
println!("{command:?}");