Install NixOS on Hetzner Cloud: Difference between revisions

update example to 24.11
Sandro (talk | contribs)
Fix nixos-anywhere install
 
(8 intermediate revisions by 5 users not shown)
Line 85: Line 85:
     nix.settings = {
     nix.settings = {
       experimental-features = "nix-command flakes";
       experimental-features = "nix-command flakes";
      auto-optimise-store = true;
     };
     };
      
      
Line 91: Line 90:
       pkgs.vim
       pkgs.vim
       pkgs.git
       pkgs.git
      pkgs.zip
      pkgs.unzip
      pkgs.wget
     ];
     ];
      
      
Line 110: Line 106:
     ];
     ];
      
      
    documentation.nixos.enable = false;
     time.timeZone = "Europe/London";
     time.timeZone = "Europe/London";
     i18n.defaultLocale = "en_GB.UTF-8";
     i18n.defaultLocale = "en_US.UTF-8";
     console.keyMap = "us";
     console.keyMap = "us";
    nix.settings.trusted-users = [ "@wheel" ];
      
      
     boot.loader.grub.enable = true;
     boot.loader.grub.enable = true;
Line 141: Line 135:
       };
       };
     };
     };
   
     networking.firewall.allowedTCPPorts = [ 22 ];
     networking.firewall.allowedTCPPorts = [ 22 ];
      
      
    # This value determines the NixOS release from which the default
     system.stateVersion = "24.11";
    # settings for stateful data, like file locations and database versions
    # on your system were taken. It‘s perfectly fine and recommended to leave
    # this value at the release version of the first install of this system.
    # Before changing this value read the documentation for this option
    # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
     system.stateVersion = "24.11"; # Did you read the comment?
   }
   }




To access the system, you will need to ensure that port 22 on the VM is available via the Hetzner firewall.
To access the VM, you will need to ensure that port 22 on the VM is opened via the Hetzner firewall if that is configured.


