10 KiB
Installing NixOS on a Proxmox VM using nixos-anywhere
- TL;DR: Quick Install Guide
- Table of Contents
- Prerequisites on the Target VM
- Installation Process
- Post-Installation: Secrets Management
- Optional NixOS Modules
- Notes and Configuration Details
- TODOs
- Inspiration
Abstract
This guide documents the process for a minimal installation of NixOS on a Proxmox virtual machine. It leverages the nixos-anywhere tool for remote deployment and disko for declarative disk partitioning. It also covers the essential post-installation steps for integrating the new host with sops-nix for secrets management and lists available custom modules.
TL;DR: Quick Install Guide
-
Prepare VM: Boot the target Proxmox VM from a NixOS ISO and set a root password:
passwd -
Deploy NixOS: From your workstation, run
nixos-anywhere, pointing to your flake and the VM's IP address.nix run github:nix-community/nixos-anywhere -- \ --flake .#susano-minimal \ --target-host root@<vm-ip-address> -
Get Host Key: After installation, SSH into the new VM and get its host AGE key.
ssh root@<vm-ip-address> nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' -
Update Secrets: On your workstation, add the new AGE key to
.sops.yamland re-encrypt secrets.sops updatekeys secrets/secrets.yaml
Table of Contents TOC
Prerequisites on the Target VM
Before attempting to install NixOS with nixos-anywhere, you must first perform a critical setup step on the target Proxmox VM.
The minimal NixOS installation ISO does not have a default password for the root user. The nixos-anywhere command requires SSH access, which necessitates a password.
- Boot the Proxmox VM using the minimal NixOS installation ISO.
- Open a terminal on the VM's console.
-
Set a password for the
rootuser by running the following command:passwdYou will be prompted to enter and confirm a new password.
Installation Process
Deploying NixOS
With the root password set on the target VM, you can now run nixos-anywhere from your local machine to deploy your NixOS configuration.
The following command uses nix run to execute nixos-anywhere, pointing it to a specific flake output (.#susano-minimal) and the IP address of the target VM.
nix run github:nix-community/nixos-anywhere -- \
--flake .#susano-minimal \
--target-host root@192.168.1.85
Post-Installation: Secrets Management
Step 1: Generating the Host AGE Key
After the initial installation is complete, you will need its host AGE key to manage secrets with tools like sops-nix. This key is derived from the host's SSH key.
-
SSH into the newly installed NixOS machine.
ssh root@192.168.1.85 -
Run the following command. It temporarily installs the
ssh-to-ageutility and pipes the public SSH host key to it, converting it to an AGE public key.nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age' - The command will output the new AGE public key. Copy this key for the next step.
Step 2: Updating SOPS and Re-encrypting Secrets
The new AGE key must be added to your .sops.yaml configuration file. This allows sops to encrypt secrets in a way that the new host (susano) can decrypt them.
- Open the
.sops.yamlfile in the root of your Nix flake. -
Replace the old key for the
susanohost with the new key you generated.keys: - &primary age19wvqtn4ju6k4vs8fxr34unl6xx4cv04jw0lx9ps20xlde927zfssgl4qke - &susano age1vkfq9gpqfpyq3s9e79e6vw8kv9485tzna4fm3dy6p0u9uz9feu8qr9sgcf # <--- REPLACE THIS WITH THE NEW KEY creation_rules: - path_regex: secrets/secrets.yaml$ key_groups: - age: - *primary - *susano -
After saving the updated
.sops.yamlfile, run theupdatekeyscommand. This re-encrypts the specified secrets file with the new set of keys defined in.sots.yaml. For more information, see the official documentation.sops updatekeys secrets/secrets.yamlYour secrets are now encrypted for both the primary key and the new host's key.
Optional NixOS Modules
Reverse Proxies
The following modules can be enabled to provide a reverse proxy.
Note: Previously, all reverse proxy modules were considered non-functional. Recent troubleshooting has provided a fix for Traefik, but the other modules may still have issues.
NGINX
The initial switch to this configuration may be slow as it waits for ACME to issue SSL certificates.
dov = {
# Reverse Proxy
reverse-proxy = {
nginx.enable = true;
};
};
Traefik
dov = {
# Reverse Proxy
reverse-proxy = {
traefik.enable = true;
};
};
Troubleshooting Traefik ACME with DuckDNS
- Context: Issues getting an ACME certificate from DuckDNS with Traefik.
- Roadblock: The process was failing, but eventually succeeded.
-
Possible Solutions:
- Setting
disablePropagationCheck = true;for the DNS challenge. - Extending the
delayfor the DNS challenge.
- Setting
- Notes: It's unclear which specific option resolved the issue, but one of them, or a combination, allowed the certificate to be obtained. The first time Traefik tries to get a certificate it might fail, and a restart of the service is needed. After some time, the Let's Encrypt certificate will be received.
Caddy
dov = {
# Reverse Proxy
reverse-proxy = {
caddy.enable = true;
};
};
File Servers
copyparty
Provides a web-based file manager. For more information, see the official documentation.
dov = {
file-server.copyparty.enable = true;
};
Dependency: For copyparty to function correctly in this configuration, it requires a Samba share to be mounted to the path /MEDIA. Therefore, the samba module must also be enabled:
dov = {
samba.enable = true;
};
Search Engines
searxng
A privacy-respecting metasearch engine. For more information, see the NixOS Wiki page.
dov = {
searxng.enable = true;
};
Notes and Configuration Details
Disko Configuration for Proxmox (MBR Boot)
A critical requirement for ensuring a NixOS VM can boot correctly in Proxmox is the disk partition scheme. Proxmox expects a Master Boot Record (MBR) compatible setup.
When using disko for declarative disk management, you must configure it to create a GPT partition table that includes a special 1M BIOS boot partition (type EF02). This partition is specifically used by GRUB for MBR compatibility.
Here is an example snippet for the disko configuration:
{
disko.devices = {
disk = {
main = {
device = "/dev/sda";
type = "disk";
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02"; # for grub MBR
};
# ... your other partitions like root, swap, etc.
};
};
};
};
};
}
For a complete example, you can refer to the official disko repository: gpt-bios-compat.nix.
Generating Hardware Configuration
The nixos-anywhere tool can automatically generate a hardware configuration file from the target machine. This is useful for capturing machine-specific settings.
To do this, include the --generate-hardware-config flag in your command. The following example shows how to generate the file and save it as ./hardware-configuration.nix in your local flake directory.
nix run github:nix-community/nixos-anywhere -- \
--flake .#your-flake-output \
--target-host root@192.168.1.85 \
--generate-hardware-config ./hardware-configuration.nix
TODOs
- Investigate and fix remaining issues with reverse proxy modules (NGINX, Caddy).
- Troubleshoot and fix an issue that occurs when reloading the NixOS configuration remotely, which breaks the SSH pipe and requires entering the root password three times.
- Investigate and resolve the issue where updating a user's password declaratively using a secret managed by
sopsfailed after the initial installation. - Refactor the
diskoconfiguration to make the disk device name (e.g.,/dev/sda) a variable. This will avoid hardcoding the value and make the configuration more portable. - Create a custom ISO image to streamline the installation process, potentially pre-configuring items like the root user to avoid manual console steps.
- Develop an automated installation script to handle the post-install process, such as fetching the AGE key and updating sops, based on this guide.
Inspiration
The configuration and structure of this setup were inspired by the following repository: