Jump to content

Netboot: Difference between revisions

From Official NixOS Wiki
imported>Erikarvstedt
mNo edit summary
Onny (talk | contribs)
Cleanup page
 
(27 intermediate revisions by 13 users not shown)
Line 1: Line 1:
== Building and serving a netboot image ==
This provides an easy way to serve the NixOS installer over netboot, such as when you already have a working NixOS machine and want to install NixOS on a second machine connected to the same network.


=== Example ===
== Setup ==
This example uses [https://github.com/danderson/netboot/tree/master/pixiecore pixicore] for hosting, which works in an ordinary network environment with an existing DHCP server.
This example uses [https://github.com/danderson/netboot/tree/main/pixiecore Pixiecore] for hosting, which works in an ordinary network environment with an existing DHCP server. Pixiecore will notice when the booted machine talks to the network's existing DHCP server, and send netboot information to it at that time.
{{Note|Your iPXE must be recent enough to support https:// links}}<syntaxhighlight lang="nix">
services.pixiecore = {
  enable = true;
  openFirewall = true;
  dhcpNoBind = true;
  kernel = "https://boot.netboot.xyz";
};


<syntaxHighlight lang=bash>
</syntaxhighlight>The Pixicore server will provide a [https://netboot.xyz netboot.xyz] multi-boot image to the clients, offering various operating systems which will get downloaded by the client on demand.
#!/usr/bin/env bash


set -euo pipefail
== Tips and tricks ==


nix-build --out-link /tmp/netboot - <<'EOF'
=== Serve custom NixOS installation images ===
Create file <code>system.nix</code>:
<syntaxhighlight lang="nix">let
  nixpkgs = builtins.getFlake "github:nixos/nixpkgs/nixos-25.11";
 
  sys = nixpkgs.lib.nixosSystem {
    system = "x86_64-linux";
    modules = [
      ({ config, pkgs, lib, modulesPath, ... }: {
        imports = [
          (modulesPath + "/installer/netboot/netboot-minimal.nix")
        ];
        config = {
          ## Some useful options for setting up a new system
          # services.getty.autologinUser = lib.mkForce "root";
          # users.users.root.openssh.authorizedKeys.keys = [ ... ];
          # console.keyMap = "de";
          # hardware.video.hidpi.enable = true;
 
          system.stateVersion = config.system.nixos.release;
        };
      })
    ];
  };
 
  run-pixiecore = let
    hostPkgs = if sys.pkgs.system == builtins.currentSystem
              then sys.pkgs
              else nixpkgs.legacyPackages.${builtins.currentSystem};
    build = sys.config.system.build;
  in hostPkgs.writers.writeBash "run-pixiecore" ''
    exec ${hostPkgs.pixiecore}/bin/pixiecore \
      boot ${build.kernel}/bzImage ${build.netbootRamdisk}/initrd \
      --cmdline "init=${build.toplevel}/init loglevel=4" \
      --debug --dhcp-no-bind \
      --port 64172 --status-port 64172 "$@"
  '';
in
  run-pixiecore</syntaxhighlight>Building:<syntaxhighlight lang="bash">
# Build pixiecore runner
nix-build system.nix -o /tmp/run-pixiecore
</syntaxhighlight>Running:<syntaxhighlight lang="bash">
# Open required firewall ports
sudo iptables -w -I nixos-fw -p udp -m multiport --dports 67,69,4011 -j ACCEPT
sudo iptables -w -I nixos-fw -p tcp -m tcp --dport 64172 -j ACCEPT
 
# Run pixiecore
sudo $(realpath /tmp/run-pixiecore)
 
# Close ports
sudo iptables -w -D nixos-fw -p udp -m multiport --dports 67,69,4011 -j ACCEPT
sudo iptables -w -D nixos-fw -p tcp -m tcp --dport 64172 -j ACCEPT
 
</syntaxhighlight>
 
==== Another example ====
{{file|netboot.nix|nix|3={
  name ? "netboot",
  arch ? "x86_64-linux",
  configuration ? _: { }, # --arg configuration 'import ./netboot-config.nix'
  legacy ? false, # variation with pxelinux and dnsmasq for older systems
  cmdline ? [ ],
  loglevel ? 4,
  pixiecoreport ? 64172,
  proxynets ? [ "192.168.0.0" ],
  serialconsole ? false,
  serialport ? 0,
  serialspeed ? 9600,
  nixpkgs ? import <nixpkgs> { },
  ...
}:
with nixpkgs;
with lib;
let
let
  bootSystem = import <nixpkgs/nixos> {
    # system = ...;


    configuration = { config, pkgs, lib, ... }: with lib; {
  example-configuration =
       imports = [
    { pkgs, config, ... }:
           <nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix>
    with pkgs;
      ];
    {
      ## Some useful options for setting up a new system
       config = {
      services.mingetty.autologinUser = mkForce "root";
        environment.systemPackages = [
      # Enable sshd which gets disabled by netboot-minimal.nix
           mtr
      systemd.services.sshd.wantedBy = mkOverride 0 [ "multi-user.target" ];
          bridge-utils
      # users.users.root.openssh.authorizedKeys.keys = [ ... ];
          vlan
      # i18n.consoleKeyMap = "de";
          ethtool
          jwhois
          sipcalc
          netcat-openbsd
          tsocks
          psmisc
          pciutils
          usbutils
          lm_sensors
          dmidecode
          microcom
          unar
          mkpasswd
          ripgrep
          wget
          rsync
          sshfs-fuse
          iperf3
          mc
          mutt
          borgbackup
          rxvt-unicode
        ];
        # users.users.nixos.openssh.authorizedKeys.keys = [ … ];
        # services.openssh = { ports = [2]; settings.PasswordAuthentication = false; };
        # virtualisation.lxc.enable = true;
      };
     };
     };
  config = import <nixpkgs/nixos/lib/eval-config.nix> {
    # see <nixpkgs/nixos/release.nix>
    system = arch;
    modules = [
      <nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix>
      # Reduce build time by ~7x (~1 minute instead of many minutes) by not using the highest compression (image is 5% larger).
      ({ ... }: { netboot.squashfsCompression = "zstd -Xcompression-level 6"; })
      version-module
      example-configuration
      configuration
    ];
   };
   };


   pkgs = import <nixpkgs> {};
   version-module =
    { config, ... }:
    {
      system.stateVersion = builtins.substring 0 (builtins.stringLength "XX.XX") config.system.nixos.version;
      system.nixos.tags = [ name ];
    };
 
  run-pixiecore = writeShellScript "${name}-run-pixiecore" ''
    exec ${pixiecore}/bin/pixiecore \
      boot ${kernel} ${initrd} \
      --cmdline "${cmd-line}" \
      --debug --dhcp-no-bind --log-timestamps \
      --port ${toString pixiecoreport} \
      --status-port ${toString pixiecoreport} "$@"
  '';
 
  run-dnsmasq = writeShellScript "${name}-run-dnsmasq" ''
    exec ${dnsmasq}/bin/dnsmasq \
      -d -k --no-daemon -C "${dnsmasq-conf}" "$@"
  '';
 
  tftp-root = linkFarm "${name}-tftp-root" (
    mapAttrsToList (name: path: { inherit name path; }) {
      "pxelinux.cfg/default" = pxelinux-cfg;
      "pxelinux.0" = "syslinux/pxelinux.0";
      "syslinux" = "${syslinux}/share/syslinux";
      "bzImage" = kernel;
      "initrd" = initrd;
    }
  );
 
  dnsmasq-conf = writeText "${name}-dnsmasq-conf" ''
    pxe-prompt="Booting NixOS..",1
    local-service=net
    dhcp-boot=pxelinux.0
    ${flip concatMapStrings proxynets (net: ''
      dhcp-range=${net},proxy
    '')}
    dhcp-no-override
    dhcp-leasefile=/dev/null
    log-dhcp
    enable-tftp
    tftp-port-range=6900,6999
    tftp-root=${tftp-root}
  '';
 
  cmd-line = concatStringsSep " " (
    [
      "init=${build.toplevel}/init"
      "loglevel=${toString loglevel}"
    ]
    ++ optional serialconsole "console=ttyS${toString serialport},${toString serialspeed}"
    ++ cmdline
  );
 
  pxelinux-cfg = writeText "${name}-pxelinux.cfg" ''
    ${optionalString serialconsole "serial ${toString serialport} ${toString serialspeed}"}
    console 1
    prompt 1
    timeout 37
    default NixOS
    label NixOS
      kernel bzImage
      append initrd=initrd ${cmd-line}
  '';
 
  build = config.config.system.build;
  kernel = "${build.kernel}/${kernel-target}";
  kernel-target = config.pkgs.stdenv.hostPlatform.linux-kernel.target;
  initrd = "${build.netbootRamdisk}/initrd";
 
in
in
  pkgs.symlinkJoin {
if legacy then run-dnsmasq else run-pixiecore|name=netboot.nix|lang=nix}}
    name = "netboot";
 
    paths = with bootSystem.config.system.build; [
Building:
      netbootRamdisk
<syntaxhighlight lang="bash"># Build pixiecore runner
      kernel
nix-build netboot.nix -o /tmp/run-pixiecore
      netbootIpxeScript
    ];
    preferLocalBuild = true;
  }
EOF


n=$(realpath /tmp/netboot)
# Build dnsmasq + pxelinux runner
init=$(grep -ohP 'init=\S+' $n/netboot.ipxe)
nix-build netboot.nix --arg legacy true -o /tmp/run-dnsmasq


# As of May 2020, pixiecore is only available on nixos-unstable
# Build for some ancient system with a serial console
nix build -o /tmp/pixiecore -f channel:nixos-unstable pixiecore
nix-build netboot.nix --arg name '"ancient-netboot"' -o /tmp/run-netboot \
  --arg configuration 'import ./ancient-config.nix' \
  --arg legacy true --arg proxynets '["10.2.1.0"]' \
  --arg serialconsole true --arg serialport 3 --arg serialspeed 115200</syntaxhighlight>Running:


# Start the PXE server.
* Run the example exactly like the other example further up on the page.
# These ports need to be open in your firewall:
# UDP: 67, 69
# TCP: 64172
sudo /tmp/pixiecore/bin/pixiecore \
  boot $n/bzImage $n/initrd \
  --cmdline "$init loglevel=4" \
  --debug --dhcp-no-bind --port 64172 --status-port 64172


</syntaxHighlight>
=== Troubleshooting ===


=== See also ===
* Error "'''autoexec.ipxe... Operation not supported'''": See [https://github.com/NixOS/nixpkgs/pull/378513#pullrequestreview-3081586117 this issue].
NixOS manual: [https://nixos.org/nixos/manual/index.html#sec-booting-from-pxe PXE booting].


NixOS unstable has a Pixiecore service module.
== See also ==


== netboot.xyz ==
* NixOS manual: [https://nixos.org/nixos/manual/index.html#sec-booting-from-pxe PXE booting].
There is now official [https://netboot.xyz/ netboot.xyz] support.
Just select <b>NixOS</b> from Linux installs and you should be ready to go.


<b>Note:</b> Your iPXE must be recent enough to support https:// links
[[Category:Booting]]

Latest revision as of 22:32, 24 February 2026

This provides an easy way to serve the NixOS installer over netboot, such as when you already have a working NixOS machine and want to install NixOS on a second machine connected to the same network.

Setup

This example uses Pixiecore for hosting, which works in an ordinary network environment with an existing DHCP server. Pixiecore will notice when the booted machine talks to the network's existing DHCP server, and send netboot information to it at that time.

Note: Your iPXE must be recent enough to support https:// links
services.pixiecore = {
  enable = true;
  openFirewall = true;
  dhcpNoBind = true;
  kernel = "https://boot.netboot.xyz";
};

The Pixicore server will provide a netboot.xyz multi-boot image to the clients, offering various operating systems which will get downloaded by the client on demand.

Tips and tricks

Serve custom NixOS installation images

Create file system.nix:

let
  nixpkgs = builtins.getFlake "github:nixos/nixpkgs/nixos-25.11";

  sys = nixpkgs.lib.nixosSystem {
    system = "x86_64-linux";
    modules = [
      ({ config, pkgs, lib, modulesPath, ... }: {
        imports = [
          (modulesPath + "/installer/netboot/netboot-minimal.nix")
        ];
        config = {
          ## Some useful options for setting up a new system
          # services.getty.autologinUser = lib.mkForce "root";
          # users.users.root.openssh.authorizedKeys.keys = [ ... ];
          # console.keyMap = "de";
          # hardware.video.hidpi.enable = true;

          system.stateVersion = config.system.nixos.release;
        };
      })
    ];
  };

  run-pixiecore = let
    hostPkgs = if sys.pkgs.system == builtins.currentSystem
               then sys.pkgs
               else nixpkgs.legacyPackages.${builtins.currentSystem};
    build = sys.config.system.build;
  in hostPkgs.writers.writeBash "run-pixiecore" ''
    exec ${hostPkgs.pixiecore}/bin/pixiecore \
      boot ${build.kernel}/bzImage ${build.netbootRamdisk}/initrd \
      --cmdline "init=${build.toplevel}/init loglevel=4" \
      --debug --dhcp-no-bind \
      --port 64172 --status-port 64172 "$@"
  '';
in
  run-pixiecore

Building:

# Build pixiecore runner
nix-build system.nix -o /tmp/run-pixiecore

Running:

# Open required firewall ports
sudo iptables -w -I nixos-fw -p udp -m multiport --dports 67,69,4011 -j ACCEPT
sudo iptables -w -I nixos-fw -p tcp -m tcp --dport 64172 -j ACCEPT

# Run pixiecore
sudo $(realpath /tmp/run-pixiecore)

# Close ports
sudo iptables -w -D nixos-fw -p udp -m multiport --dports 67,69,4011 -j ACCEPT
sudo iptables -w -D nixos-fw -p tcp -m tcp --dport 64172 -j ACCEPT

Another example

❄︎ netboot.nix
{
  name ? "netboot",
  arch ? "x86_64-linux",
  configuration ? _: { }, # --arg configuration 'import ./netboot-config.nix'
  legacy ? false, # variation with pxelinux and dnsmasq for older systems
  cmdline ? [ ],
  loglevel ? 4,
  pixiecoreport ? 64172,
  proxynets ? [ "192.168.0.0" ],
  serialconsole ? false,
  serialport ? 0,
  serialspeed ? 9600,
  nixpkgs ? import <nixpkgs> { },
  ...
}:
with nixpkgs;
with lib;
let

  example-configuration =
    { pkgs, config, ... }:
    with pkgs;
    {
      config = {
        environment.systemPackages = [
          mtr
          bridge-utils
          vlan
          ethtool
          jwhois
          sipcalc
          netcat-openbsd
          tsocks
          psmisc
          pciutils
          usbutils
          lm_sensors
          dmidecode
          microcom
          unar
          mkpasswd
          ripgrep
          wget
          rsync
          sshfs-fuse
          iperf3
          mc
          mutt
          borgbackup
          rxvt-unicode
        ];
        # users.users.nixos.openssh.authorizedKeys.keys = [ … ];
        # services.openssh = { ports = [2]; settings.PasswordAuthentication = false; };
        # virtualisation.lxc.enable = true;
      };
    };

  config = import <nixpkgs/nixos/lib/eval-config.nix> {
    # see <nixpkgs/nixos/release.nix>
    system = arch;
    modules = [
      <nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix>
      # Reduce build time by ~7x (~1 minute instead of many minutes) by not using the highest compression (image is 5% larger).
      ({ ... }: { netboot.squashfsCompression = "zstd -Xcompression-level 6"; })
      version-module
      example-configuration
      configuration
    ];
  };

  version-module =
    { config, ... }:
    {
      system.stateVersion = builtins.substring 0 (builtins.stringLength "XX.XX") config.system.nixos.version;
      system.nixos.tags = [ name ];
    };

  run-pixiecore = writeShellScript "${name}-run-pixiecore" ''
    exec ${pixiecore}/bin/pixiecore \
      boot ${kernel} ${initrd} \
      --cmdline "${cmd-line}" \
      --debug --dhcp-no-bind --log-timestamps \
      --port ${toString pixiecoreport} \
      --status-port ${toString pixiecoreport} "$@"
  '';

  run-dnsmasq = writeShellScript "${name}-run-dnsmasq" ''
    exec ${dnsmasq}/bin/dnsmasq \
      -d -k --no-daemon -C "${dnsmasq-conf}" "$@"
  '';

  tftp-root = linkFarm "${name}-tftp-root" (
    mapAttrsToList (name: path: { inherit name path; }) {
      "pxelinux.cfg/default" = pxelinux-cfg;
      "pxelinux.0" = "syslinux/pxelinux.0";
      "syslinux" = "${syslinux}/share/syslinux";
      "bzImage" = kernel;
      "initrd" = initrd;
    }
  );

  dnsmasq-conf = writeText "${name}-dnsmasq-conf" ''
    pxe-prompt="Booting NixOS..",1
    local-service=net
    dhcp-boot=pxelinux.0
    ${flip concatMapStrings proxynets (net: ''
      dhcp-range=${net},proxy
    '')}
    dhcp-no-override
    dhcp-leasefile=/dev/null
    log-dhcp
    enable-tftp
    tftp-port-range=6900,6999
    tftp-root=${tftp-root}
  '';

  cmd-line = concatStringsSep " " (
    [
      "init=${build.toplevel}/init"
      "loglevel=${toString loglevel}"
    ]
    ++ optional serialconsole "console=ttyS${toString serialport},${toString serialspeed}"
    ++ cmdline
  );

  pxelinux-cfg = writeText "${name}-pxelinux.cfg" ''
    ${optionalString serialconsole "serial ${toString serialport} ${toString serialspeed}"}
    console 1
    prompt 1
    timeout 37
    default NixOS
    label NixOS
      kernel bzImage
      append initrd=initrd ${cmd-line}
  '';

  build = config.config.system.build;
  kernel = "${build.kernel}/${kernel-target}";
  kernel-target = config.pkgs.stdenv.hostPlatform.linux-kernel.target;
  initrd = "${build.netbootRamdisk}/initrd";

in
if legacy then run-dnsmasq else run-pixiecore

Building:

# Build pixiecore runner
nix-build netboot.nix -o /tmp/run-pixiecore

# Build dnsmasq + pxelinux runner
nix-build netboot.nix --arg legacy true -o /tmp/run-dnsmasq

# Build for some ancient system with a serial console
nix-build netboot.nix --arg name '"ancient-netboot"' -o /tmp/run-netboot \
  --arg configuration 'import ./ancient-config.nix' \
  --arg legacy true --arg proxynets '["10.2.1.0"]' \
  --arg serialconsole true --arg serialport 3 --arg serialspeed 115200

Running:

  • Run the example exactly like the other example further up on the page.

Troubleshooting

  • Error "autoexec.ipxe... Operation not supported": See this issue.

See also