Introduction
In this post, I will show you how you can create a custom NixOS ISO image, using our normal nix configuration as if it another machine/device. Some of you may be wondering why you want to do that vs using the normal ISO. Particular for installing on my machines I would like to have my device setup in one go, rather than previously I would install using the normal ISO, then clone my dot files and build my config again.
Well, we can skip the first step and have it build my config in my one go during with our custom ISO. We can also have some applications available to us in the ISO, like a different terminal, i.e. Wezterm. Which makes the experience slightly nicer.
For those of you wondering, an ISO archive file system which contains files, which can burn onto a disk or typically USB. Which normally is used to install operating systems.
Flakes
I will assume you already have your nix config setup and using flakes. I will be adding a new NixOS configuration which will then build our ISO.
Within our nixosConfigurations
, in our flake.nix
file we will add a new section, which I simply named iso
.
{
nixosConfigurations = {
iso = lib.nixosSystem {
modules = [
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix"
"${nixpkgs}/nixos/modules/installer/cd-dvd/channel.nix"
./hosts/iso/configuration.nix
];
specialArgs = {inherit inputs outputs;};
};
framework = lib.nixosSystem {
modules = [./hosts/framework/configuration.nix];
specialArgs = {inherit inputs outputs;};
};
};
}
You can see framework
is another device (my laptop) nix config. Above it is the config for my nix ISO. There are a
bunch of installers config available here.
I will use the graphical gnome config, ${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
because I am already familiar with gnome and sometimes having a desktop environment can make it
easier to debug and use it also a recovery media if something goes wrong with my NixOS device. We can access a browser
for example. But if you want a smaller more minimal ISO, you can use one of the installers without a GUI, i.e. ones
without graphical
.
We also provide with some channels ${nixpkgs}/nixos/modules/installer/cd-dvd/channel.nix
, so the user doesn’t need
to update the channels manually first.
Configuration
Then onto the real meat and potatoes, the entry point for our ISO hosts/iso/configuration.nix
.
{
pkgs,
lib,
...
}: {
imports = [
../../nixos
];
nixpkgs = {
hostPlatform = lib.mkDefault "x86_64-linux";
config.allowUnfree = true;
};
nix = {
settings.experimental-features = ["nix-command" "flakes"];
extraOptions = "experimental-features = nix-command flakes";
};
services = {
qemuGuest.enable = true;
openssh.settings.PermitRootLogin = lib.mkForce "yes";
};
boot = {
kernelPackages = pkgs.linuxPackages_latest;
supportedFilesystems = lib.mkForce ["btrfs" "reiserfs" "vfat" "f2fs" "xfs" "ntfs" "cifs"];
};
networking = {
hostName = "iso";
};
# gnome power settings do not turn off screen
systemd = {
services.sshd.wantedBy = pkgs.lib.mkForce ["multi-user.target"];
targets = {
sleep.enable = false;
suspend.enable = false;
hibernate.enable = false;
hybrid-sleep.enable = false;
};
};
home-manager.users.nixos = import ./home.nix;
users.extraUsers.root.password = "nixos";
}
First, I import the global NixOS config I share with all of my NixOS configurations, which include things like PAM auth, fonts and some common nix settings. I could probably strip out some of the config to trim down the ISO, but I am not super fused about the total size of the ISO at the moment. I don’t even use it very frequently, as I don’t need to reinstall my system very often, now that my config has stabilised.
The rest of my config above is just some basic settings, such as making sure gnome doesn’t turn off the screen or suspend during the installation. The installation can take some time, I didn’t like having to manually set the option during the installation.
Install Script
To make the above a bit simpler to follow, I removed the pkgs I installed. The main one being the nix_installer
, bash
script, which uses the very cool gum tool. It makes it effortless to provide
an interactive script. The main thing the script does, it clones my dot files, asks which host to install. Then apply
the disko configuration to partition the disk. Where disko,
allows us to declaratively declare how our disk will look, i.e. setting up LUKS, swap file.
Here
is a link to an example config for my laptop. Again, I will do a more in-depth post about disko in the future.
{
environment.systemPackages = with pkgs; [
git
gum
(
writeShellScriptBin "nix_installer"
''
#!/usr/bin/env bash
set -euo pipefail
gsettings set org.gnome.desktop.session idle-delay 0
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
if [ "$(id -u)" -eq 0 ]; then
echo "ERROR! $(basename "$0") should be run as a regular user"
exit 1
fi
if [ ! -d "$HOME/dotfiles/.git" ]; then
git clone https://gitlab.com/hmajid2301/dotfiles.git "$HOME/dotfiles"
fi
TARGET_HOST=$(ls -1 ~/dotfiles/hosts/*/configuration.nix | cut -d'/' -f6 | grep -v iso | gum choose)
if [ ! -e "$HOME/dotfiles/hosts/$TARGET_HOST/disks.nix" ]; then
echo "ERROR! $(basename "$0") could not find the required $HOME/dotfiles/hosts/$TARGET_HOST/disks.nix"
exit 1
fi
gum confirm --default=false \
"🔥 🔥 🔥 WARNING!!!! This will ERASE ALL DATA on the disk $TARGET_HOST. Are you sure you want to continue?"
echo "Partitioning Disks"
sudo nix run github:nix-community/disko \
--extra-experimental-features "nix-command flakes" \
--no-write-lock-file \
-- \
--mode zap_create_mount \
"$HOME/dotfiles/hosts/$TARGET_HOST/disks.nix"
sudo nixos-install --flake "$HOME/dotfiles#$TARGET_HOST"
''
)
];
}
Then finally, we run NixOS install, using the dot files we cloned. Again, the specifics don’t matter too much. As this is my specific ISO, the script is actually run automatically, setup in my home-manager config, when you log in to the ISO. This happens automatically when the desktop environment loads. I just wanted to show an example of a script. I’m pretty sure, I found something similar script and changed it to fit my needs, but I couldn’t find the original 🙈.
home-manager
I also set up home-manager, so I can reuse my home-manager
config for my other devices, such as terminal config. Which
fonts to use. Again, just a small thing to make my ISO a bit closer to my normal dev setup. I will have a more in-depth
post about how my home-manager setup works and how I have set up in the future, but for now, I will share briefly
what it looks like without going into too much detail.
Where my home-manager config looks like so:
{
inputs,
lib,
config,
...
}: {
imports = [
../../home-manager
];
config = {
home.file.".config/autostart/foot.desktop".text = ''
[Desktop Entry]
Type=Application
Exec=foot -m fish -c 'nix_installer' 2>&1
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name[en_NG]=Terminal
Name=Terminal
Comment[en_NG]=Start Terminal On Startup
Comment=Start Terminal On Startup
'';
modules = {
editors = {
nvim.enable = true;
};
shells = {
fish.enable = true;
};
terminals = {
foot.enable = true;
};
};
my.settings = {
host = "iso";
default = {
shell = "fish";
terminal = "foot";
browser = "firefox";
editor = "nvim";
};
fonts.monospace = "FiraCode Nerd Font Mono";
};
colorscheme = inputs.nix-colors.colorSchemes.catppuccin-mocha;
home = {
username = lib.mkDefault "nixos";
homeDirectory = lib.mkDefault "/home/${config.home.username}";
stateVersion = lib.mkDefault "23.05";
};
};
}
Again, this shares config with my common home manager config, here we also have to activate which tools we want to use
such as shells, terminals and some default settings which we probably don’t need.
The interesting bit here is the foot.desktop
app, which auto-runs and starts the nix_installer
script.
Build
Then to build the config, we can run nix build ~/dotfiles#nixosConfigurations.iso.config.system.build.isoImage
.
Note, you will have to change the path to your nix config ~/dotfiles
to where your nix config actually is. The ISO
will be located in the results/
folder in the same folder as your nix config.
That’s it! We went over how we can create our own custom ISO. Which you can use to speed up setting new machines to use NixOS. If you like, check out my post about Ventoy, which allows us to have multiple ISOs on the same USB. A great way to try out multiple OSs.