use tatara_nix::derivation::{BridgeTarget, Derivation, Outputs, Source};
use tatara_nix::synth::{Artifact, MultiSynthesizer};
use tatara_os::SystemConfig;
use crate::config::{GuestKernel, GuestRootfs, Hypervisor, VmSpec};
use crate::rootfs::{InitrdFile, LinuxRootfs};
use crate::vfkit::VfkitEmitter;
pub struct BootManifest {
pub kernel: Derivation,
pub initrd: Derivation,
pub vm: VmSpec,
}
pub fn compose(
sys: &SystemConfig,
init_binary_path: impl Into<String>,
vm: Option<VmSpec>,
) -> BootManifest {
let init_path = init_binary_path.into();
let hostname = sys.hostname.clone();
let kernel_attr = match &sys.kernel {
tatara_os::KernelSpec::Bridge { attr_path } => attr_path.clone(),
tatara_os::KernelSpec::Package { name } => name.clone(),
tatara_os::KernelSpec::Custom { .. } => "linuxPackages.kernel".into(),
};
let kernel = Derivation {
name: format!("kernel-{}", sanitize(&hostname)),
version: None,
inputs: vec![],
source: Source::default(),
builder: Default::default(),
outputs: Outputs::default(),
env: vec![],
sandbox: Default::default(),
bridge: Some(BridgeTarget::nixpkgs(kernel_attr)),
nix_expr: None,
};
let shares_for_init: Vec<crate::config::ShareSpec> = vm
.as_ref()
.map(|v| v.shares.clone())
.unwrap_or_default();
let init_config = synthesize_init_config(sys, &shares_for_init);
let mut rootfs = LinuxRootfs::new(&init_path, init_config)
.with_name(format!("initrd-{}", sanitize(&hostname)));
rootfs = rootfs.with_file("/etc/hostname", format!("{}\n", sys.hostname));
for f in &sys.environment.etc_files {
let path = if f.path.starts_with('/') {
f.path.clone()
} else {
format!("/etc/{}", f.path)
};
rootfs.extra_files.push(InitrdFile {
path,
content: crate::rootfs::InitrdContent::Inline(f.content.clone()),
mode: 0o644,
});
}
if let Some(sshd) = &sys.sshd {
rootfs = rootfs.with_sshd(sshd.clone());
}
if !sys.packages.is_empty() {
rootfs = rootfs.with_packages(sys.packages.iter().cloned());
}
let initrd = rootfs.derivation();
let mut vm = vm.unwrap_or_else(|| VmSpec::plex_default(&hostname));
vm.hypervisor = Hypervisor::Vfkit;
vm.kernel = GuestKernel::Custom {
derivation: kernel.clone(),
};
vm.rootfs = GuestRootfs::Image {
derivation: initrd.clone(),
};
vm.initrd = Some(initrd.clone());
if !vm
.cmdline
.iter()
.any(|s| s.contains("init=/bin/tatara-init"))
{
vm.cmdline.push("init=/bin/tatara-init".into());
}
BootManifest { kernel, initrd, vm }
}
fn synthesize_init_config(sys: &SystemConfig, shares: &[crate::config::ShareSpec]) -> String {
let mut s = format!(
"; auto-generated by tatara-vm::boot for '{}'\n",
sys.hostname
);
s.push_str(&format!("(definit\n :name \"{}-boot\"\n", sys.hostname));
let sshd_svc = sys.sshd.as_ref().map(|sshd| {
format!(
" (:name \"sshd\" :exec \"/bin/sshd -D -f /etc/ssh/sshd_config -p {port}\" :enable #t)\n",
port = sshd.port,
)
});
if sys.services.is_empty() && sshd_svc.is_none() {
s.push_str(" :services ()\n");
} else {
s.push_str(" :services (\n");
if let Some(svc) = sshd_svc {
s.push_str(&svc);
}
for svc in &sys.services {
let enable = if svc.enable { "#t" } else { "#f" };
s.push_str(&format!(
" (:name \"{}\" :exec \"{}\" :enable {})\n",
svc.name,
svc.exec.replace('"', "\\\""),
enable
));
}
s.push_str(" )\n");
}
if shares.is_empty() {
s.push_str(" :mounts ()");
} else {
s.push_str(" :mounts (\n");
for sh in shares {
let tag = mount_tag_for_guest_path(&sh.guest);
let opts = if sh.read_only { "ro" } else { "rw" };
s.push_str(&format!(
" (:source \"{}\" :target \"{}\" :fstype \"virtiofs\" :options \"{}\")\n",
tag, sh.guest, opts
));
}
s.push_str(" )");
}
s.push_str(")\n");
s
}
pub fn mount_tag_for_guest_path(guest: &str) -> String {
let mut out = String::new();
for c in guest.chars() {
if c.is_ascii_alphanumeric() {
out.push(c);
} else {
out.push('_');
}
}
let trimmed: String = out.chars().skip_while(|c| *c == '_').collect();
if trimmed.is_empty() {
"share".into()
} else {
trimmed
}
}
fn sanitize(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' {
c
} else {
'-'
}
})
.collect()
}
pub struct BootSynthesizer {
pub init_binary_path: String,
pub vm_override: Option<VmSpec>,
pub out_prefix: String,
pub busybox: bool,
}
impl Default for BootSynthesizer {
fn default() -> Self {
Self {
init_binary_path: "${pkgs.hello}/bin/hello".into(),
vm_override: None,
out_prefix: "boot".into(),
busybox: true,
}
}
}
impl BootSynthesizer {
pub fn new() -> Self {
Self::default()
}
pub fn with_init_binary_path(mut self, p: impl Into<String>) -> Self {
self.init_binary_path = p.into();
self
}
pub fn with_out_prefix(mut self, p: impl Into<String>) -> Self {
self.out_prefix = p.into();
self
}
pub fn with_vm_override(mut self, vm: VmSpec) -> Self {
self.vm_override = Some(vm);
self
}
pub fn with_busybox(mut self, on: bool) -> Self {
self.busybox = on;
self
}
}
impl MultiSynthesizer for BootSynthesizer {
type Input = SystemConfig;
fn generate_all(&self, cfg: &SystemConfig) -> Vec<Artifact> {
let mut bm = compose(cfg, self.init_binary_path.clone(), self.vm_override.clone());
if !self.busybox {
let shares = self
.vm_override
.as_ref()
.map(|v| v.shares.clone())
.unwrap_or_default();
let mut rootfs = crate::rootfs::LinuxRootfs::new(
self.init_binary_path.clone(),
synthesize_init_config(cfg, &shares),
)
.with_name(format!("initrd-{}", sanitize(&cfg.hostname)))
.without_busybox();
rootfs = rootfs.with_file("/etc/hostname", format!("{}\n", cfg.hostname));
for f in &cfg.environment.etc_files {
let path = if f.path.starts_with('/') {
f.path.clone()
} else {
format!("/etc/{}", f.path)
};
rootfs.extra_files.push(crate::rootfs::InitrdFile {
path,
content: crate::rootfs::InitrdContent::Inline(f.content.clone()),
mode: 0o644,
});
}
bm.initrd = rootfs.derivation();
bm.vm.rootfs = crate::config::GuestRootfs::Image {
derivation: bm.initrd.clone(),
};
bm.vm.initrd = Some(bm.initrd.clone());
}
let prefix = &self.out_prefix;
let vfkit = VfkitEmitter::new();
let mut arts = vfkit.generate_all(&bm.vm);
for a in &mut arts {
if let Some(suffix) = a.path.strip_prefix(&format!("vm/{}/", bm.vm.name)) {
a.path = format!("{prefix}/{suffix}");
}
}
let kernel_expr = match &bm.kernel.bridge {
Some(b) if b.pkg_set.is_none() => format!(
"# kernel for {}\n(import <nixpkgs> {{ system = \"{}\"; }}).{}\n",
cfg.hostname, cfg.system, b.attr_path
),
Some(b) => format!(
"# kernel for {}\n({}).{}\n",
cfg.hostname,
b.resolved_pkg_set(),
b.attr_path
),
None => "# (custom kernel — no bridge)\n".into(),
};
let initrd_expr = bm
.initrd
.nix_expr
.clone()
.unwrap_or_else(|| "# (initrd has no nix_expr — unexpected)\n".into());
arts.push(Artifact::new(format!("{prefix}/kernel.nix"), kernel_expr));
arts.push(Artifact::new(format!("{prefix}/initrd.nix"), initrd_expr));
let shares_for_init = self
.vm_override
.as_ref()
.map(|v| v.shares.clone())
.unwrap_or_default();
arts.push(Artifact::new(
format!("{prefix}/init.lisp"),
synthesize_init_config(cfg, &shares_for_init),
));
if let Ok(json) = serde_json::to_string_pretty(cfg) {
arts.push(Artifact::new(format!("{prefix}/system.json"), json));
}
arts.push(Artifact::new(
format!("{prefix}/README.md"),
render_readme(cfg, &bm),
));
arts
}
}
fn render_readme(cfg: &SystemConfig, bm: &BootManifest) -> String {
format!(
"# tatara-os boot artifact — `{hostname}`\n\n\
Generated from a `(defsystem …)` Lisp form via `tatara-vm::BootSynthesizer`.\n\n\
## Files\n\n\
- `system.json` — the typed `SystemConfig`\n\
- `init.lisp` — the tatara-init supervisor config (baked into the initrd)\n\
- `kernel.nix` — `nix build -f kernel.nix` → a Linux kernel derivation\n\
- `initrd.nix` — `nix build -f initrd.nix` → `{initrd_name}/initrd.cpio.gz`\n\
- `vm.json` — vfkit config with placeholders for the realized paths\n\
- `boot.sh` — helper that runs `vfkit --config vm.json`\n\n\
## To boot\n\n\
```sh\n\
KERNEL=$(nix build -f kernel.nix --no-link --print-out-paths)/bzImage\n\
INITRD=$(nix build -f initrd.nix --no-link --print-out-paths)/initrd.cpio.gz\n\
# Substitute paths into vm.json (jq recommended) and run:\n\
./boot.sh\n\
```\n\n\
## Spec\n\n\
- Host: `{hostname}` on `{system}`\n\
- Init system: `{init:?}` (tatara-init is PID 1 by default)\n\
- Services: {n_services}\n\
- Kernel: `{kernel_name}`\n\
- Initrd: `{initrd_name}`\n\
- vfkit CPUs: {cpus}, memory: {mem_mib} MiB\n",
hostname = cfg.hostname,
system = cfg.system,
init = cfg.init,
n_services = cfg.services.len(),
kernel_name = bm.kernel.name,
initrd_name = bm.initrd.name,
cpus = bm.vm.cpus,
mem_mib = bm.vm.memory_mib,
)
}
#[cfg(test)]
mod tests {
use super::*;
fn sys() -> SystemConfig {
SystemConfig {
hostname: "plex".into(),
system: "aarch64-linux".into(),
kernel: tatara_os::KernelSpec::Bridge {
attr_path: "linuxPackages.kernel".into(),
},
bootloader: Default::default(),
init: tatara_os::InitSystem::Tatara,
services: vec![
tatara_os::ServiceSpec {
name: "demo".into(),
exec: "/bin/busybox sh -c 'echo tatara'".into(),
enable: true,
extra: vec![],
package_refs: vec![],
},
tatara_os::ServiceSpec {
name: "disabled-one".into(),
exec: "/bin/disabled".into(),
enable: false,
extra: vec![],
package_refs: vec![],
},
],
users: vec![],
filesystems: vec![],
environment: Default::default(),
packages: vec![],
sshd: None,
}
}
#[test]
fn compose_produces_kernel_initrd_and_vm() {
let bm = compose(&sys(), "/nix/store/xxx-tatara-init/bin/tatara-init", None);
assert_eq!(bm.kernel.name, "kernel-plex");
assert!(bm.kernel.bridge.is_some());
assert_eq!(bm.initrd.name, "initrd-plex");
assert!(bm.initrd.nix_expr.is_some());
assert_eq!(bm.vm.name, "plex");
}
#[test]
fn cmdline_gets_tatara_init_appended() {
let mut custom = VmSpec::plex_default("plex");
custom.cmdline = vec!["console=hvc0".into()]; let bm = compose(&sys(), "/nix/store/xxx-init/bin/tatara-init", Some(custom));
assert!(bm
.vm
.cmdline
.iter()
.any(|s| s.contains("init=/bin/tatara-init")));
}
#[test]
fn init_lisp_lists_enabled_services_only_as_enabled() {
let bm = compose(&sys(), "/nix/store/xxx-init/bin/tatara-init", None);
let expr = bm.initrd.nix_expr.unwrap();
assert!(expr.contains("(:name \"demo\" :exec"));
assert!(expr.contains(":enable #t"));
assert!(expr.contains("(:name \"disabled-one\" :exec"));
assert!(expr.contains(":enable #f"));
}
#[test]
fn etc_hostname_is_included() {
let bm = compose(&sys(), "/nix/store/xxx-init/bin/tatara-init", None);
let expr = bm.initrd.nix_expr.unwrap();
assert!(expr.contains("root/etc/hostname"));
assert!(expr.contains("plex"));
}
#[test]
fn synthesizer_emits_full_artifact_tree() {
let s = BootSynthesizer::new().with_out_prefix("out");
let arts = s.generate_all(&sys());
let paths: Vec<&str> = arts.iter().map(|a| a.path.as_str()).collect();
for expected in [
"out/vm.json",
"out/boot.sh",
"out/kernel.nix",
"out/initrd.nix",
"out/init.lisp",
"out/system.json",
"out/README.md",
] {
assert!(
paths.contains(&expected),
"missing artifact: {expected}\n got: {paths:?}"
);
}
}
#[test]
fn synthesizer_kernel_nix_is_buildable_expression() {
let s = BootSynthesizer::new();
let arts = s.generate_all(&sys());
let kernel = arts
.iter()
.find(|a| a.path.ends_with("kernel.nix"))
.unwrap();
assert!(kernel.content.contains("import <nixpkgs>"));
assert!(kernel.content.contains(".linuxPackages.kernel"));
}
#[test]
fn synthesizer_initrd_nix_is_buildable_expression() {
let s = BootSynthesizer::new();
let arts = s.generate_all(&sys());
let initrd = arts
.iter()
.find(|a| a.path.ends_with("initrd.nix"))
.unwrap();
assert!(initrd.content.contains("runCommand"));
assert!(initrd.content.contains("initrd.cpio.gz"));
assert!(initrd.content.contains("tatara-init"));
}
#[test]
fn synthesizer_readme_is_populated_from_spec() {
let s = BootSynthesizer::new();
let arts = s.generate_all(&sys());
let readme = arts.iter().find(|a| a.path.ends_with("README.md")).unwrap();
assert!(readme.content.contains("plex"));
assert!(readme.content.contains("aarch64-linux"));
assert!(readme.content.contains("Services: 2"));
}
#[test]
fn custom_kernel_package_propagates_to_bridge() {
let mut s = sys();
s.kernel = tatara_os::KernelSpec::Bridge {
attr_path: "linuxPackages_latest.kernel".into(),
};
let bm = compose(&s, "/nix/store/x/bin/tatara-init", None);
assert_eq!(
bm.kernel.bridge.unwrap().attr_path,
"linuxPackages_latest.kernel"
);
}
}