Skip to content

Instantly share code, notes, and snippets.

@pweldon
Last active June 17, 2023 13:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pweldon/82c05505543ea194e9c704980389c3bf to your computer and use it in GitHub Desktop.
Save pweldon/82c05505543ea194e9c704980389c3bf to your computer and use it in GitHub Desktop.
zfs nixos bootstrap
#!/usr/bin/env bash
# credit: https://github.com/a-schaefers/themelios
set -euxo pipefail
die() {
[ $# -gt 0 ] && printf -- "%s\n" "$*"
exit 1
}
uefi_or_legacy() {
[[ -d "/sys/firmware/efi/efivars" ]] && uefi_install="1"
}
disk_prep() {
# some initial translation for whether or not the script was provided disks with sd* or /dev/disk/by-id/*, etc.
echo "${zfs_pool_disks[0]}" | grep -q "/dev/sd" && use_sdX="1"
echo "${zfs_pool_disks[0]}" | grep -q "/dev/nvme" && use_nvme="1"
if [[ $use_sdX ]]
then
boot_part="2"
slog_partition="3"
zpool_partition="4"
elif [[ $use_nvme ]]
then
boot_part="p2"
slog_partition="p3"
zpool_partition="p4"
else
boot_part="-part2"
slog_partition="-part3"
zpool_partition="-part4"
fi
sgdisk_clear() {
for disk_id in "${zfs_pool_disks[@]}"
do
echo "clearing disk with sgdisk..."
sgdisk --zap-all "$disk_id" || die "sgdisk_clear failed"
done
}
[[ $use_sgdisk_clear == "true" ]] && sgdisk_clear
wipefs_all() {
for disk_id in "${zfs_pool_disks[@]}"
do
echo "wiping disk signatures with wipefs..."
wipefs -fa "$disk_id" || die "wipefs_all failed"
done
}
[[ $use_wipefs_all == "true" ]] && wipefs_all
dd_zero() {
for disk_id in "${zfs_pool_disks[@]}"
do
echo "writing zeros to ${disk_id}..."
dd if=/dev/zero of="$disk_id" bs=1M oflag=direct status=progress &
done
wait
}
[[ $use_zero_disks == "true" ]] && dd_zero
}
zpool_create() {
echo "creating zpool..."
zpool create -f \
-O compression=lz4 \
-O atime=on \
-O relatime=on \
-O normalization=formD \
-O xattr=sa \
-O acltype=posixacl \
-m none \
-R /mnt \
$zfs_pool_name \
$zfs_pool_type \
${zfs_pool_disks[@]/%/$zpool_partition} || die "zpool_create failed"
}
disk_part() {
for disk_id in "${zfs_pool_disks[@]}"
do
sgdisk -og "$disk_id"
echo "making bios boot partition..."
sgdisk -a 1 -n 1:48:2047 -t 1:ef02 -c 0:"BIOS Boot" "$disk_id" || die "disk_part failed"
partx -u "$disk_id"
echo "making 1G /boot fat32 ESP partition..."
sgdisk -n 2:0:1G -c 0:"Fat32 ESP Boot" -t 0:ef00 "$disk_id" || die "disk_part failed"
partx -u "$disk_id"
echo "making SLOG partition..."
sgdisk -n 3:0:4G -c 0:"SLOG" -t 0:ef00 "$disk_id" || die "disk_part failed"
partx -u "$disk_id"
echo "making zpool partition..."
sgdisk -n 4:0:0 -c 0:"ZPOOL" -t 0:8300 "$disk_id" || die "disk_part failed"
sgdisk -p "$disk_id" || die "disk_part failed"
partx -u "$disk_id"
sleep 5
done
sleep 10
}
mount_boots() {
sleep 10
declare -i bootnum=0
for disk_id in "${zfs_pool_disks[@]}"
do
mkfs.vfat -F32 "${disk_id}${boot_part}" || die "mount_boots mkfs.vfat failed"
mkdir -p "/mnt/boot${bootnum}"
mount "${disk_id}${boot_part}" "/mnt/boot${bootnum}"
((bootnum++))
done
}
mount_create_datasets() {
echo "Creating and mounting datasets in /mnt..."
# / (root) datasets
zfs create -o mountpoint=none -o canmount=off -o encryption=aes-128-gcm -o keyformat=passphrase "$zfs_pool_name/ROOT"
zfs create -o mountpoint=legacy -o canmount=on "$zfs_pool_name/ROOT/nixos"
mount -t zfs "$zfs_pool_name/ROOT/nixos" /mnt
zpool set bootfs="$zfs_pool_name/ROOT/nixos" "$zfs_pool_name"
# 1G fat32 /boot(X) ESP
mount_boots
# mount /nix outside of the root dataset
zfs create -o mountpoint=legacy -o canmount=on "$zfs_pool_name/nix"
mkdir /mnt/nix
mount -t zfs "$zfs_pool_name/nix" /mnt/nix
mkdir -p /mnt/{home,tmp}
# /home datasets
zfs create -o mountpoint=legacy -o canmount=on "$zfs_pool_name/ROOT/home"
mount -t zfs "$zfs_pool_name/ROOT/home" /mnt/home
# /tmp datasets
zfs create -o mountpoint=legacy -o canmount=on -o sync=disabled "$zfs_pool_name/tmp"
mount -t zfs "$zfs_pool_name/tmp" /mnt/tmp
chmod 1777 /mnt/tmp
# Reservation
zfs create -o refreservation=1G -o mountpoint=none "$zfs_pool_name/reserved"
zfs_auto_snapshot() {
for dataset in "${zfs_auto_snapshot[@]}"
do
echo "Setting property com.sun:auto-snapshot=true to ${dataset}..."
zfs set com.sun:auto-snapshot=true "$dataset"
done
}
zfs_auto_snapshot
}
bootstrap_nixcfg() {
# this is for generating our ./hardware-configuration.nix files.
# ./configuration.nix will be overwritten shortly hereafter.
echo "executing nixos-generate-config --root /mnt"
nixos-generate-config --root /mnt || die "nixos-generate-config --root /mnt failed"
echo "generating random hostid..."
zfs_host_id="$(head -c4 /dev/urandom | od -A none -t x4 | cut -d ' ' -f 2)"
echo "$zfs_host_id"
cp /mnt/etc/nixos/configuration.nix{,.orig}
# create /mnt/etc/nixos/configuration.nix and import user's top_level_nixfile.
cat << EOF > /mnt/etc/nixos/configuration.nix
{ ... }:
{
imports = [
./hardware-configuration.nix
./zfs-configuration.nix
];
services.xserver.desktopManager.plasma5.enable = true;
services.xserver.displayManager.sddm.enable = true;
services.xserver.xkbVariant = "dvorak";
console.useXkbConfig = true;
nixpkgs.config.allowUnfree = true;
programs.bash.enable = true;
users.extraUsers.peter = {
isNormalUser = true;
home = "/home/peter";
extragroups = [ "wheel" "networkmanager" ];
hashedPassword = "$6$yQwAqlTX$o5i1KMWOUlymUzkZ451aC1JjIsudcj54WPSNERGiHJVoE3aFqocTLabWMUWTXj7u8pKlyKEJdQaB.trxr3ppd.";
}
}
EOF
# create /mnt/etc/nixos/zfs-configuration.nix
cat << EOF > /mnt/etc/nixos/zfs-configuration.nix
{ ... }:
{ imports = [];
boot.supportedFilesystems = [ "zfs" ];
boot.loader = {
$(if [[ $uefi_install ]]; then # reinstall in legacy mode, it's better. :P
cat <<- UEFI
efi = {
canTouchEfiVariables = true;
efiSysMountPoint = "/boot0"; # use the same mount point here.
};
UEFI
fi)
grub = {
enable = true;
version = 2;
copyKernels = true;
$(if [[ $uefi_install ]]; then
cat <<- UEFI
efiSupport = true;
UEFI
fi)
$(if [ ${#zfs_pool_disks[@]} -gt 1 ]
then
cat <<- MIRROREDBOOTS
mirroredBoots = [
$(declare -i bootnum=0
for disk_id in "${zfs_pool_disks[@]}"
do
echo "{devices = [ \"${disk_id}\" ]; path = \"/boot${bootnum}\";}"
((bootnum++))
done)
];
MIRROREDBOOTS
else
cat <<- SINGLEBOOT
devices = [
$(for disk_id in "${zfs_pool_disks[@]}"
do
echo "\"$disk_id\""
done)
];
SINGLEBOOT
fi)
};
};
# The 32-bit host id of the machine, formatted as 8 hexadecimal characters.
# You should try to make this id unique among your machines.
networking.hostId = "$zfs_host_id";
# noop, the recommended elevator with zfs.
# shell_on_fail allows to force import manually in the case of zfs import failure.
boot.kernelParams = [ "elevator=noop" "boot.shell_on_fail" ];
# Uncomment [on a working system] to ensure extra safeguards are active that zfs uses to protect zfs pools:
boot.zfs.forceImportAll = false;
boot.zfs.forceImportRoot = false;
boot.zfs.requestEncryptionCredentials = true;
)
$([[ $nix_zfs_configuration_extra_enabled == "true" ]] && cat <<- EXTRA
# Enables periodic scrubbing of ZFS pools.
services.zfs.autoScrub.enable = $nix_zfs_extra_auto_scrub;
# Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service.
services.zfs.autoSnapshot = {
enable = $nix_zfs_extra_auto_snapshot_enabled;
frequent = $nix_zfs_extra_auto_snapshot_frequent;
hourly = $nix_zfs_extra_auto_snapshot_hourly;
daily = $nix_zfs_extra_auto_snapshot_daily;
weekly = $nix_zfs_extra_auto_snapshot_weekly;
monthly = $nix_zfs_extra_auto_snapshot_monthly;
};
# Use gc.automatic to keep disk space under control.
nix.autoOptimiseStore = $nix_zfs_extra_auto_optimise_store;
nix.gc.automatic = $nix_zfs_extra_gc_automatic;
nix.gc.dates = "$nix_zfs_extra_gc_dates";
nix.gc.options = "$nix_zfs_extra_gc_options";
# Clean /tmp automatically on boot.
boot.cleanTmpDir = $nix_zfs_extra_clean_tmp_dir;
EXTRA
)
}
EOF
# give user a retry option with --show-trace to have a chance to fix imports on another tty. :)
nixos-install-show-trace() {
nixos-install $install_arguments --show-trace || nixos-install_fail_retry
}
nixos-install_fail_retry() {
echo "themelios hint: check /mnt/etc/nixos/configuration.nix and your other files in /mnt/nix-config before trying again."
echo "themelios hint: make sure you are using relative path imports for all of your .nix files."
read -p "nixos-install failed, retry? will add --show-trace (y or n) " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]
then
nixos-install-show-trace
else
die "the only steps remaining after nixos-install should be unmounting /mnt and exporting the pool :) good luck."
fi
}
install() {
echo "executing nixos-install"
nixos-install $install_arguments || nixos-install_fail_retry
}
# install
}
installation_complete() {
echo "unmounting /mnt"
umount -lR /mnt
echo "exporting $zfs_pool_name"
zpool export "$zfs_pool_name"
read -p "finished. reboot now? (y or n) " -n 1 -r
[[ $REPLY =~ ^[Yy]$ ]] && reboot
}
# start executing code !
use_sgdisk_clear="true" # use sgdisk --clear
use_wipefs_all="true" # use wipefs --all
use_zero_disks="false" # use dd if=/dev/zero ...
install_arguments="" # any extra arguments to nixos-install, like --no-root-passwd
zfs_pool_name="nixpool"
zfs_pool_disks=("/dev/disk/by-id/ata-Crucial_CT525MX300SSD4_173818E39F49" "/dev/disk/by-id/ata-Crucial_CT525MX300SSD4_173818E39F8C") # Note: using /dev/disk/by-id is also preferable.
zfs_pool_type="mirror" # use "" for single, or "mirror", "raidz1", etc.
zfs_auto_snapshot=("$zfs_pool_name/ROOT") # datasets to be set with com.sun:auto-snapshot=true
nix_zfs_configuration_extra_enabled="false" # uncomment below if set to true
nix_zfs_extra_auto_scrub="true"
nix_zfs_extra_auto_snapshot_enabled="true" # Enable the ZFS auto-snapshotting service
nix_zfs_extra_auto_snapshot_frequent="8"
nix_zfs_extra_auto_snapshot_hourly="24"
nix_zfs_extra_auto_snapshot_daily="0"
nix_zfs_extra_auto_snapshot_weekly="0"
nix_zfs_extra_auto_snapshot_monthly="0"
nix_zfs_extra_auto_optimise_store="true"
nix_zfs_extra_gc_automatic="true"
nix_zfs_extra_gc_dates="weekly"
nix_zfs_extra_gc_options="--delete-older-than 7d"
nix_zfs_extra_clean_tmp_dir="true"
uefi_or_legacy
disk_prep
disk_part
zpool_create
mount_create_datasets
bootstrap_nixcfg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment