Install NixOS on Hetzner Online: Difference between revisions

From NixOS Wiki
imported>Montchr
Update initial Nix and nixos-generators installation steps
Klinger (talk | contribs)
 
(19 intermediate revisions by 9 users not shown)
Line 1: Line 1:
This article is about installing NixOS on [https://www.hetzner.com/dedicated-rootserver?country=us Hetzner Online], which provides dedicated
{{Note|This article is about installing NixOS on Hetzner Online, which provides dedicated bare-metal servers. It is not to be confused with [[Install_NixOS_on_Hetzner_Cloud|Hetzner Cloud]], which provides VPS cloud servers.}}
bare-metal servers.


This is not to be confused with [https://www.hetzner.com/cloud Hetzner Cloud], that provides VMs (an example for how to install NixOS there is shown [https://github.com/nix-community/nixos-install-scripts/blob/master/hosters/hetzner-cloud/nixos-install-hetzner-cloud.sh here]).
== Installation ==


There are three ways at the time to install NixOS on Hetzner dedicated:
There are three ways at the time to install NixOS on Hetzner dedicated:


# From Hetzner's rescue image one can boot into the nixos installer using a custom kexec image that is configured with the fixed IPv6 provided by Hetzner and also contain your ssh key. Tip: The kexec tarball as generated by [https://github.com/nix-community/nixos-generators nixos-generators] can remain put into the /boot partition for future use.  
# From Hetzner's rescue image, one can boot into the nixos installer using a custom kexec image that is configured with the fixed IPv6 provided by Hetzner and also contain your ssh key. Tip: The kexec tarball as generated by [https://github.com/nix-community/nixos-generators nixos-generators] can remain put into the /boot partition for future use.  
# Hetzner also provides an interface to upload your own ISO-images. Also here you may want to build your own iso-image, which has openssh with ssh keys due the lack of a remote console.  
# Hetzner also provides an interface to upload your own ISO-images. Also, here you may want to build your own iso-image, which has openssh with ssh keys due the lack of a remote console.  
# An easier method to install NixOS on Hetzner, is to use the existing integration into NixOps.
# An easier method to install NixOS on Hetzner, is to use the existing integration into NixOps.
# An example to install NixOS in the Hetzner rescue mode, including full RAID partitioning, is available [https://github.com/nix-community/nixos-install-scripts/blob/master/hosters/hetzner-dedicated/hetzner-dedicated-wipe-and-install-nixos.sh here].
# An example to install NixOS in the Hetzner rescue mode, including full RAID partitioning, is available [https://github.com/nix-community/nixos-install-scripts/blob/master/hosters/hetzner-dedicated/hetzner-dedicated-wipe-and-install-nixos.sh here].
Line 13: Line 12:
== Network configuration ==
== Network configuration ==


From Hetzner's [https://accounts.hetzner.com/login web interface], one can obtain both ipv4/ipv6 addresses and gateways.
Hetzner Online offers both IPv4 (usually in a shared /26 or /27 subnet) and IPv6 (/64 subnet) connectivity to each machine. The assigned addresses can be looked up on the [https://robot.hetzner.com/server Hetzner Robot] on the IPs tab of a machine. The public IPv4 address of the server can automatically be obtained via DHCP. For IPv6 you have to statically configure both address and gateway.
Hetzner does announce ipv6 addresses servers, so you need to assign those statically.
In this example we use networkd to configure the interface. The same configuration can be used for both
the kexec installation image and the final server configuration.


<syntaxHighlight lang=nix>
<syntaxhighlight lang="nix">
{ ... }: {
{
  # This make sure that our interface is named `eth0`.
  # This should be ok as long as you don't have multiple physical network cards
  # For multiple cards one could add a netdev unit to rename the interface based on the mac address
  networking.usePredictableInterfaceNames = false;
   systemd.network = {
   systemd.network = {
     enable = true;
     enable = true;
     networks."eth0".extraConfig = ''
     networks.default = {
       [Match]
       name = "enp1s0"; # The name of the interface
       Name = eth0
       DHCP = "ipv4";
       [Network]
       addresses = [  
      # Add your own assigned ipv6 subnet here here!
        {
      Address = 2a01:4f9:ffff::1/64
          # Replace the address with the one assigned to your machine
       Gateway = fe80::1
          Address = "2a01:4f8:AAAA:BBBB::1/64";
       # optionally you can do the same for ipv4 and disable DHCP (networking.dhcpcd.enable = false;)
        }
      # Address =  144.x.x.x/26
       ];
      # Gateway = 144.x.x.1
      gateway = [ "fe80::1" ];
     '';
       linkConfig.RequiredForOnline = "routable";
     };
   };
   };
}
}
</syntaxHighlight>
</syntaxhighlight>


Another possibility is to use <code>networking.interfaces</code>:
=== Static IPv4 configuration ===
<syntaxHighlight lang=nix>
Since the IPv4 network configuration is known, it can also be configured statically, preventing reliance on the DHCP service. The gateway and subnet information is visible when hovering the IPv4 address. The subnet size is usually a /26 (<code>255.255.255.224</code>) or a /27 (<code>255.255.255.192</code>).<syntaxhighlight lang="nix">
let
{
   external-mac = "00:11:22:33:44:55";
   systemd.network = {
  ext-if = "et0";
    enable = true;
  external-ip = "144.x.x.x";
    networks."30-wan" = {
  external-gw = "144.x.x.255";
      name = "enp1s0"; # The predictable name of the network interface
  external-ip6 = "2a01:XXXX:XXXX::1";
      DHCP = "no";
  external-gw6 = "fe80::1";
      addresses = [
  external-netmask = 27;
        # Replace the addresses with the ones assigned to your machine
  external-netmask6 = 64;
        {
in {
          Address = "A.B.C.D/26";
  # rename the external interface based on the MAC of the interface
        }
  services.udev.extraRules = ''SUBSYSTEM=="net", ATTR{address}=="${external-mac}", NAME="${ext-if}"'';
        {
  networking = {
          Address = "2a01:4f8:AAAA:BBBB::1/64";
    interfaces."${ext-if}" = {
         }
      ipv4.addresses = [{
       ];
        address = external-ip;
       gateway = [
         prefixLength = external-netmask;
         # Replace the gateway address with the one in your subnet
       }];
        "A.B.C.E"
       ipv6.addresses = [{
         "fe80::1"
         address = external-ip6;
       ];
         prefixLength = external-netmask6;
       linkConfig.RequiredForOnline = "routable";
       }];
    };
    defaultGateway6 = {
       address = external-gw6;
      interface = ext-if;
     };
     };
    defaultGateway = external-gw;
   };
   };
}
}
</syntaxHighlight>
</syntaxhighlight>


== Bootstrap from the Rescue System ==
== Bootstrap from the Rescue System ==


Here are some quick notes on how to bootstrap. Inspiration comes from https://github.com/ofborg/infrastructure/commit/0712a5cf871b7a6d2fbbd2df539d3cd90ab8fa1f
Here are some quick notes on how to bootstrap.
 
The nixos-install-scripts repo may also be a valuable resource:
 
https://github.com/nix-community/nixos-install-scripts/tree/master/hosters/hetzner-dedicated
 
Otherwise, inspiration for the kexec approach below comes from https://github.com/ofborg/infrastructure/commit/0712a5cf871b7a6d2fbbd2df539d3cd90ab8fa1f
and https://github.com/andir/infra/tree/master/bootstrap
and https://github.com/andir/infra/tree/master/bootstrap


The main principle is that we will go from: Rescue system, kexec into a NixOS system, finally install the system.
The main principle is that we will go from: Rescue system, install Nix, kexec into a NixOS system, finally install the system.
 
First, reboot the machine in Rescue mode. Note that just enabling Rescue mode from the dashboard doesn't immediately reboot so make sure to power cycle the server. The Rescue mode runs from a RAM disk, so make also sure that you have enough RAM. Temporarily rescaling to 32 GiB of RAM (the RAM disk will be half of the available RAM) during the bootstrapping process helps. Make sure to select your SSH public key. SSH into the machine:


First, reboot the machine in Rescue mode. Make sure to select your SSH public key. SSH into the machine:
You can skip the entire next part by using https://github.com/nix-community/nixos-images#kexec-tarballs


<syntaxHighlight>
<syntaxHighlight lang=bash>
# Create a user, because the nix installer disallows running as root
# The installer needs sudo
adduser --add_extra_groups=sudo foo
apt install -y sudo
install -d -m700 -o foo /nix
su - foo


# Pre-configure Nix
# Let root run the nix installer
mkdir -p /etc/nix
echo "build-users-group =" > /etc/nix/nix.conf
echo "build-users-group =" > /etc/nix/nix.conf


# Install Nix
# Install Nix in single-user mode
sh <(curl -L https://nixos.org/nix/install) --daemon
curl -L https://nixos.org/nix/install | sh
 
. $HOME/.nix-profile/etc/profile.d/nix.sh
# As mentioned in the installer, you'll need to log out
# (back to root) and start a new session
exit
su - foo
 
# Optional: Enable flakes
echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf


# Install nixos-generators
# Install nixos-generators
# This might take a while, so the verbose flag `-v` is included to monitor progress
# This might take a while, so the verbose flag `-v` is included to monitor progress
nix-env -f https://github.com/nix-community/nixos-generators/archive/master.tar.gz -i -v
nix-env -f https://github.com/nix-community/nixos-generators/archive/1.7.0.tar.gz -i -v
# Or with flakes:
nix profile install github:nix-community/nixos-generators


# Create a initial config, just to kexec into
# Create a initial config, just to kexec into
cat <<EOF > /home/foo/config.nix
cat <<EOF > /root/config.nix
{
{
   services.openssh.enable = true;
   services.openssh.enable = true;
   users.users.root.openssh.authorizedKeys.keys = [
   users.users.root.openssh.authorizedKeys.keys = [
     # Replace with your public key
     # Replace with your public key
     "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGB1Pog97SWdV2UEA40V+3bML+lSZXEd48zCRlS/eGbY3rsXfgUXb5FIBulN9cET9g0OOAKeCZBR1Y2xXofiHDYkhk298rHDuir6cINuoMGUO7VsygUfKguBy63QMPHYnJBE1h+6sQGu/3X9G2o/0Ys2J+lZv4+N7Hqolhbg/Cu6/LUCsJM/udqTVwJGEqszDWPtuuTAIS6utB1QdL9EZT5WBb1nsNyHnIlCnoDKZvrrO9kM0FGKhjJG2skd3+NqmLhYIDhRhZvRnL9c8U8uozjbtj/N8L/2VCRzgzKmvu0Y1cZMWeAAdyqG6LoyE7xGO+SF4Vz1x6JjS9VxnZipIB zimbatm@nixos"
     "ssh-rsa AAAA..."
   ];
   ];
}
}
Line 122: Line 107:


# Generate the kexec script
# Generate the kexec script
nixos-generate -o /home/foo/result  -f kexec-bundle -c /home/foo/config.nix
nixos-generate -o /root/result  -f kexec-bundle -c /root/config.nix
 
# Switch back to root
exit


# Switch to the new system
# Switch to the new system
/home/foo/result
/root/result
</syntaxHighlight>
</syntaxHighlight>


At this point the shell should stop responding. Kill the shell and ssh back into the machine. The server public key will have changed.
At this point, the shell should stop responding. Kill the shell and ssh back into the machine. The server public key will have changed.


<syntaxHighlight lang=bash>
<syntaxHighlight lang=bash>
Line 143: Line 125:


# In this particular machine we have two NVMe disks
# In this particular machine we have two NVMe disks
# If your machine has > 2TB drives, open a ticket and ask for UEFI boot, it will save you a lot of hassle
format /dev/nvme0n1
format /dev/nvme0n1
format /dev/nvme1n1
format /dev/nvme1n1
Line 162: Line 145:


At this point, edit the /mnt/etc/nixos/configuration.nix and tune as needed. I just added the following lines:
At this point, edit the /mnt/etc/nixos/configuration.nix and tune as needed. I just added the following lines:
<syntaxHighlight>
<syntaxHighlight lang=nix>
boot.loader.grub.device = "/dev/nvme0n1";
boot.loader.grub.device = "/dev/nvme0n1";
services.openssh.enable = true;
services.openssh.enable = true;
Line 174: Line 157:
Voila! (after 1000 steps)
Voila! (after 1000 steps)


== Bootstrap with cloud-init ==
[[Category:Cookbook]]
 
[[Category:Server]]
Create a server with the Debian 11 image (using the web interface or the hcloud cli tool) and provide the following "user data" for cloud-init:
 
<syntaxHighlight lang=yaml>
#cloud-config
runcmd:
- set -e
- /root/install-nix
- /root/.nix-profile/bin/nix --extra-experimental-features "nix-command flakes" build -L github:BBBSnowball/nixcfg#nixosConfigurations.hetzner-temp.config.system.build.toplevel --out-link /root/system && /root/.nix-profile/bin/nix-env -p /nix/var/nix/profiles/system --set /root/system
# NixOS manual suggests this. Not really needed here because we do a multi-user install.
- chown -R 0.0 /nix
- touch /etc/NIXOS
- echo "etc/nixos" >/etc/NIXOS_LUSTRATE
- echo "root/.ssh/authorized_keys" >>/etc/NIXOS_LUSTRATE
- echo "etc/ssh-shared-secret" >>/etc/NIXOS_LUSTRATE
- echo "var/lib/systemd/random-seed" >>/etc/NIXOS_LUSTRATE
- rm -rf /boot/efi/*
- mkdir /boot/efi/{EFI/systemd,EFI/BOOT} -p
- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
- reboot
write_files:
- path: /root/install-nix
  permissions: '0755'
  content: |
    #!/bin/bash
    set -xe
    apt update && apt install -y gnupg2 sudo
    # Nix installer refuses to run as root...
    useradd --create-home -G sudo user
    sudo -u user  curl --fail -o ~user/install-nix-2.5.1 https://releases.nixos.org/nix/nix-2.5.1/install
    sudo -u user  curl --fail -o ~user/install-nix-2.5.1.asc https://releases.nixos.org/nix/nix-2.5.1/install.asc
    cp ~user/install-nix-2.5.1{,.asc} ~/
    sha256sum -c <<<'e265dfd8e80223633a9726009b42c534ac3d5f2b6da5ad6432ca1f6ea88206d0  /root/install-nix-2.5.1'
    echo 'user ALL=(ALL:ALL) NOPASSWD: ALL' >>/etc/sudoers
    yes | sudo -u user  sh ~user/install-nix-2.5.1 --daemon --no-channel-add
</syntaxHighlight>
 
Change the third command to use your own flake (or fetch your config in some other way). The authorized_keys file is not removed so you can login with the SSH keys that you choose when creating the server. If it doesn't reboot to NixOS, check the syntax of your user data (and keep the first line!) and have a look at the log: journalctl --unit cloud-final


Your config has to replace the Debian bootloader. There is an EFI partition but the server uses legacy boot so make sure to replace the legacy bootloader, i.e. configure boot.loader.* to not use EFI.
[[Category:Deployment]]
 
I use [https://github.com/BBBSnowball/groot/blob/5ca69fbb339404c9104467f0eadf234ee960aa23/hcloud-create.sh this helper script] to create the server and retrieve its SSH host key.
 
You can remove /old-root after booting into NixOS, which will free up about 1 GB. There is some interesting metadata in /old-root/var/lib/cloud/instances (e.g. the IP address) but you can also fetch similar information from [https://docs.hetzner.cloud/#server-metadata Hetzner's API].
 
[[Category:Server]]

Latest revision as of 09:13, 26 October 2024

Note: This article is about installing NixOS on Hetzner Online, which provides dedicated bare-metal servers. It is not to be confused with Hetzner Cloud, which provides VPS cloud servers.

Installation

There are three ways at the time to install NixOS on Hetzner dedicated:

  1. From Hetzner's rescue image, one can boot into the nixos installer using a custom kexec image that is configured with the fixed IPv6 provided by Hetzner and also contain your ssh key. Tip: The kexec tarball as generated by nixos-generators can remain put into the /boot partition for future use.
  2. Hetzner also provides an interface to upload your own ISO-images. Also, here you may want to build your own iso-image, which has openssh with ssh keys due the lack of a remote console.
  3. An easier method to install NixOS on Hetzner, is to use the existing integration into NixOps.
  4. An example to install NixOS in the Hetzner rescue mode, including full RAID partitioning, is available here.

Network configuration

Hetzner Online offers both IPv4 (usually in a shared /26 or /27 subnet) and IPv6 (/64 subnet) connectivity to each machine. The assigned addresses can be looked up on the Hetzner Robot on the IPs tab of a machine. The public IPv4 address of the server can automatically be obtained via DHCP. For IPv6 you have to statically configure both address and gateway.

{
  systemd.network = {
    enable = true;
    networks.default = {
      name = "enp1s0"; # The name of the interface
      DHCP = "ipv4";
      addresses = [ 
        {
          # Replace the address with the one assigned to your machine
          Address = "2a01:4f8:AAAA:BBBB::1/64"; 
        }
      ];
      gateway = [ "fe80::1" ];
      linkConfig.RequiredForOnline = "routable";
    };
  };
}

Static IPv4 configuration

Since the IPv4 network configuration is known, it can also be configured statically, preventing reliance on the DHCP service. The gateway and subnet information is visible when hovering the IPv4 address. The subnet size is usually a /26 (255.255.255.224) or a /27 (255.255.255.192).

{
  systemd.network = {
    enable = true;
    networks."30-wan" = {
      name = "enp1s0"; # The predictable name of the network interface
      DHCP = "no";
      addresses = [
        # Replace the addresses with the ones assigned to your machine
        {
          Address = "A.B.C.D/26";
        }
        {
          Address = "2a01:4f8:AAAA:BBBB::1/64";
        }
      ];
      gateway = [
        # Replace the gateway address with the one in your subnet
        "A.B.C.E"
        "fe80::1"
      ];
      linkConfig.RequiredForOnline = "routable";
    };
  };
}

Bootstrap from the Rescue System

Here are some quick notes on how to bootstrap.

The nixos-install-scripts repo may also be a valuable resource:

https://github.com/nix-community/nixos-install-scripts/tree/master/hosters/hetzner-dedicated

Otherwise, inspiration for the kexec approach below comes from https://github.com/ofborg/infrastructure/commit/0712a5cf871b7a6d2fbbd2df539d3cd90ab8fa1f and https://github.com/andir/infra/tree/master/bootstrap

The main principle is that we will go from: Rescue system, install Nix, kexec into a NixOS system, finally install the system.

First, reboot the machine in Rescue mode. Note that just enabling Rescue mode from the dashboard doesn't immediately reboot so make sure to power cycle the server. The Rescue mode runs from a RAM disk, so make also sure that you have enough RAM. Temporarily rescaling to 32 GiB of RAM (the RAM disk will be half of the available RAM) during the bootstrapping process helps. Make sure to select your SSH public key. SSH into the machine:

You can skip the entire next part by using https://github.com/nix-community/nixos-images#kexec-tarballs

# The installer needs sudo
apt install -y sudo

# Let root run the nix installer
mkdir -p /etc/nix
echo "build-users-group =" > /etc/nix/nix.conf

# Install Nix in single-user mode
curl -L https://nixos.org/nix/install | sh
. $HOME/.nix-profile/etc/profile.d/nix.sh

# Install nixos-generators
# This might take a while, so the verbose flag `-v` is included to monitor progress
nix-env -f https://github.com/nix-community/nixos-generators/archive/1.7.0.tar.gz -i -v

# Create a initial config, just to kexec into
cat <<EOF > /root/config.nix
{
  services.openssh.enable = true;
  users.users.root.openssh.authorizedKeys.keys = [
    # Replace with your public key
    "ssh-rsa AAAA..."
  ];
}
EOF

# Generate the kexec script
nixos-generate -o /root/result  -f kexec-bundle -c /root/config.nix

# Switch to the new system
/root/result

At this point, the shell should stop responding. Kill the shell and ssh back into the machine. The server public key will have changed.

format() {
  parted -s "$1" -- mklabel msdos
  parted -s "$1" -- mkpart primary 1MiB 512MiB
  parted -s "$1" -- set 1 boot on
  parted -s "$1" -- mkpart primary 512MiB 100%
  parted -s "$1" -- print
}

# In this particular machine we have two NVMe disks
# If your machine has > 2TB drives, open a ticket and ask for UEFI boot, it will save you a lot of hassle 
format /dev/nvme0n1
format /dev/nvme1n1

# Here we create a single btrfs volume using both disks. Change as needed

# TODO: Use boot.loader.grub.mirroredBoots
mkfs.ext2 /dev/nvme0n1p1
mkfs.btrfs -d raid0 -m raid1 -L nixos /dev/nvme0n1p2 /dev/nvme1n1p2

# Mount the disks
mount /dev/disk/by-label/nixos /mnt
mkdir /mnt/boot
mount /dev/nvme0n1p1 /mnt/boot

# Generate the NixOS configuration.
nixos-generate-config --root /mnt

At this point, edit the /mnt/etc/nixos/configuration.nix and tune as needed. I just added the following lines:

boot.loader.grub.device = "/dev/nvme0n1";
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keys = [
  "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGB1Pog97SWdV2UEA40V+3bML+lSZXEd48zCRlS/eGbY3rsXfgUXb5FIBulN9cET9g0OOAKeCZBR1Y2xXofiHDYkhk298rHDuir6cINuoMGUO7VsygUfKguBy63QMPHYnJBE1h+6sQGu/3X9G2o/0Ys2J+lZv4+N7Hqolhbg/Cu6/LUCsJM/udqTVwJGEqszDWPtuuTAIS6utB1QdL9EZT5WBb1nsNyHnIlCnoDKZvrrO9kM0FGKhjJG2skd3+NqmLhYIDhRhZvRnL9c8U8uozjbtj/N8L/2VCRzgzKmvu0Y1cZMWeAAdyqG6LoyE7xGO+SF4Vz1x6JjS9VxnZipIB zimbatm@nixos"
];

Finally run nixos-install, and then reboot the machine.

Voila! (after 1000 steps)