The Declarative Cure for Your NixOS Headaches
Elixos is a modular, declarative NixOS configuration system for multi-host and multi-user environments.
It leverages flakes and sops-nix for secure, reproducible, and extendable NixOS installations.
The elixos structure looks like:
elixos
βββ home
β βββ modules
β β βββ benchmarking
β β βββ common
β β βββ databases
β β βββ desktop
β β βββ development
β β βββ editors
β β βββ engineering
β β βββ graphics
β β βββ hyperland
β β βββ internet
β β βββ maintainance
β β βββ multimedia
β β βββ office
β β βββ publishing
β β βββ security
β β βββ shells
β β βββ socialmedia
β β βββ terminals
β βββ users
βββ nixos
β βββ disks
β βββ hardware
β β βββ alloy
β β βββ contabo
β β βββ singer
β β βββ tongfang
β βββ hosts
β βββ modules
β β βββ disk-layouts
β β βββ fonts
β β βββ hardware
β β βββ lib
β β βββ profiles
β β βββ secrets
β β βββ services
β βββ secrets
β β βββ surfshark
β βββ users
βββ scripts
βββ bootstrap
βββ install
βββ vpn
Use the following steps to quickly install a NixOS VM using QEMU.
Before you can encrypt secrets for your NixOS hosts using sops-nix, you must first generate a master
Age key once on your main (host) machine.
Run the following command on your host system:
mkdir -p ~/.config/sops/age
rage-keygen -o ~/.config/sops/age/keys.txtThis creates a private key in ~/.config/sops/age/keys.txt. Make sure this file is never shared and
backed up securely (e.g. to an encrypted external drive or secure password manager).
To view and copy the corresponding public key:
rage-keygen -y ~/.config/sops/age/keys.txtUse this public key whenever encrypting secrets for any target system (VM, laptop, etc.).
just vm_prerequisites # Install qemu, ovmf, rage, sops
just vm_reset # Remove old VM files
just vm_prepare # Download ISO and create VM disk
just vm_run_installer # Boot the live installer in QEMUIn the newly started Qemu window, login as root with
sudo suand set a password for the root with
passwdJust pick an easy password like nixos, as it is temporarly used anyway.
At this point, you should be able to login on the Live Installer by accessing the localhost on port 2222. In the next steps, we are going to use that.
Now your Live installer has started, open an new terminal in your local machine and run just vm_prerequisites again to load the needed application. In this new terminal, load the .env file which set the environment variables of the current setup. For instance, load:
. .env-generic-vmThis sets:
HOST=generic-vm
SSH_USER=root
SSH_PORT=2222
SSH_HOST=localhost
REPO_DIR=/root/elixos
Now you can run the bootstrap for this VM
just bootstrap-vmThis performs the following:
- Pushes your Age master key (keys.txt) to the live installer
- Pushes your repo to a bare Git repo on the VM
- Clones the repo into ~/elixos on the VM
- Installs the master key to /etc/sops/age/keys.txt
- Partitions the disk using disko
- Installs NixOS using the
generic-vmconfiguration
First, close the live installer and start up your newly create VM with:
just vm_runAfter bootstrapping the VM, the age key is available in memory, but not yet in the installed system. To fix that, after booting the VM, first load you new environment of the new virtual machine you have just installed. First you have to close the still running Live installer. Then do:
. .env.localhostThis sets
HOST=generic-vm
SSH_USER=eelco
SSH_HOST=localhost
SSH_PORT=2222
REPO_DIR=/home/eelco/elixos
Now you can run:
just post-boot-setup generic-vm eelcoThis will:
- Push the age key to the real VM
- Install the key to /etc/sops/age/keys.txt
- Push and clone the repo again
- Prepare for
nixos-rebuild switch
At this point you can start your newly created VM. Make sure to close the Live Installer first, because you cannot run two QEMU windows simultaneously. Then, start the VM with:
ssh -p 2222 eelco@localhost
If backspace does not work:
export TERM=xterm
-
Modify your configuration (e.g.
hosts/tongfang.nix,modules/, etc.) -
Push to GitHub or directly to the live VM:
git add . && git commit -m "Update" && git push
-
On the VM:
cd ~/elixos git pull sudo nixos-rebuild switch --flake .#generic-vm
The last command can be replaced with
just switch generic-vm
Try restarting your machine if you dont see an id_ed25519 file yet in your .ssh folder
Secrets like your SSH private key are stored as encrypted YAML files.
just make-secret HOST USERThis creates:
~/.ssh/ssh_key_HOST_USERnixos/secrets/HOST-USER-secrets.yaml
just encrypt-key # Convert ~/.ssh/id_ed25519 to encrypted YAML
just show-key # View decrypted secret
just decrypt-key # Restore ~/.ssh/id_ed25519 from secretsjust update # Update flake inputs
just clean # Run nix garbage collection
just fmt # Format all .nix files
just vm_reset # Reset and clean VM setup
just vm_build_generic-vm # Build the system only (no run)For manual access to the live installer:
just live_setup_ssh # Start sshd and set root password
just ssh_authorize USER # Add your SSH key to the live VM Host (QEMU & Just)
|
v
Live Installer (VM)
|
v
Installed NixOS VM
|
v
sops decrypt β ~/.ssh/id_ed25519
|
v
Working SSH login
- Add
export TERM=xtermto your VM shell profile for better terminal compatibility. - Use
just vm_run_gpufor graphical output with virtio-vga and virgl. - Create VM snapshots before major system changes.
Happy hacking with Elixos! π§¬
-
Download the [https://nixos.org/download/](nixos minimal ISO image) and create a live USB starter with it
-
Start up live NIXOS installer
Tip: use the copytoram option to prevent issues during startup (blackscreen)
-
Log in as root
sudo su
-
Look up the name of your wifi device
ip link
The name is for example
wlp2s0 -
Scan the available networks
iw dev wlp2s0 scan | grep SSIDIf you get: 'Network is down (-100), activate it with:
ip link set wlp2s0 upIf you now get
Operation not possible due to RF-kill, then checkrfkill list
Check if
0: phy0: Wireless LAN Soft blocked: yes Hard blocked: noIf it it soft blocked, unblock with
rfkill unblock all
Now, activate your device
ip link set wlp2s0 upand scan again
iw dev wlp2s0 scan | grep SSIDAlso, check if you on the right interface with:
iw dev
this should show:
Interface wlp2s0 type: managed
Now you should see your network
-
Connect to your network
wpa_passphrase "mijn-wifi-ssid" "mijn-wifi-wachtwoord" > wpa.confand then
wpa_supplicant -B -i wlp2s0 -c wpa.confand now request a ip-address using
dhcpcd wlp2s0You can ignore the notification read_config: /etc/dhcpcd.conf: No such file or directory.
Just check that you are connected with:
ip a show wlp2s0Also, check if you are connected to the internet with
ping 1.1.1.1Just start:
nmtuiAnd set you password to the network in the terminal interface.
To start your demeaon, first set your root password with
passwdThen run
sudo systemctl start sshdCheck if it is running
sudo systemctl status sshdLook up your ip address with:
ip adIt should be something like 192.168.2.3
Make sure you have set the root password. To do that, on your live installer, login as root as
sudo suand
passwdThen you should be able to login from your host machine as
ssh root@192.168.2.3If you get a warning about 'Remote Host Identification Has Changed', you have probably logged in on this IP Address earlier. Delete you key with
ssh-keygen -R "[192.168.2.3]:22"Alternatively, you can just open your ~/.ssh/known_hosts file and look for the lines containing
192.168.2.3 and remove those lines.
In case logging in is not allowed at all, you may want to change your /etc/ssh/sshd_config file. Since in nixos you cannot change settings files (even not as root), just copy the file to your home
cp /etc/ssh/sshd_config ~You may want to change the setting UsePAM Yes to UsePAM No
Then, restart your sshd deamon with this new settings file as
sudo $(which sshd) -f ~/sshd_configNote that this which sshd is needed since you need to use the full path to the sshd file.
Check if you are now listening to port 22 with
ss -tlnp | grep 22sudo useradd -r -s /urs/sbin/nologin -c "sshd user" sshdstart sshd in the background with
sudo nix run --extra-experimental-features 'nix-command flakes' github:nix-community/disko -- \
--flake .#singer --mode zap_create_mountto login: don't use password, but copy you public ssh key and add to authorized_keys. I used keep to copy my key.
Also check your firewall if it is not running
To transer your git repo, either bundle or just add your publish key to your git hub account
From now on, you can use the justfile entries to install the laptop
First, load the laptop environment
. ./.env.singerand run
just bootstrap-laptop singerThis performs all the steps. After you are done, reboot your laptop and login via ssh again and then do
just post-boot-setup singer eelcoThis installs the age key back to /etc/sops/age
Now, on the new target in de elixos repo, run
just switch singerThis should finalize your installation
In your terminal where you are remotely logged in on you laptop do:
mkdir /tmp/elixos.gitand turn it into a bare repository with
git init --bare /tmp/elixos.gitOn you host, do
ssh-copy-ip root@192.168.2.3to prevent that you have to type a password each time
In your elixos repository do
git remote add nixtmp root@192.168.2.3:/tmp/elixos.gitNo you can push your repository to the laptop with
git push nixtmp mainInstall just to be able to use is
nix-shell -p justStart with running disko to partition your hard-drive
just partition singerCheck your partitions with
findmnt /mntwhich should give you
TARGET
SOURCE FSTYPE OPTIONS
/mnt /dev/nvme0n1p2 ext4 rw,relatime
Copy the sops age key to the laptop installer. Run from your host:
scp ~/.config/sops/age/keys.txt root@192.168.2.3:~And then run in your live installer
mkdir /root/.config/sopsmv /root/keys.txt /root/.config/sopsAnd also copy them to your future hardrive
mkdir -p /mnt/etc/sops/age
cp /root/keys.txt /mnt/etc/sops/age/keys.txt
chmod 400 /mnt/etc/sops/age/keys.txthardware-configuration.nix can only be generated after the partitions have been created and mounted.
To initialize the disk layout:
sudo nix run github:nix-community/disko -- --flake .#tongfang --mode zap_create_mount
Then generate the hardware configuration:
sudo nixos-generate-config --root /mnt
After that, copy the generated hardware-configuration.nix to:
nixos/hardware/tongfang/hardware-configuration.nix
You can then proceed with nixos-rebuild or nixos-install using your flake-based configuration.
At this point you have two choises: for very larges memory size you can just do nixos-install (step) 2. However, installing nixos requires to operate with your RAM as your hard drive, therefore on normal machines, you memory will not be suffition. To fix that follow step 1.
- Normal memory
Make sure you have mounted you partitions by running
lsblkNormally, if you have just paritioned your harddrive, they should be mounted.
Clone your repository to /mnt/home
cd /mnt/home
git clone /tmp/elixos.git -b mainNow run the install script
cd /mnt/home/elixos
./scripts/bootstrap/build-on-laptop.shThis should install your nixos by using your hardrive to store all nixos packages.
- Large memmory Now you can install your laptop with
nixos-install --flake .#singerAfter installing, if you ssh keys are not present yet, you can try the following.
First, loging onto your newly installed laptop using the same prodceedure as above (start sshd deamon).
Then copy the ~/.config/sops/age/keys.txt file to the newly installed laptop.
Clone the repository to the newly installed laptop. Then do this:
mkdir -p /mnt/etc/sops/age
cp /root/keys.txt /mnt/etc/sops/age/keys.txt
chmod 400 /mnt/etc/sops/age/keys.txtAnd try to rebuild your system with
sudo nixos-rebuild switch --flake .#singerThis guide describes how to install the Elixos operating system on a remote server. The example below assumes a Contabo server, but the process works for any x86_64 Linux machine in rescue mode or with Ubuntu pre-installed.
- Install Ubuntu LTS 22.04 (or later) on your server with a root user.
- SSH into the machine using the SSH key you provided during setup.
First, install required packages:
apt update && apt install -y xz-utils gitInstall just:
snap install just --classicOn your local machine (not the server), source the environment file:
. ./.env.contaboThis sets the following environment variables:
export HOST=contabo
export SSH_USER=root
export SSH_PORT=22
export SSH_HOST=194.146.13.222
export REPO_DIR=/tmp/elixosMake sure SSH_HOST points to the public IP of your server.
From your local machine, run:
just bootstrap-baseThis clones the Elixos repository to the remote server.
Then SSH into the server:
ssh root@$SSH_HOST
cd /root/elixosNow run the following steps to install Elixos:
just install_nix_installer_on_ubuntu
just build_on_ubuntu contabo
just install_on_ubuntu contaboAlternatively, you can do everything in one go:
just install_server contaboAfter installation, reboot the server into your new NixOS system.
Once it boots, run the following to re-clone your repository (if needed):
just bootstrap-baseThen log in again and go to the cloned repo:
cd ~/elixosFinalize the configuration:
just switch contaboYou're done! π
These instructions explain how to set up and use a VPN connection in NixOS.
They are based on Surfshark with a WireGuard backend, but the steps can be adapted for other providers.
To authenticate with your VPN provider, you first need to create a WireGuard key pair.
just gen-vpn-keypair [PROVIDER] [BACKEND]- Without arguments, this creates a key pair for Surfshark with the WireGuard backend.
- Keys are stored in
nixos/secrets/vpn/surfshark/wg. - The private key is encrypted using your SOPS setup.
- The public key is stored alongside it β you can delete it after registering with your provider if desired.
Registering your key with Surfshark:
- Log in to your Surfshark account.
- Go to VPN β Manual setup β I already have a key pair.
- Paste the generated public key.
You can add one or more VPN locations to your configuration.
-
Select a location from your providerβs list and download the configuration.
-
Edit
nixos/modules/services/vpn-entries.nixand copy an existing entry, for example:# NL (Amsterdam) networking.wg-quick.interfaces."wg-surfshark-nl" = { address = [ "10.14.0.2/32" ]; dns = [ "162.252.172.57" "149.154.159.92" ]; privateKeyFile = config.sops.secrets."vpn/surfshark/wg/privatekey".path; peers = [ { publicKey = "Lxg3jAOKcBA9tGBtB6vEWMFl5LUEB6AwOpuniYn1cig="; endpoint = "nl-ams.prod.surfshark.com:51820"; allowedIPs = [ "0.0.0.0/0" "::/0" ]; persistentKeepalive = 25; } ]; mtu = 1380; autostart = false; };
-
Change the interface name to match your new location, e.g.
"wg-surfshark-ff"for Frankfurt.
Note: WireGuard interface names have a 15-character limit. -
Update the
address,dns,publicKey, andendpointfields with the values from your providerβs configuration. -
Add the short code (e.g.
ff) to the VPN helperβs location list so it can be selected in the filehome/modules/security/vpn-config.nix.
List available locations:
just vpn-list
π Available Surfshark locations:
* bk endpoint=103.176.152.7:51820 latency=6 ms
ff endpoint=sg-sng.prod.surfshark.com:51820 latency=32 ms
nl endpoint=143.244.42.89:51820 latency=235 ms
sg endpoint=sg-sng.prod.surfshark.com:51820 latency=31 ms* marks the currently active location. Latency is measured from your current connection, so it may be skewed if the VPN is already on.
Turn VPN off:
just vpn-offSpeed test all locations (VPN off recommended for accuracy):
just vpn-list --speedtest-all
π Available Surfshark locations:
* bk endpoint=th-bkk.prod.surfshark.com:51820 latency=10 ms DL=111.95 Mbit/s UL=45.88 Mbit/s Ping=13.027 ms
ff endpoint=sg-sng.prod.surfshark.com:51820 latency=30 ms DL=n/a UL=n/a Ping=n/a
nl endpoint=143.244.42.89:51820 latency=n/a DL=56.00 Mbit/s UL=15.33 Mbit/s Ping=307.128 ms
sg endpoint=sg-sng.prod.surfshark.com:51820 latency=355 ms DL=101.26 Mbit/s UL=76.57 Mbit/s Ping=58.118 msTurn on a specific location:
just vpn-on bkCheck VPN status:
just vpn-statusShow current VPN location:
just vpn-locationWhen connected to a VPN server, you may no longer be able to reach github.com on port 22.
The solution is to use port 443 instead.
Although this change is only strictly necessary when using a VPN, it is recommended to set GitHub to use port 443 by default.
This configuration can be found in home/modules/security/ssh-config.nix.
To verify that GitHub is indeed using port 443, run:
ssh -T git@github.com -vIn the output, you should see:
debug1: Connecting to ssh.github.com [...] port 443.