Jump to content

Install NixOS on Hetzner Cloud: Difference between revisions

m
no edit summary
(add some library references)
mNo edit summary
(One intermediate revision by the same user not shown)
Line 10: Line 10:


=== From NixOS minimal ISO ===
=== From NixOS minimal ISO ===
# Create a new instance and power it off
The tutorial assumes you already have an account on Hetzner Cloud, and no prior access to a system with NixOS or nix CLI utility installed:
# Switch to the ISO-Images tab and mount the NixOS minimal ISO
# Create a temp folder for future use. Run:<syntaxhighlight lang="shell">
# Open the remote console (<code>>_</code> button) and power the machine on
mkdir /tmp/my-first-flake
# Follow the usual [https://nixos.org/manual/nixos/stable/index.html#sec-installation-manual installation guide]
</syntaxhighlight>'''Note''': this folder will be mounted into docker container on the next step. Having the folder on the host system enables editing files using a familiar editor, available on the host system, such as VS Code or neovim.
#* Use EFI for arm64 instances, MBR for x86 instances
#Enter docker container. Run:<syntaxhighlight lang="shell">
# Unmount the ISO and reboot
docker run --rm --interactive --tty --mount type=bind,source=/tmp/my-first-flake,target=/tmp/my-first-flake alpine:3.20 ash
</syntaxhighlight>'''Note''': this is done in a container in order to reduce the "setup footprint and residue", allowing to throw away this setup environment quickly.
#Install <code>nix</code> and <code>hcloud</code> CLI utilities. Run:<syntaxhighlight lang="shell">
apk add nix hcloud openssh-client
</syntaxhighlight>
#Authenticate <code>hcloud</code> CLI utility. Run:<syntaxhighlight lang="shell">
hcloud context create my-first-context
</syntaxhighlight>
#When asked, enter value of the token in the prompt.  '''Note''': the token with "Read/Write" permissions can be obtained on a project page inside Hetzner Cloud: <nowiki>https://console.hetzner.cloud/projects/0000000/security/tokens</nowiki>
#Create a VM on Hetzner. Run:<syntaxhighlight lang="shell">
hcloud server create --name my-hetzner-vm --type cpx21 --image ubuntu-24.04 --location fsn1 --start-after-create=false
</syntaxhighlight>'''Note 1''': this tutorial uses <code>cpx21</code> VM instance type which corresponds to an x86 architecture marchine with 3 CPU cores and 4GB of RAM, and <code>fsn1</code> location which corresponds to a data center in the city of Falkenstein in Germany. A list of all instance types can be obtained by running command <code>hcloud server-type list</code>, while a list of all locations can be obtained by running <code>hcloud location list</code> command.  '''Note 2''': Hopefully, Hetzner Cloud team will support NixOS disk images soon, see [https://www.reddit.com/r/NixOS/comments/1desdbv/could_we_convince_hetzner_to_add_nixos_as_a/ Could we convince Hetzner to add Nixos as a standard image choice].
#Attach an ISO with NixOS installer. Run:<syntaxhighlight lang="shell">
hcloud server attach-iso my-hetzner-vm nixos-minimal-24.05.1503.752c634c09ce-aarch64-linux.iso
</syntaxhighlight>'''Note''': Hetzner attempts to keep the image as up-to-date as possible, hence the hash of the nixos-minimal image at the time of following this tutorial is highly likely to have changed. Run <code>hcloud iso list</code> and look up an up-to-date name of the nixos-minimal ISO image.
#Start a VM. Run:<syntaxhighlight lang="shell">
hcloud server poweron my-hetzner-vm
</syntaxhighlight>
#Open Hetzner Cloud console web page, find the <code>my-hetzner-vm</code> server, open a remote web terminal (aka "VNC over "wss://") and change password of <code>nixos</code> user to <code>my-temp-password-123</code>:[[File:Prompt_with_a_token.png|right|frameless|197x197px]]
# On your host computer, create a folder. Run:<syntaxhighlight lang="shell">
mkdir -p /tmp/my-first-flake/my-systems/my-hetzner-vm/
</syntaxhighlight>
#Using a code editor on your host computer, create 4 files. File contents, as well as the location of where to put corresponding file are indicated below:<syntaxhighlight lang="nix">
# /tmp/my-first-flake/my-systems/my-hetzner-vm/hardware-configuration.nix
 
{ config, lib, pkgs, modulesPath, ... }:
 
{
  imports = [
    (modulesPath + "/profiles/qemu-guest.nix")
  ];
 
  boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" ];
  boot.initrd.kernelModules = [ ];
  boot.kernelModules = [ ];
  boot.extraModulePackages = [ ];
  swapDevices = [ ];
  networking.useDHCP = lib.mkDefault true;
  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
</syntaxhighlight><syntaxhighlight lang="nix">
# /tmp/my-first-flake/my-systems/my-hetzner-vm/disko-config.nix
 
{
  disko.devices = {
    disk = {
      main = {
        type = "disk";
        device = "/dev/sda";
        content = {
          type = "gpt";
          partitions = {
            boot = {
              size = "1M";
              type = "EF02";
              priority = 1;
            };
            ESP = {
              size = "512M";
              type = "EF00";
              content = {
                type = "filesystem";
                format = "vfat";
                mountpoint = "/boot";
              };
            };
            root = {
              size = "100%";
              content = {
                type = "filesystem";
                format = "ext4";
                mountpoint = "/";
              };
            };
          };
        };
      };
    };
  };
}
</syntaxhighlight><syntaxhighlight lang="nix">
# /tmp/my-first-flake/my-systems/my-hetzner-vm/configuration.nix
 
{ config, lib, pkgs, ... }:
 
{
  imports =
    [
      ./hardware-configuration.nix
      ./disko-config.nix
    ];
 
  boot.loader.grub.enable = true;
 
  services.openssh.enable = true;
 
  users.users.eugene = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
    initialHashedPassword = "$y$j9T$2DyEjQxPoIjTkt8zCoWl.0$3mHxH.fqkCgu53xa0vannyu4Cue3Q7xL4CrUhMxREKC"; # Password.123
  };
 
  programs.neovim = {
    enable = true;
    defaultEditor = true;
    configure = {
      customRC = ''
        colorscheme base16-ashes
      '';
 
      packages.packages = {
        start = [
          pkgs.vimPlugins.nvim-base16
        ];
      };
    };
  };
 
  system.stateVersion = "24.05";
}       
</syntaxhighlight>'''Note''': the value of <code>initialHashedPassword</code> was obtained using <code>mkpasswd</code> command in Linux, and corresponds to <code>Password.123</code> string used as password.<syntaxhighlight lang="nix">
# /tmp/my-first-flake/flake.nix
 
{
  inputs = {
    nixpkgs = {
      url = "github:NixOS/nixpkgs/nixos-24.05";
    };
 
    disko = {
      url = "github:nix-community/disko";
      inputs = {
        nixpkgs = {
          follows = "nixpkgs";
        };
      };
    };
  };
 
  outputs = inputs@{ self, nixpkgs, ... }: {
    nixosConfigurations = {
      my-hetzner-vm = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
 
        modules = [
          ./my-systems/my-hetzner-vm/configuration.nix
          inputs.disko.nixosModules.disko
        ];
      };
    };
  };
}
</syntaxhighlight>'''Note''': all these files constitute what's known as a ''nix [[flake]]''. The flake in question is small, though not exactly a minimal one.
#Get the service IP address. Run:<syntaxhighlight lang="shell">
hcloud server ip my-hetzner-vm
</syntaxhighlight>
#Build NixOS from flake. Run:<syntaxhighlight lang="shell">
nix run --extra-experimental-features 'nix-command flakes' github:nix-community/nixos-anywhere -- --flake /tmp/my-first-flake#my-hetzner-vm nixos@0.0.0.0 --build-on-remote
</syntaxhighlight>'''Note''': replace <code>0.0.0.0</code> with an IP address obtained during the previous step.
#Detach ISO from VM. Run:<syntaxhighlight lang="shell">
hcloud server detach-iso my-hetzner-vm
 
