+5
-3
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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,5 +1,6 @@
|
||||
mod build;
|
||||
mod command;
|
||||
mod forward;
|
||||
mod image;
|
||||
mod meta;
|
||||
mod qemu;
|
||||
|
||||
+26
-3
@@ -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:?}");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user