![[NixOS.png]] # Installation Guide This installation guide will install a NixOS machine with the following features: - Standardized flake format using [Blueprint](https://github.com/numtide/blueprint) - Encrypted secrets using [Ragenix](https://github.com/yaxitech/ragenix) - Declarative disk configuration using [Disko](https://github.com/nix-community/disko) - Root as tmpfs using [Preservation](https://github.com/nix-community/preservation) - Manager user configurations using [Home Manager](https://github.com/nix-community/home-manager) - Manage macOS systems using [Nix Darwin](https://github.com/nix-darwin/nix-darwin) - Local database of Nix packages using [Nix Index Database](https://github.com/nix-community/nix-index-database) - TODO Secure boot using Lanzaboote - TODO auto copy the flake to the remote machine The guide provides instructions for the following installation scenarios: - Installing NixOS locally via the NixOS ISO - Installing NixOS remotely via nixos-anywhere ## Preparing the target machine First, we need to get the target machine booted into the NixOS installer ISO, regardless of local or remote installation. 1. Download the latest NixOS ISO and flash it to a thumb drive. 2. Boot the target machine using the flash drive. 3. Adjust the display scaling, if needed. 4. Connect the machine to Wifi. 5. Open a terminal on the target machine and run the following commands: ```bash # enable flakes $ export NIX_CONFIG="experimental-features = nix-command flakes" # set a temporary password for the root user, like "12345". # this is so we can copy our ssh pubkey in later. # only do this if you are installing remotely. $ sudo passwd root # show the current ip address, remember this for later. # this is so we can point nixos-anywhere to the proper installation target. # only do this if you are installing remotely. $ ip addr ``` All steps after this point can be executed from either: - The NixOS ISO live image directly (the local install) - Or remotely via another device running Nix (the remote install) ## Creating a temporary shell Next, we need to get ourselves some basic tools to construct a flake from scratch. To do so, we're going to make a temporary environment using a temporary Nix shell, which we will replace shortly with a permanent Nix devshell flake output. ```bash # create a temporary shell with some basic tools to get going $ nix-shell -p git gh openssh openssl helix nil # generate an ssh key pair for use with encrypting/decrypting secrets, later. # you might have an ssh key already, but if you don't, do this: $ < /dev/zero > /dev/null ssh-keygen -q -N "" -t ed25519 # in a remote installation scenario where the machine does not have git configured # or a local installation scenario using the NixOS ISO live image, # configure basics for git, replacing below with your own information $ git config --global user.name "First Last" $ git config --global user.email "[email protected]" $ git config --global init.defaultBranch "main" # create a Git repo $ mkdir nixcfg $ cd nixcfg $ git init ``` ## Creating the flake The entrypoint to everything Nix. You only need to provide inputs here. Blueprint abstracts away everything else to separate files in separate directories, which we will create later. ```bash $ hx flake.nix ``` ```nix { description = "My NixOS Config"; inputs = { nixpkgs = { url = "github:nixos/nixpkgs/nixos-25.11"; }; blueprint = { url = "github:numtide/blueprint"; inputs.nixpkgs.follows = "nixpkgs"; }; ragenix = { url = "github:yaxitech/ragenix"; inputs.nixpkgs.follows = "nixpkgs"; }; disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; preservation = { url = "github:nix-community/preservation"; }; home-manager = { url = "github:nix-community/home-manager/release-25.11"; inputs.nixpkgs.follows = "nixpkgs"; }; nix-darwin = { url = "github:nix-darwin/nix-darwin"; inputs.nixpkgs.follows = "nixpkgs"; }; nix-index-database = { url = "github:nix-community/nix-index-database"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = inputs: inputs.blueprint { inherit inputs; nixpkgs.config.allowUnfree = true; }; } ``` ## Creating the devshell Okay, time to create the replacement for that temporary Nix shell I mentioned earlier. This one is a doozy, but I did as much as possible to hide a ton of complexity in helper methods in this devshell. Just copy and paste it in, it'll make sense later. ```bash $ hx devshells/default.nix ``` ```nix { pkgs, ... }: pkgs.mkShell { packages = with pkgs; [ coreutils git gh openssh openssl libuuid rsync helix nil ragenix disko nixos-anywhere ]; shellHook = '' set +e __bash_helpers=$(mktemp) cat > "$__bash_helpers" <<'EOF' __fail_fast() { local _old_opts _old_opts=$(set +o) set -euo pipefail # Name of the function that invoked __fail_fast (the helper itself) local _func_name="''${FUNCNAME[1]}" # FUNCNAME[0] is __fail_fast, [1] is the caller # Helper that knows whether we are inside a function or not _in_function() { [ -n "$_func_name" ]; } local _failed_fn="''${FUNCNAME[0]}" trap ' rc=$? echo "⚠️ '"$_failed_fn"' failed (status $rc) at line $LINENO" eval "$_old_opts" return $rc 2> /dev/null ' ERR trap ' eval "$_old_opts" ' RETURN } export BASEDIR="$(git rev-parse --show-toplevel)" export RULES="$BASEDIR/secrets.nix" export EDITOR="hx" # encrypts a secret from stdin # ref: https://github.com/yaxitech/ragenix/issues/154 encrypt() { __fail_fast agenix --editor "-" --edit "$1" } # prints a secret to stdout # ref: https://github.com/yaxitech/ragenix/issues/158 decrypt() { __fail_fast agenix --editor cat --edit "$1" | grep -v "wasn't changed" } # edits an existing secret edit() { __fail_fast agenix --edit "$1" } # reencrypts all secrets rekey() { __fail_fast agenix --rekey } # generates a valid uuid4, then encrypts it as the machine-id # ref: https://unix.stackexchange.com/a/395460 generate-machine-id() { __fail_fast if [ -z "''${TARGET_HOST+x}" ]; then echo "TARGET_HOST must be defined" return 1 fi file="hosts/$TARGET_HOST/secrets/machine-id.age" if [ -f "$file" ]; then echo "$file already exists. skipping..." return 0 fi mkdir -p "hosts/$TARGET_HOST/secrets" uuidgen | tr -d '-' | tr '[:upper:]' '[:lower:]' | encrypt "$file" } generate-host-ssh-private-key() { __fail_fast if [ -z "''${TARGET_HOST+x}" ]; then echo "TARGET_HOST must be defined" return 1 fi file="hosts/$TARGET_HOST/secrets/ssh_host_ed25519_key.age" if [ -f "$file" ]; then echo "$file already exists. skipping..." return 0 fi temp=$(mktemp -d) cleanup() { rm -rf "$temp"; } trap cleanup EXIT mkdir -p "hosts/$TARGET_HOST/secrets" ssh-keygen -t ed25519 -N "" -C "root@$TARGET_HOST" -f "$temp/key" > /dev/null cat "$temp/key" | encrypt "$file" } generate-user-password() { __fail_fast if [ -z "''${TARGET_HOST+x}" ]; then echo "TARGET_HOST" must be defined return 1 fi read -p "Username: " user file="hosts/$TARGET_HOST/secrets/user-$user-password.age" if [ -f "$file" ]; then echo "$file" already exists. skipping... return 0 fi mkdir -p "hosts/$TARGET_HOST/secrets" openssl passwd -6 | encrypt "$file" return 0 } generate-luks-password() { __fail_fast if [ -z "''${TARGET_HOST+x}" ]; then echo "TARGET_HOST" must be defined return 1 fi file="hosts/$TARGET_HOST/secrets/luks-password.age" if [ -f "$file" ]; then echo "$file" already exists. skipping... return 0 fi read -s -p "Disk Encryption Password: " pass1 && echo read -s -p "Verifying - Disk Encryption Password: " pass2 && echo if [[ "$pass1" != "$pass2" ]]; then echo "Verify failure" return 1 fi mkdir -p "hosts/$TARGET_HOST/secrets" echo "$pass1" | encrypt "$file" return 0 } generate() { __fail_fast read -p "Hostname: " TARGET_HOST generate-machine-id generate-host-ssh-private-key generate-user-password generate-luks-password } decrypt-host-ssh-public-key() { __fail_fast if [ -z "''${TARGET_HOST+x}" ]; then echo "TARGET_HOST" must be defined return 1 fi file="hosts/$TARGET_HOST/secrets/ssh_host_ed25519_key.age" if [ ! -f "$file" ]; then echo "$file" does not exist return 1 fi temp=$(mktemp -d) cleanup() { rm -rf "$temp" } trap cleanup EXIT mkdir -p "$temp/persist/etc/ssh" chmod 755 "$temp/persist/etc/ssh" decrypt "hosts/$TARGET_HOST/secrets/ssh_host_ed25519_key.age" > "$temp/persist/etc/ssh/ssh_host_ed25519_key" chmod 600 "$temp/persist/etc/ssh/ssh_host_ed25519_key" ssh-keygen -y -f "$temp/persist/etc/ssh/ssh_host_ed25519_key" } write-extra-files() { __fail_fast if [ -z "''${TARGET_HOST+x}" ]; then echo "TARGET_HOST" must be defined return 1 fi file="hosts/$TARGET_HOST/secrets/ssh_host_ed25519_key.age" if [ ! -f "$file" ]; then echo "$file" does not exist return 1 fi temp="$1" mkdir -p "$temp/persist/etc/ssh" chmod 755 "$temp/persist/etc/ssh" decrypt "hosts/$TARGET_HOST/secrets/ssh_host_ed25519_key.age" > "$temp/persist/etc/ssh/ssh_host_ed25519_key" chmod 600 "$temp/persist/etc/ssh/ssh_host_ed25519_key" ssh-keygen -y -f "$temp/persist/etc/ssh/ssh_host_ed25519_key" > "$temp/persist/etc/ssh/ssh_host_ed25519_key.pub" chmod 644 "$temp/persist/etc/ssh/ssh_host_ed25519_key.pub" decrypt "hosts/$TARGET_HOST/secrets/machine-id.age" > "$temp/persist/etc/machine-id" } # Example for a function that already handles its own errors: install() { __fail_fast temp=$(mktemp -d) cleanup() { rm -rf "$temp" } trap cleanup EXIT read -p "Hostname: " TARGET_HOST read -p "Host IP: " TARGET_IP write-extra-files "$temp" if [ "$TARGET_IP" = "localhost" ]; then decrypt "hosts/$TARGET_HOST/secrets/luks-password.age" > /tmp/luks.key sudo disko --mode destroy,format,mount "hosts/$TARGET_HOST/disk-configuration.nix" sudo cp -r "$temp"/* /mnt sudo nixos-install --no-root-password --flake ".#$TARGET_HOST" echo "reboot now!" else nixos-anywhere "root@$TARGET_IP" \ --flake ".#$TARGET_HOST" \ --extra-files "$temp" \ --build-on remote -L \ --disk-encryption-keys /tmp/luks.key <(decrypt "hosts/$TARGET_HOST/secrets/luks-password.age") fi } EOF source "$__bash_helpers" set -e trap 'rm -f "$__bash_helpers"' EXIT ''; } ``` ## Using the devshell Now, it's time to switch from the temporary shell to the new, permanent installation devshell. ```bash # track all files via Git, or the flake won't see them $ git add . # pin our flake dependencies $ nix flake lock && git add flake.lock # leave our temporary development environment $ exit # navigate back to nixcfg directory $ cd nixcfg # enter our improved development environment $ nix develop ``` ## SSH'ing into the target machine If you are installing NixOS on a remote machine, you'll need to SSH into the target machine from the source machine. If you are installing NixOS on the local machine, you can skip this section. ```bash # set the ip for use in later commands $ export TARGET_IP="192.168.blah.blah" # ssh in as root using temp password, then delete it in favor of ssh key $ ssh-keygen -R "$TARGET_IP" $ ssh-copy-id -i ~/.ssh/id_ed25519.pub "root@$TARGET_IP" $ ssh "root@$TARGET_IP" $ passwd -d root ``` ## Acquiring target machine config files There are certain files that we must retrieve from the target system in order to configure our Flake. These include the `hardware-configuration.nix` file, which contains configuration for the supported hardware of the system, and the disk layout from `lsblk` for partitioning. ```bash # create nixos config $ sudo nixos-generate-config --no-filesystems --root /mnt # copy disk layout $ sudo bash -c "lsblk > /mnt/etc/disks.txt" ``` ## Rsync'ing the files from the target to the source machine If you are installing NixOS on a remote machine, you'll need to Rsync the files we made in the previous step from the target machine to the source machine. If you are installing NixOS on the local machine, you can skip this section. ```bash # back to host machine $ exit # create a directory to store files from target machine temporarily $ mkdir mnt && echo mnt >> .gitignore # copy /mnt to the source machine $ rsync -e ssh -azvhP "root@$TARGET_IP:/mnt/" ./mnt ``` ## Copying the files from `/mnt` to the Git repo If you are installing NixOS on the local machine, you'll need to copy the files we made in the previous step from the `/mnt` directory to the Git repo. If you are installing NixOS on a remote machine, you can skip this section. ```bash # create a directory to store files from target machine temporarily $ mkdir mnt && echo mnt >> .gitignore # copy /mnt to the Git repo $ cp -r /mnt/* mnt ``` ## Configuring agenix Next, we need to focus on secrets. There are a few *core secrets* that every NixOS system needs: - `machine-id`, which is the unique identifier for this machine, typically populated at first boot by systemd. Without this, the system will be borked. - `ssh_host_ed25519_key`, which is the unique ssh private key for the machine. This is not only used for ssh access (which you may not need), but is also the key that is used to decrypt the secrets at boot time. Without this, the system will be borked. We are going to encrypt the secrets using age, then commit and push the encrypted secret files to our Git repo. We manage access to the secrets via the `secrets.nix` file. To use this file, simply define the relative file path of your secret file within the Git repo, and set the `publicKeys` subkey value to an array of ssh public keys that are authorized to decrypt that secret. Essentially, we are defining a list of secrets, and a list of machines/users that are allowed to decrypt those secrets. ```bash $ hx secrets.nix ``` ```nix # you MUST allow the target machine to decrypt its own secrets! # otherwise you'll bork the install! # you MUST also rekey your secrets whenever you change this file! let keys = { "user@device" = "<ssh public key>"; }; all = builtins.attrValues keys; in { # replace <hostname> and <username> "hosts/<hostname>/secrets/machine-id.age".publicKeys = all; "hosts/<hostname>/secrets/ssh_host_ed25519_key.age".publicKeys = all; "hosts/<hostname>/secrets/luks-password.age".publicKeys = all; "hosts/<hostname>/secrets/user-<username>-password.age".publicKeys = all; } ``` Replace the `user@device` above with a better name and an actual ssh public key. For example, in a remote installation scenario, say you are installing NixOS on a Framework 16 remotely from an M1 Macbook. Your username on the Macbook is "jacobranson". I'd call the key `jacobranson@m1-macbook` and set the value of the string to the output of running the command `cat ~/.ssh/id_ed25519.pub` on the Macbook. I'd replace every instance of `<hostname>` with `framework-16`. This authorizes `jacobranson@m1-macbook` to decrypt the secrets for the `framework-16` device. Another example, in a local installation scenario, say you are installing NixOS on a Framework 16 locally via the NixOS ISO live image. Your username on the live image is "nixos". I'd call the key `nixos@nixos` and set the value of the string to the output of running the command `cat ~/.ssh/id_ed25519.pub`. This authorizes `nixos@nixos` to decrypt the secrets for the `framework-16` device. Unlike the first installation scenario, the second scenario is only good for a single use, because the next time you boot a NixOS ISO live image, it'll auto-generate a new ssh key pair. We need to do this for now, so we'll deal with cleaning it up later. ## Generating secrets Now, that devshell we set up earlier is going to do some heavy lifting. This `generate` command will prompt you to define the hostname (ex: `framework-16` from the previous example) that you wish to generate secrets for, as well as the username (ex: `jacobranson`) of the default user for the machine. It will then create and encrypt the following secrets for you: - `machine-id.age`, which is auto-generated - `ssh_host_ed25519_key.age`, which is auto-generated - `user-yourusername-password.age` (where `yourusername` is replaced), which prompts you for a password for that user account - `luks-password.age`, which prompts you for a password to decrypt a luks-encrypted disk partition ```bash $ generate ``` If you want to change anything, just delete the `.age` file and run `generate` again, and it'll prompt you only for the missing files to generate new secrets. ## Configuring agenix again Now that we've generated our secrets for the machine we are installing, we next need to authorize that machine to decrypt its own secrets. If we don't, the machine will be borked beyond repair. First, let's get the machine's ssh host public key: ```bash $ decrypt-host-ssh-public-key ``` Next, let's add it to our `secrets.nix` file: ```bash $ hx secrets.nix ``` Add a new entry to the `keys` attrset `root@<hostname>`. It should look something like this in a remote installation scenario: ```nix let keys = { "jacobranson@m1-macbook" = "some ssh pub key"; "root@framework-16" = "some other ssh pub key"; }; all = builtins.attrValues keys; in { "hosts/framework-16/secrets/machine-id.age".publicKeys = all; "hosts/framework-16/secrets/ssh_host_ed25519_key.age".publicKeys = all; "hosts/framework-16/secrets/luks-password.age".publicKeys = all; "hosts/framework-16/secrets/user-jacobranson-password.age".publicKeys = all; } ``` Or something like this in a local install scenario: ```nix let keys = { "nixos@nixos" = "some ssh pub key"; "root@framework-16" = "some other ssh pub key"; }; all = builtins.attrValues keys; in { "hosts/framework-16/secrets/machine-id.age".publicKeys = all; "hosts/framework-16/secrets/ssh_host_ed25519_key.age".publicKeys = all; "hosts/framework-16/secrets/luks-password.age".publicKeys = all; "hosts/framework-16/secrets/user-jacobranson-password.age".publicKeys = all; } ``` Finally, we need to rekey our secrets. This needs to be done every time we modify this `secrets.nix` file. If we forget to do this, even though we added the `root@framework-16` line, it won't apply, and we'll have an installation that is borked beyond repair. ```bash $ rekey ``` ## Configuring disks Next, it's time to configure our disks. Let's take a look at what hardware is available, first. ```bash $ hx mnt/etc/disks.txt ``` Once you determine which device you are going to install NixOS on, you can create the `disk-configuration.nix` file. All you have to do is set the `disk`, `root-size`, and `swap-size` variables, and keep the rest of the file as-is, unless you want to change it for your own needs. ```bash # set a hostname variable for repeated use later (ex: "framework-16") $ export TARGET_HOST="<hostname>" $ hx hosts/$TARGET_HOST/disk-configuration.nix ``` ```nix let disk = "<disk>"; # replace <disk> with target disk, for example, nvme0n1 root-size = "<root-size>"; # replace with intended size, for example, 32G swap-size = "<swap-size>"; # replace with intended size, for example, 16G in { fileSystems."/persist".neededForBoot = true; disko.devices = { nodev."/" = { fsType = "tmpfs"; mountOptions = [ "defaults" "size=${root-size}" "mode=755" ]; }; disk."${disk}" = { device = "/dev/${disk}"; type = "disk"; content = { type = "gpt"; partitions = { ESP = { size = "512M"; type = "EF00"; content = { type = "filesystem"; format = "vfat"; mountpoint = "/boot"; }; }; luks = { size = "100%"; content = { type = "luks"; name = "crypted"; passwordFile = "/tmp/luks.key"; settings.allowDiscards = true; content = { type = "btrfs"; extraArgs = [ "-f" ]; subvolumes = { "/nix" = { mountpoint = "/nix"; mountOptions = [ "compress=zstd" "noatime" ]; }; "/persist" = { mountpoint = "/persist"; mountOptions = [ "compress=zstd" "noatime" ]; }; "/swap" = { mountpoint = "/swap"; swap.swapfile.size = "${swap-size}"; }; }; }; }; }; }; }; }; }; } ``` ## Configuring the host Now it's time to configure the system itself! We're only going to do the basics here, for now. ```bash $ hx hosts/$TARGET_HOST/configuration.nix ``` ```nix { config, pkgs, inputs, ... }: let user = "<user>"; # default username, ex: jacobranson hostname = "<hostname>"; # hostname: ex: framework-16 layout = "<layout>"; # keyboard layout; ex: us locale = "<locale>"; # ex: en_US.UTF-8 timezone = "<timezone>"; # ex: America/New_York ssh-keys = [ # who is authorized to remote access this machine "<some public ssh public key>" # ex: jacobranson@m1-macbook ssh pub key ]; in { imports = [ ./hardware-configuration.nix ./disk-configuration.nix inputs.disko.nixosModules.default inputs.ragenix.nixosModules.default inputs.preservation.nixosModules.default ]; # linux kernel version to use and boot options. # systemd-boot is mandatory for lanzaboote. # systemd initrd is mandatory for preservation. boot.kernelPackages = pkgs.linuxPackages_latest; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; boot.initrd.systemd.enable = true; # ensure the system can connect to the internet networking.networkmanager.enable = true; networking.hostName = hostname; # configure the keyboard layout services.xserver.xkb.layout = layout; console.keyMap = layout; # set the locale and timezone i18n.defaultLocale = locale; time.timeZone = timezone; # enable the GNOME desktop environment services.desktopManager.gnome.enable = true; services.displayManager.gdm.enable = true; # configure standard fonts fonts.fontDir.enable = true; fonts.enableDefaultPackages = true; # add additional system packages to install environment.systemPackages = with pkgs; [ gh devbox ]; # populate nix channels with this flake's nixpkgs revision nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ]; # set any nix settings you want here. for reference, see: # https://nixos.org/manual/nix/unstable/command-ref/conf-file.html#available-settings nix.settings = { # enable flakes experimental-features = [ "nix-command" "flakes" ]; }; # secrets for this machine age.secrets = { "user-${user}-password".file = ./secrets/user-${user}-password.age; }; # assign the machine id environment.etc.machine-id.source = "/persist/etc/machine-id"; # ensure the system can be accessed remotely via ssh services.openssh = { enable = true; # ragenix uses this to determine which ssh keys to use for decryption hostKeys = [{ path = "/persist/etc/ssh/ssh_host_ed25519_key"; type = "ed25519"; }]; }; # configure the users of this system users.users = { root.hashedPasswordFile = config.age.secrets."user-${user}-password".path; "${user}" = { isNormalUser = true; hashedPasswordFile = config.age.secrets."user-${user}-password".path; extraGroups = [ "wheel" "networkmanager" ]; openssh.authorizedKeys.keys = ssh-keys; }; }; fileSystems."/persist".neededForBoot = true; preservation = { enable = true; preserveAt."/persist" = { commonMountOptions = [ "x-gvfs-hide" ]; directories = [ { directory = "/var/lib/nixos"; inInitrd = true; } "/etc/secureboot" "/etc/NetworkManager/system-connections" "/var/lib/bluetooth" "/var/lib/fprint" "/var/lib/fwupd" "/var/lib/libvirt" "/var/lib/power-profiles-daemon" "/var/lib/systemd" "/var/log" ]; users = { root = { home = "/root"; directories = [ { directory = ".ssh"; mode = "0700"; } ]; }; "${user}" = { directories = [ "Desktop" "Documents" "Downloads" "Music" "Pictures" "Projects" "Public" "Templates" "Videos" { directory = ".ssh"; mode = "0700"; } ".config/nixcfg" ".config/gh" ]; files = [ ".bash_history" ".config/monitors.xml" ]; }; }; }; }; systemd.tmpfiles.settings.preservation = { "/home/${user}/.config".d = { user = "${user}"; group = "users"; mode = "0755"; }; "/home/${user}/.local".d = { user = "${user}"; group = "users"; mode = "0755"; }; "/home/${user}/.local/share".d = { user = "${user}"; group = "users"; mode = "0755"; }; "/home/${user}/.local/state".d = { user = "${user}"; group = "users"; mode = "0755"; }; }; # systemd-machine-id-commit.service would fail, but it is not relevant # in this specific setup for a persistent machine-id so we disable it systemd.suppressedSystemUnits = [ "systemd-machine-id-commit.service" ]; # This value determines the NixOS release from which the default # 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 = "25.11"; # Did you read the comment? } ``` ## Configuring the user We have finished covering system configuration, but now we must configure our user account. The minimum config below sets up `home-manager` for `$TARGET_USER` on the given `$TARGET_HOST`, and configures `git` with the minimum configuration. ```bash # export username variable for use later $ export TARGET_USER="<username>" # ex: jacobranson $ hx hosts/$TARGET_HOST/users/$TARGET_USER/home-configuration.nix ``` ```nix { ... }: { home.username = "<username>"; # ex: jacobranson home.homeDirectory = "/home/<username>"; # ex: /home/jacobranson home.stateVersion = "25.11"; programs.git = { enable = true; settings = { user.name = "First Last"; # ex: Jacob Ranson user.email = "[email protected]"; init.defaultBranch = "main"; }; }; } ``` ## Deploying to the target system Time for the moment of truth! Let's get the install going. If you are installing locally, make sure you push the repo to GitHub, or you'll lose all your progress. ```bash # move the hardware config next to the system config $ mv mnt/etc/nixos/hardware-configuration.nix hosts/$TARGET_HOST/ # add everything to Git and push $ git add . $ git commit -m "Created my first NixOS system flake" $ git push # install the system $ install ``` Grab some popcorn and take a seat, this may take a while, especially if your system requires certain packages to be compiled from source. ## Booting the target system Alright, boot up that device and you should see a fresh NixOS installation! Make sure you can: - Decrypt the drive using the luks password you set earlier - Sign in to the default user using the user password you set earlier - `cat /etc/machine-id` and see if it stays consistent between reboots ## Cleaning up from a remote installation If you did a remote install, you can do the following steps: ```bash # ssh in as the new user, replacing the old ssh information with the new one $ ssh-keygen -R "$TARGET_IP" && ssh $TARGET_USER@$TARGET_IP # login to GitHub CLI $ gh auth login # clone the flake $ gh repo clone nixcfg ~/.config/nixcfg # close ssh session $ exit # get rid of the temporary files we rsync'd earlier $ rm -rf mnt ``` ## Cleaning up from a local installation If you did a local install, you can do the following steps: ```bash # login to GitHub CLI $ gh auth login # clone the flake $ gh repo clone nixcfg ~/.config/nixcfg ``` You can now remove `nixos@nixos` from `secrets.nix`, add, commit, and push that change, since that live image's ssh key pair is gone forever. ## Wrapping up That's it! You should now have a very bleeding-edge NixOS installation! # Resources | Source | Link | | ------------------- | --------------------------------------------------- | | Determinate Systems | https://determinate.systems | | | https://zero-to-nix.com | | | https://github.com/DeterminateSystems/nix-installer | | | https://flakehub.com | | | https://flakehub.com/flake/DeterminateSystems/fh | | MyNixOS | https://mynixos.com | | Jetpack | https://jetpack.io/devbox | | | https://nixhub.io | | Cachix | https://cachix.org | | | https://devenv.sh | | Open Source Nix | https://github.com/Mic92/sops-nix | | | https://github.com/nix-darwin/nix-darwin | | | https://github.com/LGUG2Z/nixos-wsl-starter | | | https://nixos-and-flakes.thiscute.world | | Universal Blue | https://getfleek.dev | | Nix Community | https://github.com/nix-community/home-manager | | | https://github.com/nix-community/nixos-anywhere | | | https://github.com/nix-community/disko | | | https://github.com/nix-community/lanzaboote | | | https://github.com/nix-community/impermanence | | | https://github.com/nix-community/nixGL | | | https://github.com/nix-community/comma | | | https://github.com/nix-community/nix-index-database | | | https://github.com/nix-community/nix-direnv | | | https://github.com/nix-community/nixvim | | | https://github.com/nix-community/NixOS-WSL |