</syntaxhighlight>
#Reboot VM. Run:<syntaxhighlight lang="shell">
hcloud server reboot my-hetzner-vm
</syntaxhighlight>
The NixOS on Hetzner is installed! Let's do a few more steps to customize the installation.
#First, "forget" existing key fingerprint for the Hetzner host. Run:<syntaxhighlight lang="shell">
ssh-keygen -f /root/.ssh/known_hosts -R 0.0.0.0
</syntaxhighlight>'''Note''': again, here and below, replace <code>0.0.0.0</code> with an IP address obtained via <code>hcloud server ip my-hetzner-vm</code>.
#Copy flake files onto the server. Run:<syntaxhighlight lang="shell">
scp -r /tmp/my-first-flake eugene@0.0.0.0:~/
</syntaxhighlight>
#Using <code>neovim</code> editor on the VM, modify <code>configuration.nix</code> to include a package containing Elixir programming language runtime for <code>eugene</code> user. Run:<syntaxhighlight lang="shell">
nvim my-first-flake/my-vms/my-hetzner-vm/configuration.nix
 
</syntaxhighlight>Edit the <code>configuration.nix</code> so that <code>users</code> block looks like this:<syntaxhighlight lang="shell">
# ~/my-first-flake/my-vms/my-hetzner-vm/configuration.nix
# ...
users.users.eugene = {
  isNormalUser = true;
  extraGroups = [ "wheel" ];
  initialHashedPassword = "$y$j9T$2DyEjQxPoIjTkt8zCoWl.0$3mHxH.fqkCgu53xa0vannyu4Cue3Q7xL4CrUhMxREKC"; # Password.123
 
  packages = [
    pkgs.beam.packages.erlang_26.elixir_1_16
  ];
};
# ...
</syntaxhighlight>
#Re-build NixOS. Run:<syntaxhighlight lang="shell">
sudo nixos-rebuild switch --flake ./my-first-flake#my-hetzner-vm
</syntaxhighlight>


=== disko ===
=== disko ===
13

edits