Skip to content

Instantly share code, notes, and snippets.

@mx00s
Last active Dec 23, 2022
Embed
What would you like to do?
NixOS install script based on @grahamc's "Erase Your Darlings" blog post
#!/usr/bin/env bash
#
# NixOS install script synthesized from:
#
# - Erase Your Darlings (https://grahamc.com/blog/erase-your-darlings)
# - ZFS Datasets for NixOS (https://grahamc.com/blog/nixos-on-zfs)
# - NixOS Manual (https://nixos.org/nixos/manual/)
#
# It expects the name of the block device (e.g. 'sda') to partition
# and install NixOS on and an authorized public ssh key to log in as
# 'root' remotely. The script must also be executed as root.
#
# Example: `sudo ./install.sh sde "ssh-rsa AAAAB..."`
#
set -euo pipefail
################################################################################
export COLOR_RESET="\033[0m"
export RED_BG="\033[41m"
export BLUE_BG="\033[44m"
function err {
echo -e "${RED_BG}$1${COLOR_RESET}"
}
function info {
echo -e "${BLUE_BG}$1${COLOR_RESET}"
}
################################################################################
export DISK=$1
export AUTHORIZED_SSH_KEY=$2
if ! [[ -v DISK ]]; then
err "Missing argument. Expected block device name, e.g. 'sda'"
exit 1
fi
export DISK_PATH="/dev/${DISK}"
if ! [[ -b "$DISK_PATH" ]]; then
err "Invalid argument: '${DISK_PATH}' is not a block special file"
exit 1
fi
if ! [[ -v AUTHORIZED_SSH_KEY ]]; then
err "Missing argument. Expected public SSH key, e.g. 'ssh-rsa AAAAB...'"
exit 1
fi
if [[ "$EUID" > 0 ]]; then
err "Must run as root"
exit 1
fi
export ZFS_POOL="rpool"
# ephemeral datasets
export ZFS_LOCAL="${ZFS_POOL}/local"
export ZFS_DS_ROOT="${ZFS_LOCAL}/root"
export ZFS_DS_NIX="${ZFS_LOCAL}/nix"
# persistent datasets
export ZFS_SAFE="${ZFS_POOL}/safe"
export ZFS_DS_HOME="${ZFS_SAFE}/home"
export ZFS_DS_PERSIST="${ZFS_SAFE}/persist"
export ZFS_BLANK_SNAPSHOT="${ZFS_DS_ROOT}@blank"
################################################################################
info "Running the UEFI (GPT) partitioning and formatting directions from the NixOS manual ..."
parted "$DISK_PATH" -- mklabel gpt
parted "$DISK_PATH" -- mkpart primary 512MiB 100%
parted "$DISK_PATH" -- mkpart ESP fat32 1MiB 512MiB
parted "$DISK_PATH" -- set 2 boot on
export DISK_PART_ROOT="${DISK_PATH}1"
export DISK_PART_BOOT="${DISK_PATH}2"
info "Formatting boot partition ..."
mkfs.fat -F 32 -n boot "$DISK_PART_BOOT"
info "Creating '$ZFS_POOL' ZFS pool for '$DISK_PART_ROOT' ..."
zpool create -f "$ZFS_POOL" "$DISK_PART_ROOT"
info "Enabling compression for '$ZFS_POOL' ZFS pool ..."
zfs set compression=on "$ZFS_POOL"
info "Creating '$ZFS_DS_ROOT' ZFS dataset ..."
zfs create -p -o mountpoint=legacy "$ZFS_DS_ROOT"
info "Configuring extended attributes setting for '$ZFS_DS_ROOT' ZFS dataset ..."
zfs set xattr=sa "$ZFS_DS_ROOT"
info "Configuring access control list setting for '$ZFS_DS_ROOT' ZFS dataset ..."
zfs set acltype=posixacl "$ZFS_DS_ROOT"
info "Creating '$ZFS_BLANK_SNAPSHOT' ZFS snapshot ..."
zfs snapshot "$ZFS_BLANK_SNAPSHOT"
info "Mounting '$ZFS_DS_ROOT' to /mnt ..."
mount -t zfs "$ZFS_DS_ROOT" /mnt
info "Mounting '$DISK_PART_BOOT' to /mnt/boot ..."
mkdir /mnt/boot
mount -t vfat "$DISK_PART_BOOT" /mnt/boot
info "Creating '$ZFS_DS_NIX' ZFS dataset ..."
zfs create -p -o mountpoint=legacy "$ZFS_DS_NIX"
info "Disabling access time setting for '$ZFS_DS_NIX' ZFS dataset ..."
zfs set atime=off "$ZFS_DS_NIX"
info "Mounting '$ZFS_DS_NIX' to /mnt/nix ..."
mkdir /mnt/nix
mount -t zfs "$ZFS_DS_NIX" /mnt/nix
info "Creating '$ZFS_DS_HOME' ZFS dataset ..."
zfs create -p -o mountpoint=legacy "$ZFS_DS_HOME"
info "Mounting '$ZFS_DS_HOME' to /mnt/home ..."
mkdir /mnt/home
mount -t zfs "$ZFS_DS_HOME" /mnt/home
info "Creating '$ZFS_DS_PERSIST' ZFS dataset ..."
zfs create -p -o mountpoint=legacy "$ZFS_DS_PERSIST"
info "Mounting '$ZFS_DS_PERSIST' to /mnt/persist ..."
mkdir /mnt/persist
mount -t zfs "$ZFS_DS_PERSIST" /mnt/persist
info "Permit ZFS auto-snapshots on ${ZFS_SAFE}/* datasets ..."
zfs set com.sun:auto-snapshot=true "$ZFS_DS_HOME"
zfs set com.sun:auto-snapshot=true "$ZFS_DS_PERSIST"
info "Creating persistent directory for host SSH keys ..."
mkdir -p /mnt/persist/etc/ssh
info "Generating NixOS configuration (/mnt/etc/nixos/*.nix) ..."
nixos-generate-config --root /mnt
info "Enter password for the root user ..."
ROOT_PASSWORD_HASH="$(mkpasswd -m sha-512 | sed 's/\$/\\$/g')"
info "Enter personal user name ..."
read USER_NAME
info "Enter password for '${USER_NAME}' user ..."
USER_PASSWORD_HASH="$(mkpasswd -m sha-512 | sed 's/\$/\\$/g')"
info "Moving generated hardware-configuration.nix to /persist/etc/nixos/ ..."
mkdir -p /mnt/persist/etc/nixos
mv /mnt/etc/nixos/hardware-configuration.nix /mnt/persist/etc/nixos/
info "Backing up the originally generated configuration.nix to /persist/etc/nixos/configuration.nix.original ..."
mv /mnt/etc/nixos/configuration.nix /mnt/persist/etc/nixos/configuration.nix.original
info "Backing up the this installer script to /persist/etc/nixos/install.sh.original ..."
cp "$0" /mnt/persist/etc/nixos/install.sh.original
info "Writing NixOS configuration to /persist/etc/nixos/ ..."
cat <<EOF > /mnt/persist/etc/nixos/configuration.nix
{ config, pkgs, lib, ... }:
{
imports =
[
./hardware-configuration.nix
];
nix.nixPath =
[
"nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
"nixos-config=/persist/etc/nixos/configuration.nix"
"/nix/var/nix/profiles/per-user/root/channels"
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# source: https://grahamc.com/blog/erase-your-darlings
boot.initrd.postDeviceCommands = lib.mkAfter ''
zfs rollback -r ${ZFS_BLANK_SNAPSHOT}
'';
# source: https://grahamc.com/blog/nixos-on-zfs
boot.kernelParams = [ "elevator=none" ];
networking.hostId = "$(head -c 8 /etc/machine-id)";
networking.useDHCP = false;
networking.interfaces.enp3s0.useDHCP = true;
environment.systemPackages = with pkgs;
[
emacs
];
services.zfs = {
autoScrub.enable = true;
autoSnapshot.enable = true;
# TODO: autoReplication
};
services.openssh = {
enable = true;
permitRootLogin = "no";
passwordAuthentication = false;
hostKeys =
[
{
path = "/persist/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
{
path = "/persist/etc/ssh/ssh_host_rsa_key";
type = "rsa";
bits = 4096;
}
];
};
users = {
mutableUsers = false;
users = {
root = {
initialHashedPassword = "${ROOT_PASSWORD_HASH}";
};
${USER_NAME} = {
createHome = true;
initialHashedPassword = "${USER_PASSWORD_HASH}";
extraGroups = [ "wheel" ];
group = "users";
uid = 1000;
home = "/home/${USER_NAME}";
useDefaultShell = true;
openssh.authorizedKeys.keys = [ "${AUTHORIZED_SSH_KEY}" ];
};
};
};
# 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 = "20.03"; # Did you read the comment?
}
EOF
info "Installing NixOS to /mnt ..."
ln -s /mnt/persist/etc/nixos/configuration.nix /mnt/etc/nixos/configuration.nix
nixos-install -I "nixos-config=/mnt/persist/etc/nixos/configuration.nix" --no-root-passwd # already prompted for and configured password
@cole-h
Copy link

cole-h commented May 24, 2020

Very cool! However, note:

[    0.040742] Kernel parameter elevator= does not have any effect anymore.
               Please use sysfs to set IO scheduler for individual devices.

So https://gist.github.com/mx00s/ea2462a3fe6fdaa65692fe7ee824de3e#file-install-sh-L164 is unnecessary (or at least needs another solution).

@mx00s
Copy link
Author

mx00s commented May 27, 2020

Thank you, @cole-h. I just saw your comment as I updating this script here.

@grahamc, you may be interested in this too.

@grahamc
Copy link

grahamc commented May 27, 2020

👍 thanks! I will need to fix this in my blog post ... :)

@mx00s
Copy link
Author

mx00s commented May 31, 2020

I started looking into this on my NixOS 20.03 (kernel `5.4.43) and haven't seen any errors like what @cole-h reports.

Mention of the parameter was removed from docs ini rev f97eeb6cf of the kernel and the error message was introduced in f8db3835. I haven't worked out yet how to specify the desired behavior using sysfs/NixOS.

Notably, the only options supported for elevator= according to Documentation/block/switching-sched.rst in the first rev were deadline, noop, and cfq (default). @grahamc, did you perhaps mean to use noop instead of none?

@cole-h
Copy link

cole-h commented Jun 1, 2020

I only saw that message appear in dmesg's output, not journalctl -b -- maybe try looking there? I was using linuxPackages_latest when I posted my earlier comment (which was 5.6.xx at the time).

When I run cat /sys/block/sda/queue/scheduler, I can only see [mq-deadline] kyber none as options -- noop doesn't seem to exist (maybe it's an internal internal setting?). Though I wonder if the docs are outdated, considering mq-deadline and kyber are missing.

@cole-h
Copy link

cole-h commented Sep 8, 2020

I finally figured out how to set the scheduler to none for ZFS partitions using services.udev.extraRules:

https://github.com/cole-h/nixos-config/blob/3589d53515921772867065150c6b5a500a5b9a6b/hosts/scadrial/modules/services.nix#L70

Thanks to this Reddit post: https://www.reddit.com/r/zfs/comments/iluh00/zfs_linux_io_scheduler/g3uxm3m/

@operator-name
Copy link

@mx00s thanks for this script! With NixOS 21.11 you'll need to add isNormalUser = true; to L236.

Most guides seem to also call for -o ashift=12 and -o autotrim=on but I'm no zfs expert.

@tripleo1
Copy link

@mx00s @operator-name @cole-h @grahamc

Does this work because I'm about to try it and don't want any unwelcome surprises.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment