Introduce micro-activate (RIIR activate script + tiny bit of tmpfiles)
Instead of interpreting all that shell and running actual tmpfiles, use a tiny stage before systemd that mounts a tmpfs at /run (preventing systemd from doing the same), populates it with NixOS symlinks and preserved resolv.conf, and mounts the immutable /etc overlay before passing control over to systemd.
This commit is contained in:
parent
3d2f6c4732
commit
0bd986f97f
5 changed files with 151 additions and 40 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
result
|
result
|
||||||
/testvm*
|
/testvm*
|
||||||
|
/target
|
||||||
|
/micro-activate
|
||||||
.direnv/
|
.direnv/
|
||||||
|
|
|
||||||
124
micro-activate.rs
Normal file
124
micro-activate.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
use std::os::raw::{c_char, c_int, c_ulong, c_void};
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
|
||||||
|
const MS_RDONLY: c_ulong = 0x01;
|
||||||
|
const MS_NOSUID: c_ulong = 0x02;
|
||||||
|
const MS_NODEV: c_ulong = 0x04;
|
||||||
|
const MS_RELATIME: c_ulong = 0x200000;
|
||||||
|
const MS_STRICTATIME: c_ulong = 0x1000000;
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn mount(
|
||||||
|
src: *const c_char,
|
||||||
|
target: *const c_char,
|
||||||
|
fstype: *const c_char,
|
||||||
|
flags: c_ulong,
|
||||||
|
data: *const c_void,
|
||||||
|
) -> c_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tmpfiles_line(line: &str) -> Option<(&str, &str)> {
|
||||||
|
// NOTE: does not support actual whitespace inside quotes
|
||||||
|
// (that's not gonna appear in these files we parse)
|
||||||
|
let mut it = line
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|s| s.trim_start_matches('\'').trim_end_matches('\''));
|
||||||
|
let instr = it.next()?;
|
||||||
|
if !instr.starts_with('L') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let src = it.next()?;
|
||||||
|
let _ = it.next()?;
|
||||||
|
let _ = it.next()?;
|
||||||
|
let _ = it.next()?;
|
||||||
|
let _ = it.next()?;
|
||||||
|
let dst = it.next()?;
|
||||||
|
Some((src, dst))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link_tmpfiles(contents: &[u8]) -> Result<(), std::io::Error> {
|
||||||
|
for (src, dst) in str::from_utf8(contents)
|
||||||
|
.unwrap()
|
||||||
|
.lines()
|
||||||
|
.flat_map(parse_tmpfiles_line)
|
||||||
|
{
|
||||||
|
std::os::unix::fs::symlink(dst, src)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), std::io::Error> {
|
||||||
|
let closure = std::env::var("MICROVM_CLOSURE").unwrap();
|
||||||
|
|
||||||
|
// systemd really wants /run to be a mountpoint and will mount a tmpfs on its own
|
||||||
|
// if it's not already a mountpoint. Well, it's correct: reaching into virtiofs
|
||||||
|
// (which is what not-mounting would entail) for /run stuff is not great.
|
||||||
|
//
|
||||||
|
// Let's preserve the fixed passed-in files and set up the NixOS symlinks in the new mount.
|
||||||
|
let resolv_conf = std::fs::read("/run/resolv.conf")?;
|
||||||
|
let machine_id = std::fs::read("/run/machine-id")?;
|
||||||
|
assert_eq!(
|
||||||
|
unsafe {
|
||||||
|
mount(
|
||||||
|
c"tmpfs".as_ptr(),
|
||||||
|
c"/run".as_ptr(),
|
||||||
|
c"tmpfs".as_ptr(),
|
||||||
|
MS_NOSUID | MS_NODEV | MS_STRICTATIME,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
std::fs::write("/run/resolv.conf", &resolv_conf)?;
|
||||||
|
std::fs::write("/run/machine-id", &machine_id)?;
|
||||||
|
std::os::unix::fs::symlink(&closure, "/run/current-system")?;
|
||||||
|
if let Ok(tmp_graphics) =
|
||||||
|
std::fs::read(format!("{closure}/etc/tmpfiles.d/graphics-driver.conf"))
|
||||||
|
{
|
||||||
|
link_tmpfiles(&tmp_graphics)?;
|
||||||
|
} else {
|
||||||
|
eprintln!("[micro-activate] Could not find the closure's graphics-driver.conf!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need the /etc metadata overlay not just for abstract correctness, but even just to
|
||||||
|
// allow the regular user to run systemctl (it doesn't like passwd being owned by non-root)..
|
||||||
|
let metadata_img = std::fs::read_link(format!("{closure}/etc-metadata-image"))
|
||||||
|
.expect("The closure must use an immutable /etc overlay!");
|
||||||
|
let basedir = std::fs::read_link(format!("{closure}/etc-basedir"))
|
||||||
|
.expect("The closure must use an immutable /etc overlay!");
|
||||||
|
let overlay_opts = std::ffi::CString::new(format!(
|
||||||
|
"redirect_dir=on,metacopy=on,lowerdir=/run/etc.meta::{}",
|
||||||
|
basedir.display()
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
std::fs::create_dir("/run/etc.meta")?;
|
||||||
|
std::fs::remove_file("/etc")?;
|
||||||
|
std::fs::create_dir("/etc")?;
|
||||||
|
unsafe {
|
||||||
|
assert_eq!(
|
||||||
|
mount(
|
||||||
|
metadata_img.as_os_str().as_bytes().as_ptr(),
|
||||||
|
c"/run/etc.meta".as_ptr(),
|
||||||
|
c"erofs".as_ptr(),
|
||||||
|
MS_RDONLY | MS_NODEV | MS_NOSUID,
|
||||||
|
std::ptr::null(),
|
||||||
|
),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mount(
|
||||||
|
c"overlay".as_ptr(),
|
||||||
|
c"/etc".as_ptr(),
|
||||||
|
c"overlay".as_ptr(),
|
||||||
|
MS_NODEV | MS_NOSUID | MS_RELATIME,
|
||||||
|
overlay_opts.as_ptr() as *const c_void,
|
||||||
|
),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = std::env::args_os().skip(1);
|
||||||
|
let cmd = args.next().unwrap();
|
||||||
|
Err(std::process::Command::new(cmd).args(args).exec())
|
||||||
|
}
|
||||||
27
munix
27
munix
|
|
@ -180,38 +180,31 @@ BWRAP_ARGS+=(
|
||||||
bwrap --unshare-all --share-net \
|
bwrap --unshare-all --share-net \
|
||||||
--uid $MICROVM_UID --gid $MICROVM_GID \
|
--uid $MICROVM_UID --gid $MICROVM_GID \
|
||||||
--tmpfs / \
|
--tmpfs / \
|
||||||
--dir /run --dir /var --symlink /run /var/run --dir /tmp --dir /mnt \
|
--dir /run --dir /var --symlink /run /var/run --dir /tmp --dir /mnt --dir /bin --dir /usr/bin \
|
||||||
--proc /proc --ro-bind /sys /sys \
|
--proc /proc --ro-bind /sys /sys \
|
||||||
--dev /dev --dir /dev/input --dev-bind /dev/kvm /dev/kvm \
|
--dev /dev --dir /dev/input --dev-bind /dev/kvm /dev/kvm \
|
||||||
--ro-bind "$MUVM_PATH" /run/munix/muvm \
|
--ro-bind "$MUVM_PATH" /run/munix/muvm \
|
||||||
--ro-bind "$PASST_PATH" /run/munix/passt \
|
--ro-bind "$PASST_PATH" /run/munix/passt \
|
||||||
|
--ro-bind "$SCRIPT_PATH/micro-activate" /opt/bin/micro-activate \
|
||||||
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-remote \
|
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-remote \
|
||||||
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-configure-network \
|
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-configure-network \
|
||||||
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-pwbridge \
|
--ro-bind "$MUVM_PATH/muvm-guest" /opt/bin/muvm-pwbridge \
|
||||||
--symlink "$MICROVM_CLOSURE/etc/systemd" /etc/systemd \
|
--symlink "$MICROVM_CLOSURE/etc" /etc \
|
||||||
|
--symlink "$MICROVM_CLOSURE/sw/bin/sh" /bin/sh \
|
||||||
|
--symlink "$MICROVM_CLOSURE/sw/bin/env" /usr/bin/env \
|
||||||
|
--symlink "$MICROVM_CLOSURE" /run/current-system \
|
||||||
--ro-bind /nix/store /nix/store \
|
--ro-bind /nix/store /nix/store \
|
||||||
--ro-bind /run/systemd/resolve /run/systemd/resolve \
|
--file 12 /run/machine-id \
|
||||||
--file 11 /etc/passwd \
|
--file 13 /run/resolv.conf \
|
||||||
--file 12 /etc/group \
|
|
||||||
--file 13 /etc/resolv.conf \
|
|
||||||
--dir "$XDG_RUNTIME_DIR" \
|
--dir "$XDG_RUNTIME_DIR" \
|
||||||
--setenv PATH "/run/munix/muvm:/run/munix/passt:$MICROVM_CLOSURE/sw/bin" \
|
--setenv PATH "/run/munix/muvm:/run/munix/passt:$MICROVM_CLOSURE/sw/bin" \
|
||||||
"${BWRAP_ARGS[@]}" \
|
"${BWRAP_ARGS[@]}" \
|
||||||
muvm \
|
muvm \
|
||||||
--custom-init-cmdline "$MICROVM_CLOSURE/sw/sbin/init --log-target=console systemd.set_credential=sidebus.port:50000" \
|
--custom-init-cmdline "/opt/bin/micro-activate $MICROVM_CLOSURE/sw/sbin/init --log-target=console systemd.set_credential=sidebus.port:50000" \
|
||||||
"${MUVM_ARGS[@]}" \
|
"${MUVM_ARGS[@]}" \
|
||||||
-e container=munix \
|
-e container=munix \
|
||||||
-e MICROVM_CLOSURE="$MICROVM_CLOSURE" \
|
-e MICROVM_CLOSURE="$MICROVM_CLOSURE" \
|
||||||
-e MICROVM_UID="$MICROVM_UID" -e MICROVM_GID="$MICROVM_GID" \
|
-e MICROVM_UID="$MICROVM_UID" -e MICROVM_GID="$MICROVM_GID" \
|
||||||
-i -t "${MICROVM_COMMAND[@]}" \
|
-i -t "${MICROVM_COMMAND[@]}" \
|
||||||
11< <(cat <<EOF
|
12< /etc/machine-id \
|
||||||
munix:x:$MICROVM_UID:$MICROVM_GID:Hypervisor:/:/run/current-system/sw/bin/nologin
|
|
||||||
nobody:x:65534:65534:Unprivileged account:/var/empty:/run/current-system/sw/bin/nologin
|
|
||||||
EOF
|
|
||||||
) \
|
|
||||||
12< <(cat <<EOF
|
|
||||||
munix:x:$MICROVM_GID:
|
|
||||||
nogroup:x:65534:
|
|
||||||
EOF
|
|
||||||
) \
|
|
||||||
13< /etc/resolv.conf
|
13< /etc/resolv.conf
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ in {
|
||||||
"systemd-udevd-kernel.socket"
|
"systemd-udevd-kernel.socket"
|
||||||
"systemd-udevd-control.socket"
|
"systemd-udevd-control.socket"
|
||||||
"systemd-udevd.service"
|
"systemd-udevd.service"
|
||||||
"systemd-tmpfiles-setup.service"
|
|
||||||
"user.slice"
|
"user.slice"
|
||||||
];
|
];
|
||||||
upstreamWants = ["multi-user.target.wants"];
|
upstreamWants = ["multi-user.target.wants"];
|
||||||
|
|
@ -99,24 +98,6 @@ in {
|
||||||
systemd.services.systemd-pstore.enable = lib.mkForce false;
|
systemd.services.systemd-pstore.enable = lib.mkForce false;
|
||||||
systemd.services.lastlog2-import.enable = lib.mkForce false;
|
systemd.services.lastlog2-import.enable = lib.mkForce false;
|
||||||
systemd.services.suid-sgid-wrappers.enable = lib.mkForce false;
|
systemd.services.suid-sgid-wrappers.enable = lib.mkForce false;
|
||||||
systemd.services.microvm-nixos-activation = {
|
|
||||||
enable = true;
|
|
||||||
description = "NixOS Activation";
|
|
||||||
wantedBy = ["local-fs.target"];
|
|
||||||
before = ["systemd-tmpfiles-setup.service"];
|
|
||||||
requires = ["systemd-tmpfiles-setup.service"];
|
|
||||||
unitConfig.DefaultDependencies = false;
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
PassEnvironment = ["MICROVM_CLOSURE" "MICROVM_UID" "MICROVM_GID"];
|
|
||||||
} // useTTY;
|
|
||||||
script = ''
|
|
||||||
PATH=$MICROVM_CLOSURE/sw/bin
|
|
||||||
cp /etc/resolv.conf /run/
|
|
||||||
$MICROVM_CLOSURE/activate || true
|
|
||||||
chown 1337:1337 /run
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Configure user accounts
|
# Configure user accounts
|
||||||
# The immutable overlay wants userborn or sysusers.. we just want baked-in files w/o running a service.
|
# The immutable overlay wants userborn or sysusers.. we just want baked-in files w/o running a service.
|
||||||
|
|
@ -139,7 +120,6 @@ in {
|
||||||
};
|
};
|
||||||
users.groups.appvm.gid = 1337;
|
users.groups.appvm.gid = 1337;
|
||||||
users.allowNoPasswordLogin = true;
|
users.allowNoPasswordLogin = true;
|
||||||
systemd.tmpfiles.rules = ["d ${runtimeDir} 0755 1337 1337 -"];
|
|
||||||
|
|
||||||
# Configure services
|
# Configure services
|
||||||
|
|
||||||
|
|
@ -148,7 +128,6 @@ in {
|
||||||
description = "microVM Application runner";
|
description = "microVM Application runner";
|
||||||
onFailure = ["exit.target"];
|
onFailure = ["exit.target"];
|
||||||
onSuccess = ["exit.target"];
|
onSuccess = ["exit.target"];
|
||||||
after = ["microvm-nixos-activation.service"];
|
|
||||||
wantedBy = ["microvm.target"];
|
wantedBy = ["microvm.target"];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "exec";
|
Type = "exec";
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
{ writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap, sidebus-broker, mesa }:
|
{ stdenv, writeScriptBin, symlinkJoin, makeWrapper, muvm, passt, bubblewrap, sidebus-broker, mesa, rustc }:
|
||||||
|
|
||||||
let
|
let
|
||||||
munixScript = (writeScriptBin "munix" (builtins.readFile ../../munix)).overrideAttrs(old: {
|
munixScript = (writeScriptBin "munix" (builtins.readFile ../../munix)).overrideAttrs(old: {
|
||||||
buildCommand = "${old.buildCommand}\n patchShebangs $out";
|
buildCommand = "${old.buildCommand}\n patchShebangs $out";
|
||||||
});
|
});
|
||||||
|
microActivate = stdenv.mkDerivation {
|
||||||
|
name = "micro-activate";
|
||||||
|
src = ../../micro-activate.rs;
|
||||||
|
dontUnpack = true;
|
||||||
|
nativeBuildInputs = [ rustc ];
|
||||||
|
buildPhase = ''
|
||||||
|
rustc -C opt-level=s -C panic=abort --edition 2024 -o micro-activate $src
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
mv micro-activate $out/bin
|
||||||
|
'';
|
||||||
|
};
|
||||||
in symlinkJoin {
|
in symlinkJoin {
|
||||||
name = "munix";
|
name = "munix";
|
||||||
paths = [ munixScript muvm passt bubblewrap sidebus-broker ];
|
paths = [ munixScript microActivate muvm passt bubblewrap sidebus-broker ];
|
||||||
buildInputs = [ makeWrapper ];
|
buildInputs = [ makeWrapper ];
|
||||||
postBuild = ''
|
postBuild = ''
|
||||||
wrapProgram $out/bin/munix --prefix PATH : $out/bin --set FALLBACK_OPENGL_DRIVER ${mesa}
|
wrapProgram $out/bin/munix --prefix PATH : $out/bin --set FALLBACK_OPENGL_DRIVER ${mesa}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue