Make any NixOS system netbootable with 10s cycle times.

Overview

nix-netboot-serve

Dynamically generate netboot images for arbitrary NixOS system closures, profiles, or configurations with 10s iteration times.

Usage

Create working directories for it:

mkdir ./gc-roots ./profiles ./configurations ./cpio-cache

Then start up the server:

RUST_LOG=info cargo run -- --gc-root-dir ./gc-roots --config-dir ./configurations --profile-dir ./profiles/ --cpio-cache-dir ./cpio-cache/ --listen 127.0.0.1:3030

See ./boot.sh for an example of booting with QEMU.

Booting an absolute closure

How To

To boot from a specific closure like /nix/store/0m60ngchp6ki34jpwmpbdx3fby6ya0sf-nixos-system-nginx-21.11pre307912.fe01052444c, use /boot/0m60ngchp6ki34jpwmpbdx3fby6ya0sf-nixos-system-nginx-21.11pre307912.fe01052444c/netboot.ipxe as your chain url.

Behavior

As long as that closure exists on the host, that closure will always be booted, unchanged.

Booting a profile

How To

In the profiles directory, create symlinks to top level system paths. For example:

$ ls -la profiles/
example-host -> /nix/store/4y829p7lljdvwnmsk6pnig3mlh6ygklj-nixos-system-example-host-21.11pre130979.gfedcba

then use /dispatch/profile/example-host to boot it.

Behavior

The symlink will be resolved every time a machine boots.

Booting a configuration

How To

In the configurations directory, create a directory for each system, and create a default.nix inside. For example:

$ tree configurations/
configurations/
└── m1.small
    └── default.nix

In the default.nix, create an expression with your NixOS configuration ready to be built:

(import 
    {
    configuration = { pkgs, ... }: {
        networking.hostName = "m1small";
        environment.systemPackages = [ pkgs.hello ];
        fileSystems."/" = {
            device = "/dev/bogus";
            fsType = "ext4";
        };
        boot.loader.grub.devices = [ "/dev/bogus" ];
        boot.postBootCommands = ''
            PATH=${pkgs.nix}/bin /nix/.nix-netboot-serve-db/register
        '';
    };
}).system

Then use /dispatch/configuration/m1.small to boot it.

Behavior

The configuration will be nix-build once per boot, and create a symlink in the --gc-root-dir directory with the same name as the configuration.

If the build fails, the ipxe client will be told to retry in 5s.

Note: there is currently a buggy race condition. In the following circumstance:

  1. machine A turns on
  2. machine B turns on
  3. machine A hits the build URL and a long build starts
  4. you change the configuration to have a very short build
  5. machine B hits the build URL and the short build starts
  6. machine B's configuration finishes building
  7. machine B boots the short build configuration
  8. machine A's configuration finishes building
  9. machine A boots the short configuration instead of the long configuration

Notes on NixOS Configuration

Booting a machine from this server will completely ignore any of the defined fileSystems, everything will run out of RAM.

This system assumes a normal NixOS system booting off a regular disk: trying to use this to netboot a USB installer will not work.

If you don't have an existing configuration to start with, you could start with this:

{
    fileSystems."/" = {
        device = "/dev/bogus";
        fsType = "ext4";
    };
    boot.loader.grub.devices = [ "/dev/bogus" ];
}

Theory of Operation

Linux's boot process starts with two things:

  1. the kernel
  2. an initrd, or an initial ram disk

The ramdisk has all the files needed to mount any disks and start any software needed for the machine. Typically the ramdisk is constructed of a CPIO, a very simple file archive.

Linux supports a special case of its initrd being comprised of multiple CPIOs. By simply concatenating two CPIOs together, Linux's boot process will see the merged contents of both CPIOs.

Furthermore, individual CPIOs can be compressed independently, merged together with concatenation, and Linux will decompress and read each CPIO independently.

A NixOS system is comprised of hundreds of independent, immutable /nix/store paths.

Merging these together, we can dynamically create a single, compressed CPIO per Nix store path and cache it for later.

When a new boot request comes in, the software fetches the list of Nix store paths for the requested NixOS system. Then, every path has a CPIO built for it. Once each store path has a CPIO, the results are streamed back to the iPXE client. By caching the resulting CPIO, iterative development on a system configuration can result in just 3-5 new CPIOs per change.

Improvements over NixOS's NetBoot Support

NixOS's NetBoot image creation support works well, however iterating on a single closure involves recreating the CPIO and recompressing for every store path every time. This can add several minutes to cycle time.

Caveats

Loading the Nix Database

Before using Nix inside the booted machine, make sure to load the Nix database. To do that, add this to your NixOS configuration:

{ pkgs, ... }: {
    boot.postBootCommands = ''
        PATH=${pkgs.nix}/bin /nix/.nix-netboot-serve-db/register
    '';
}

This is not necessary if the system will not execute Nix commands.

Comments
  • [feat] pure-rust-cpio

    [feat] pure-rust-cpio

    This adds a private function to make a cpio archive of a given directory, but lacks integration testing or roundtrip testing with gnu cpio currently. Will pick this up and complete tests when i get a moment!

    opened by emattiza 2
  • Doesn't support UEFI

    Doesn't support UEFI

    When trying to boot a VM over iPXE utilizing OVMF (QEMU EFI software), the following error can be observed:

    >>Start PXE over IPv4.
      Station IP address is 10.0.2.26
    
      Server IP address is 10.0.2.1
      NBP filename is http://10.0.2.1:3030/dispatch/configuration/small
      NBP filesize is 0 Bytes
      PXE-E99: Unexpected network error.
    BdsDxe: failed to load Boot0005 "UEFI PXEv4 (MAC:000000000000)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(000000000000,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0): Not Found
    

    After reading https://ipxe.org/appnote/uefihttp, it appears that iPXE expects an EFI file to be served, while this is serving the standard ipxe file.

    opened by cole-h 2
  • Deleting CPIOs can cause problems serving initrds

    Deleting CPIOs can cause problems serving initrds

    Getting a cpio from the in-memory CpioCache can cause a false-positive cache hit:

    fn get_cached(&self, path: &Path) -> Option<Cpio> {
            let cpio = self.cache
                .read()
                .expect("Failed to get a read lock on the cpio cache")
                .get(path)
                .map(|entry| entry.clone())?;
        }
    

    The problem is the new cpio won't have an open handle to the cpio on disk, and it can't be read. We should verify the cpio actually exists and return None if it doesn't.

    We may also want to explicitly close the handle in the CpioCache so that deleting CPIOs off disk actually works. Also, this is screaming out for "help! we need a garbage collector!"

    opened by grahamc 1
  • hydra: pass --indirect to work on older Nix's

    hydra: pass --indirect to work on older Nix's

    nix-store --realise used to require --indirect to make a GC root anywhere:

    Jan 05 17:24:44 netboot-springboard nix-netboot-serve[2317232]: error: path '/var/cache/nix-netboot-serve/gc-roots/01ad16e6.packethost.net-nixos-install-equinix-metal-prs-pr-36-small' is not a valid garbage collector root; it's not in the directory '/nix/var/nix/gcroots'
    Jan 05 17:24:44 netboot-springboard nix-netboot-serve[2314216]:  WARN  nix_netboot_serve::dispatch_hydra > Failed to realize output /nix/store/mm5614whs2i7x6vl98q1k7nzx3bbpp33-nixos-system-nixos-21.11pre-git for "small"
    
    opened by grahamc 1
  • dispatch: support passing further kernel cmdline arguments

    dispatch: support passing further kernel cmdline arguments

    Passing cmdline_prefix_args and/or cmdline_suffix_args to any dispatcher as query parameters will allow the caller to prefix / suffix kernel cmdline arguments in addition to the arguments defined by the system.

    For example: /dispatch/profile/system?cmdline_suffix_args=loglevel%3D7

    Use case A: passing in dynamically generated password hashes.

    Use case B: passing in a Vault AppRole Role ID, while passing the response-wrapped Secret ID through some other mechanism, like a metadata API service.

    opened by grahamc 0
  • Make smaller things

    Make smaller things

    Instead of a megafile, break the code up in to smaller, digestable chunks.

    Note that basically nothing changed other than moving code from one file to another.

    opened by grahamc 0
  • boot.rs: serve_extlinux variant for serving extlinux to u-boot clients?

    boot.rs: serve_extlinux variant for serving extlinux to u-boot clients?

    Hi,

    Skimming through this, I'm curious if there's opposition to having another url that would serve up an extlinux.conf instead of the ipxe script? Then I could just have a small reverse proxy setup to redirect PXE boot requests to their given profile endpoint within nix-netboot-serve. This would allow me to leverage this same code/module/etc to run a local netboot server for my various in-progress tow-boot clients.

    I did consider that I could throw iPXE into the mix, but I've always gotten the impression that getting that going on certain embedded platforms can be a bit of a hassle.

    opened by colemickens 1