=== nixos-anywhere ===
=== nixos-anywhere ===
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:
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.
# Create a temp folder for future use. Run:<syntaxhighlight lang="shell">
#First upload your SSH key via the Hetzner Web UI
mkdir /tmp/my-first-flake
#Then click yourself a VM. For the OS choose Ubuntu but anything should work. This guide was tested with x86_64-linux but aarch64 should work with the note from below.
</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.
#Enter docker container. Run:<syntaxhighlight lang="shell">
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
</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 an SSH key:<syntaxhighlight lang="sh">
ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ''
</syntaxhighlight>
#Upload your public key to Hetzner:<syntaxhighlight lang="shell">
hcloud ssh-key create --name 'My public key' --public-key-from-file /root/.ssh/id_ed25519.pub
</syntaxhighlight>
#Create a VM on Hetzner. Run:<syntaxhighlight lang="shell">
hcloud server create --name my-hetzner-vm --type cpx21 --image ubuntu-24.04 --location fsn1 --ssh-key 'My public key'
</syntaxhighlight>Write down the IP. '''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].
# On your host computer, create a folder. Run:<syntaxhighlight lang="shell">
mkdir -p /tmp/my-first-flake/my-vms/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">
#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-vms/my-hetzner-vm/hardware-configuration.nix
# /tmp/my-hetzner-vm/hardware-configuration.nix


{ config, lib, pkgs, modulesPath, ... }:
{ config, lib, pkgs, modulesPath, ... }:
Line 192: Line 158:
   ];
   ];


  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;
   networking.useDHCP = lib.mkDefault true;
   nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
   nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
}
</syntaxhighlight><syntaxhighlight lang="nix">
</syntaxhighlight><syntaxhighlight lang="nix">
# /tmp/my-first-flake/my-vms/my-hetzner-vm/disko-config.nix
# /tmp/my-hetzner-vm/disko-config.nix


{
{
Line 241: Line 202:
}
}
</syntaxhighlight><syntaxhighlight lang="nix">
</syntaxhighlight><syntaxhighlight lang="nix">
# /tmp/my-first-flake/my-vms/my-hetzner-vm/configuration.nix
# /tmp/my-hetzner-vm/configuration.nix


{ config, lib, pkgs, ... }:
{ config, lib, pkgs, ... }:
Line 265: Line 226:
     enable = true;
     enable = true;
     defaultEditor = true;
     defaultEditor = true;
    configure = {
      customRC = ''
        colorscheme habamax
      '';
      packages.packages = {
        start = [
          pkgs.vimPlugins.nerdtree
        ];
      };
    };
   };
   };


   system.stateVersion = "24.05";
   system.stateVersion = "24.11";
}         
}         
</syntaxhighlight>'''Note''': the value of <code>initialHashedPassword</code> above was obtained using <code>mkpasswd</code> command in Linux, and corresponds to <code>Password.123</code> string used as password.<syntaxhighlight lang="nix">
</syntaxhighlight>'''Note''': the value of <code>initialHashedPassword</code> above 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
# /tmp/my-hetzner-vm/flake.nix


{
{
   inputs = {
   inputs = {
     nixpkgs = {
     nixpkgs = {
       url = "github:NixOS/nixpkgs/nixos-24.05";
       url = "github:NixOS/nixpkgs/nixos-24.11";
     };
     };


Line 305: Line 255:


         modules = [
         modules = [
           ./my-vms/my-hetzner-vm/configuration.nix
           ./configuration.nix
           inputs.disko.nixosModules.disko
           inputs.disko.nixosModules.disko
         ];
         ];
Line 312: Line 262:
   };
   };
}
}
</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.
</syntaxhighlight>
#Build NixOS from flake. Run:<syntaxhighlight lang="shell">
#To build NixOS from the flake run:<syntaxhighlight lang="shell">
nix run --extra-experimental-features 'nix-command flakes' github:nix-community/nixos-anywhere/1.3.0 -- --flake /tmp/my-first-flake#my-hetzner-vm root@0.0.0.0 --build-on-remote
nix run --extra-experimental-features 'nix-command flakes' github:nix-community/nixos-anywhere -- --flake /tmp/my-hetzner-vm#my-hetzner-vm --target-host root@0.0.0.0 --build-on-remote
</syntaxhighlight>'''Note''': replace <code>0.0.0.0</code> with an IP address obtained during an earlier step.
</syntaxhighlight>'''Note''': replace <code>0.0.0.0</code> with an IP address obtained during an earlier step.
The NixOS on Hetzner is installed!
The NixOS on Hetzner is installed!
Let's do a few more steps to customize the installation.
#Copy flake files onto the server. Run:<syntaxhighlight lang="shell">
scp -r /tmp/my-first-flake eugene@0.0.0.0:~/
</syntaxhighlight>'''Note''': again, replace <code>0.0.0.0</code> with an IP address obtained via <code>hcloud server ip my-hetzner-vm</code>.
#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 ===
Line 349: Line 274:
references:
references:


* [[Disko]]
* [https://github.com/feelssexy/hetzner-auto-nixos/blob/main/hardware-configuration.nix sample regular hardware config]
* [https://github.com/feelssexy/hetzner-auto-nixos/blob/main/hardware-configuration.nix sample regular hardware config]
* [https://github.com/LGUG2Z/nixos-hetzner-cloud-starter/blob/master/disk-config.nix sample config using disko]
* [https://github.com/LGUG2Z/nixos-hetzner-cloud-starter/blob/master/disk-config.nix sample config using disko]
Line 367: Line 293:
* Run following script. Replace <code>NIX_CHANNEL</code> variable with the version string you wish to install.
* Run following script. Replace <code>NIX_CHANNEL</code> variable with the version string you wish to install.
<syntaxHighlight lang=bash>
<syntaxHighlight lang=bash>
curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | NIX_CHANNEL=nixos-22.11 bash -x
curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | NIX_CHANNEL=nixos-24.11 bash -x
</syntaxHighlight>
</syntaxHighlight>
* Reboot into NixOS
* Reboot into NixOS
Line 382: Line 308:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
  systemd.network.enable = true;
systemd.network.enable = true;
  systemd.network.networks."30-wan" = {
systemd.network.networks."30-wan" = {
    matchConfig.Name = "ens3"; # either ens3 or enp1s0, check 'ip addr'
  matchConfig.Name = "ens3"; # either ens3 or enp1s0, check 'ip addr'
    networkConfig.DHCP = "ipv4";
  networkConfig.DHCP = "ipv4";
    address = [
  address = [
      # replace this subnet with the one assigned to your instance
    # replace this subnet with the one assigned to your instance
      "2a01:4f8:aaaa:bbbb::1/64"
    "2a01:4f8:aaaa:bbbb::1/64"
    ];
  ];
    routes = [
  routes = [
      { Gateway = "fe80::1"; }
    { Gateway = "fe80::1"; }
    ];
  ];
 
};
  };
</syntaxhighlight>
</syntaxhighlight>


Line 401: Line 326:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
  systemd.network.networks."30-wan" = {
systemd.network.networks."30-wan" = {
    matchConfig.Name = "ens3"; # either ens3 (amd64) or enp1s0 (arm64)
  matchConfig.Name = "ens3"; # either ens3 (amd64) or enp1s0 (arm64)
    networkConfig.DHCP = "no";
  networkConfig.DHCP = "no";
    address = [
  address = [
      # replace this address with the one assigned to your instance
    # replace this address with the one assigned to your instance
      "A.B.C.D/32"
    "A.B.C.D/32"
      # replace this subnet with the one assigned to your instance
    # replace this subnet with the one assigned to your instance
      "2a01:4f8:AAAA:BBBB::1/64"
    "2a01:4f8:AAAA:BBBB::1/64"
    ];
  ];
    routes = [ {
  routes = [
      routeConfig = { Gateway = "172.31.1.1"; GatewayOnLink = true; }; }
    { Gateway = "172.31.1.1"; GatewayOnLink = true; }
      { routeConfig.Gateway = "fe80::1"; }
    { Gateway = "fe80::1"; }
    ];
  ];
  };
};
</syntaxhighlight>
</syntaxhighlight>