Hi,
I would like to automate the initial install of NixOS on multiple servers by using a set of custom bash scripts and package them to build a custom ISO installer.
The goal of this initial install is to configure networking with networkd, bonding and static IP addresses in order to be able to deploy full server configurations with NixOps.
So far, I managed to package this bash script:
#!/usr/bin/env bash
#
# Install NixOS on a server
function usage() {
cat << EOF
Install NixOS on a server.
Usage:
$(basename $0) -d|--disk <path> -s|--swap <size>
$(basename $0) -h|--help
Options:
-d, --disk <path> Disk path on which to install NixOS (e.g.: /dev/disk/by-path/pci-0000:03:00.0-nvme-1).
-s, --swap <size> Size of the swap (e.g.: 16G).
-h, --help Display this help message.
EOF
}
function err() {
echo "$@" >&2
}
function usage_err() {
err "Try '$(basename $0) --help' for more information."
}
function wait_until_exists() {
local FILE="$1"
echo -n "Wait for file $FILE to exist..."
while [ ! -e "${FILE}" ]; do
echo -n "."
sleep 0.1
done
echo " done"
}
function main() {
local OPTIONS=
OPTIONS=$(getopt \
--name 'ff-install' \
--options hd:s: \
--longoptions help,disk:,swap: \
-- "$@")
if [ $? != 0 ] ; then
usage_err
exit 1
fi
set -e
eval set -- "$OPTIONS"
local DISK_PATH=
local SWAP_SIZE=
while true; do
case "$1" in
-d | --disk )
DISK_PATH="$2"
shift 2
;;
-s | --swap )
SWAP_SIZE="$2"
shift 2
;;
-h | --help )
usage
exit 0
;;
-- )
shift
break
;;
* )
break
;;
esac
done
if [[ ! "${DISK_PATH}" =~ ^/dev/disk/by-(id|path)/.+$ ]] ; then
err "Disk path must start with /dev/disk/by-id/ or /dev/disk/by-path/."
usage_err
exit 1
fi
if [ ! -e "${DISK_PATH}" ] ; then
err "Disk path '${DISK_PATH}' does not exit."
exit 1
fi
if [ -z "${SWAP_SIZE}" ] ; then
err "Swap size cannot be empty."
usage_err
exit 1
fi
# Partition disk
echo "Partition disk ${DISK_PATH} …"
# BIOS boot partition (EF02)
sgdisk --set-alignment 1 --new 1:34:2047 --typecode 1:EF02 "${DISK_PATH}"
# EFI system partition (EF00)
sgdisk --new 2:1M:+512M --typecode 2:EF00 "${DISK_PATH}"
# Main partition (BF01)
sgdisk --new 3:0:0 --typecode 3:BF01 "${DISK_PATH}"
local EFI_PART="${DISK_PATH}-part2"
local MAIN_PART="${DISK_PATH}-part3"
wait_until_exists "${MAIN_PART}"
# Create ZFS pool
echo "Create ZFS pool …"
zpool create \
-O mountpoint=none \
-O compression=lz4 \
-O xattr=sa \
-O acltype=posixacl \
-R /mnt \
rpool \
"${MAIN_PART}"
# rpool/swap dataset
echo "Create ZFS swap dataset …"
zfs create \
-V "${SWAP_SIZE}" \
-b $(getconf PAGESIZE) \
-o compression=zle \
-o logbias=throughput \
-o sync=always \
-o primarycache=metadata \
-o secondarycache=none \
-o com.sun:auto-snapshot=false \
rpool/swap
wait_until_exists /dev/zvol/rpool/swap
mkswap \
-L swap \
/dev/zvol/rpool/swap
swapon /dev/zvol/rpool/swap
# Create ZFS datasets
echo "Create ZFS other datasets …"
# rpool/root dataset
zfs create \
-o mountpoint=legacy \
-o com.sun:auto-snapshot=true \
rpool/root
mount -t zfs rpool/root /mnt
# rpool/nix dataset
zfs create \
-o mountpoint=legacy \
-o com.sun:auto-snapshot=false \
rpool/nix
mkdir /mnt/nix
mount -t zfs rpool/nix /mnt/nix
# rpool/home dataset
zfs create \
-o mountpoint=legacy \
-o com.sun:auto-snapshot=true \
rpool/home
mkdir /mnt/home
mount -t zfs rpool/home /mnt/home
# Create boot partition
echo "Create boot partition …"
mkfs.fat \
-F 32 \
-n boot \
"${EFI_PART}"
mkdir /mnt/boot
mount -t vfat "${EFI_PART}" /mnt/boot
# Generate NixOS configuration files
echo "Generate NixOS configuration …"
nixos-generate-config --root /mnt
# Modify NixOS configuration files
hostId=`head -c4 /dev/urandom | od -t x4 -A none | tr -d " "`
sed -i "s/<HOST-ID>/${hostId}/g" /mnt/etc/nixos/configuration.nix
hostSuffix=`head /dev/urandom | tr -dc A-Z0-9 | head -c4`
hostName="ff-server-${hostSuffix}"
sed -i "s/<HOST-NAME>/${hostName}/g" /mnt/etc/nixos/configuration.nix
# Install NixOS
echo "Install NixOS …"
nixos-install
# Umount installer
echo "Umount installer …"
umount /mnt/{nix,home,boot}
umount /mnt
swapoff -a
# End
echo "NixOS is installed (hostname ${hostName})."
echo 'Enter `reboot` to boot on the newly installed NixOS.'
}
main "$@"
by using this NIX expression:
{ stdenv, fetchurl }:
stdenv.mkDerivation rec {
name = "ff-install-unstable-2020-03-17";
version = "0.1.0-dev";
src = ./ff-install.sh;
phases = "installPhase fixupPhase";
installPhase = ''
mkdir -p $out/bin
cp ${src} $out/bin/ff-install
chmod +x $out/bin/ff-install
'';
}
You may have noticed than the scripts modify the content of the generated configuration.nix
with the help of placeholders. Indeed, I am using a custom default template by setting the NixOS option system.nixos-generate-config.configuration
.
The script is starting to do too many things and I would like to split it into several individual ones:
-
ff-partition-disk
: create partitions -
ff-init-filesystem
: create filesystems on partitions -
ff-generate-config
: generate the config and modify it -
ff-prepare-install
: wrap call to the scripts above -
ff-install
: install NixOS after letting the user to manually edit the configuration files
I do not fully understand how stdenv.mkDerivation
and I was thinking to build a single derivation with all the install scripts into it. Would you recommend this? If yes, how can I package multiple scripts in a single derivation?
In the same spirit, I would also like to write some templates of nixos modules that my ff-generate-config
script would include in the main configuration file. So I would need to package these template files in the derivation.
After 2 days of research, I cannot find a way to do that (I am still learning nixpkgs). Is that even a good idea in your opinion?
Thank you