Releases(v0.1.0)
Owner
Determinate Systems
Determinate Systems
Fake ping times.

Pong A Linux program that replies to ping but modifies the payload of the ICMP package to get lower ping times in some ping implementations. See https

Mara Bos 136 Sep 21, 2022
Showcase for pathological compile times when using knuffel / chumsky / VERY LARGE types

netherquote Showcase for pathological compile times when using knuffel / chumsky / VERY LARGE types. How to reproduce The rust toolchain version is pi

Amos Wenger 7 Jan 1, 2023
Stopwatch lib for rust. Start, pause, reset and lap like any stopwatch.

Chronometer Stopwatch lib for rust. Start, pause, reset and lap like any stopwatch. Nothing special I'm just learning rust. Getting Started Add this l

Naoufel Berrada 2 Sep 29, 2022
messloc is a drop in replacement for malloc that can transparently recover from memory fragmentation without any changes to application code.

messloc is a drop in replacement for malloc that can transparently recover from memory fragmentation without any changes to application code. Goals Al

null 11 Dec 10, 2022
Rust Lambda Extension for any Runtime to preload SSM Parameters as 🔐 Secure Environment Variables!

?? Crypteia Rust Lambda Extension for any Runtime to preload SSM Parameters as Secure Environment Variables! Super fast and only performaned once duri

Custom Ink 34 Jan 7, 2023
Select any exported function in a dll as the new dll's entry point.

Description This tool will patch the entry point of the input dll and replace it with the RVA of another exported function in that same dll. This allo

Kurosh Dabbagh Escalante 43 Jun 7, 2023
Make ELF formatted apps configurable

elfredo `elfredo` is a library that allows you to patch executables after they were compiled. It utilize an extra embedded section to store data/confi

Asaf Fisher 7 Sep 5, 2021
cargo, make me a project

cargo-generate cargo, make me a project cargo-generate is a developer tool to help you get up and running quickly with a new Rust project by leveragin

null 1.2k Jan 3, 2023
Make the github cli even better with fuzzy finding

github-repo-clone (grc) Github Repo Clone is a command line utility written in rust that leverages the power of fuzzy finding with the github cli Usag

Jared Moulton 2 Jul 9, 2022
Rust macro to make recursive function run on the heap (i.e. no stack overflow).

Decurse Example #[decurse::decurse] // ?? Slap this on your recursive function and stop worrying about stack overflow! fn factorial(x: u32) -> u32 {

Wisha W. 18 Dec 28, 2022
Make friends while searching.

searchbuddy Make friends while searching! Searchbuddy is a browser extension that lets you chat with people that are searching for what you're searchi

Joseph Gerber 14 May 23, 2022
Thin wrapper around [`tokio::process`] to make it streamable

This library provide ProcessExt to create your own custom process

null 4 Jun 25, 2022
Bolt is a desktop application that is designed to make the process of developing and testing APIs easier and more efficient.

Bolt ⚡ Bolt is a desktop application that is designed to make the process of developing and testing APIs easier and more efficient. Quick start ??‍??

0xHiro 6 Mar 26, 2023
wasm actor system based on lunatic

Wactor WASM actor system based on lunatic. Actors run on isolated green threads. They cannot share memory, and communicate only through input and outp

Noah Corona 25 Nov 8, 2022
EIP 1337 Subscription Billing System

CasperLabs EIP 1337 Token Subscription for the CasperLabs platform. Install Make sure wasm32-unknown-unknown is installed. $ make prepare Build Smart

David Tai 4 Jun 2, 2022
A custom invoke system for Tauri that leverages a localhost server

Tauri Invoke HTTP This is a crate that provides a custom invoke system for Tauri using a localhost server. Each message is delivered through a XMLHttp

Tauri 17 Dec 17, 2022
A super super super voting system on near block chain :)

Disp41r-Super-Voting-System a super super super voting system on near block chain :) Finish! I call it super voting system, because it is really cool!

Disp41r 1 Jan 15, 2022
The system for remote workers to prevent their family members from interrupting conference calls

onair The system for remote workers to prevent their family members from interrupting conference calls. The system is designed to automatically detect

Yushi OMOTE 6 Sep 21, 2022
Garden monitoring system using m328p and m2560 Arduino Uno boards

Garden monitoring system using m328p and m2560 Arduino Uno boards. 100% Rust [no_std] using the avr hardware abstraction layer (avr-hal)

Ethan Gallucci 1 May 4, 2022