Provisioning remote machines via SSH#
It is possible to replace any Linux installation with a NixOS configuration on running systems using nixos-anywhere
and disko
.
Introduction#
In this tutorial, you will deploy a NixOS configuration to a running computer.
What will you learn?#
You’ll learn how to
Specify a minimal NixOS configuration with a declarative disk layout and SSH access
Check that a configuration is valid
Deploy and update a NixOS configuration on a remote machine
What do you need?#
Familiarity with the Nix language
Familiarity with the Module system
For a successful unattended installation, ensure for the target machine that:
It is a QEMU virtual machine running Linux
With
kexec
supportOn the
x86-64
oraarch64
instruction set architecture (ISA)With at least 1 GB of RAM
This may also be a live system booted from USB, such as the NixOS installer.
The IP address is configured automatically with DHCP
You can login via SSH
With public key authentication (prefered), or password
As user
root
or another user withsudo
permissions
The local machine only needs a working Nix installation.
We call the target machine target-machine
in this tutorial.
Replace it with the actual hostname or IP address.
Prepare the environment#
Create a new project directory and enter it with your shell:
mkdir remote
cd remote
Specify dependencies on nixpkgs
, disko
, and nixos-anywhere
:
$ nix-shell -p npins
[nix-shell:remote]$ npins init
[nix-shell:remote]$ npins add github nix-community disko
[nix-shell:remote]$ npins add github nix-community nixos-anywhere
Create a new file shell.nix
which provides all needed tooling using the pinned dependencies:
let
sources = import ./npins;
pkgs = import sources.nixpkgs {};
in
pkgs.mkShell {
nativeBuildInputs = with pkgs; [
npins
nixos-anywhere
nixos-rebuild
];
shellHook = ''
export NIX_PATH="nixpkgs=${sources.nixpkgs}:nixos-config=$PWD/configuration.nix"
'';
}
Now exit the temporary environment and enter the newly specified one:
[nix-shell:remote]$ exit
$ nix-shell
This shell environment is ready to use well-defined versions of Nixpkgs with nixos-anywhere
and nixos-rebuild
.
Important
Run all following commands in this environment.
Create a NixOS configuration#
The new NixOS configuration will consist of the general system configuration and a disk layout specification.
The disk layout in this example describes a single disk with a master boot record (MBR) and EFI system partition (ESP) partition, and a root file system that takes all remaining available space. It will work on both EFI and BIOS systems.
Create a new file single-disk-layout.nix
with the disk layout specification:
1{ ... }:
2
3{
4 disko.devices.disk.main = {
5 type = "disk";
6 content = {
7 type = "gpt";
8 partitions = {
9 MBR = {
10 priority = 0;
11 size = "1M";
12 type = "EF02";
13 };
14 ESP = {
15 priority = 1;
16 size = "500M";
17 type = "EF00";
18 content = {
19 type = "filesystem";
20 format = "vfat";
21 mountpoint = "/boot";
22 };
23 };
24 root = {
25 priority = 2;
26 size = "100%";
27 content = {
28 type = "filesystem";
29 format = "ext4";
30 mountpoint = "/";
31 };
32 };
33 };
34 };
35 };
36}
Create the file configuration.nix
, which imports the disk layout definition and specifies which disk to format:
Tip
If you don’t know the target disk’s device identifier, list all devices on the target machine with lsblk
:
$ ssh target-machine lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 256G 0 disk
├─sda1 8:1 0 248.5G 0 part /nix/store
│ /
└─sda2 8:2 0 7.5G 0 part [SWAP]
sr0 11:0 1 1024M 0 rom
In this example, the disk name is sda
.
The block device path is then /dev/sda
.
Note that value for later.
1{ modulesPath, ... }:
2
3let
4 diskDevice = "/dev/sda";
5 sources = import ./npins;
6in
7{
8 imports = [
9 (modulesPath + "/profiles/qemu-guest.nix")
10 (sources.disko + "/module.nix")
11 ./single-disk-layout.nix
12 ];
13
14 disko.devices.disk.main.device = diskDevice;
15
16 boot.loader.grub = {
17 devices = [ diskDevice ];
18 efiSupport = true;
19 efiInstallAsRemovable = true;
20 };
21
22 services.openssh.enable = true;
23
24 users.users.root.openssh.authorizedKeys.keys = [
25 "<your SSH key here>"
26 ];
27
28 system.stateVersion = "24.11";
29}
Important
Replace /dev/sda
with your disk block device path.
Replace the <your SSH key here>
string with the SSH public key that you want to use for future logins as user root
.
Detailed explanation
The diskDevice
variable in the let
block defines the path of the disk block device:
3let
4 diskDevice = "/dev/sda";
5 sources = import ./npins;
6in
It is used to set the target for the partitioning and formatting as described in the disk layout specification. It is also used in the boot loader configuration to make it bootable on both legacy BIOS as well as UEFI systems:
14 disko.devices.disk.main.device = diskDevice;
15
16 boot.loader.grub = {
17 devices = [ diskDevice ];
18 efiSupport = true;
19 efiInstallAsRemovable = true;
20 };
The qemu-guest.nix
module makes this system compatible for running inside a QEMU virtual machine:
8 imports = [
9 (modulesPath + "/profiles/qemu-guest.nix")
10 (sources.disko + "/module.nix")
11 ./single-disk-layout.nix
12 ];
From a disk layout specification, the disko
library generates a partitioning script and the portion of the NixOS configuration that mounts the partitions accordingly at boot time.
The first line imports the library, the second line applies the disk layout:
8 imports = [
9 (modulesPath + "/profiles/qemu-guest.nix")
10 (sources.disko + "/module.nix")
11 ./single-disk-layout.nix
12 ];
Test the disk layout#
Check that the disk layout is valid:
nix-build -E "((import <nixpkgs> {}).nixos [ ./configuration.nix ]).installTest"
This command runs the complete installation in a virtual machine by building a derivation in the installTest
attribute provided by the disko
module.
Deploy the system#
To deploy the system, build the configuration and the corresponding disk formatting script, and run nixos-anywhere
using the results:
Important
Replace target-host
with the hostname or IP address of your target machine.
toplevel=$(nixos-rebuild build --no-flake)
diskoScript=$(nix-build -E "((import <nixpkgs> {}).nixos [ ./configuration.nix ]).diskoScript")
nixos-anywhere --store-paths "$diskoScript" "$toplevel" root@target-host
Note
If you don’t have public key authentication:
Set the environment variable SSH_PASS
to your password then append the --env-password
flag to the nixos-anywhere
command.
nixos-anywhere
will now log into the target system, partition, format, and mount the disk, and install the NixOS configuration.
Then, it reboots the system.
Update the system#
To update the system, run npins
and re-deploy the configuration:
npins update nixpkgs
nixos-rebuild switch --no-flake --target-host root@target-host
nixos-anywhere
is not needed any more, unless you want to change the disk layout.