#!/bin/sh # Copyright © 2023 Giovanni Biscuolo # # bootstrap-guix.sh is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # bootstrap-guix.sh is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # A copy of the GNU General Public License is available at # . # bootstrap-guix.sh is a very opinionated script to install Guix # System on a host booted in "rescue" mode. # # The system is installed on a single disk BTRFS filesystem on a LUKS # encrypted partition. # --------------------------------------------------------------------- # Variables # Disks # TODO: transform this in array TARGET_DISKS[TARGET_NUMDISKS], for multi disk setups export TARGET_NUMDISKS=1 export TARGET_DISK_PART_SUFFIX="" export TARGET_DISK1="/dev/sda" export TARGET_SWAP_SIZE="16GB" # Hostname export TARGET_HOSTNAME="pioche" # User and pub key (only one admin user for basic installation) export TARGET_USERNAME="g" export TARGET_USERGECOS="Giovanni Biscuolo" export TARGET_USERKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICqpr0unFxPo2PnQTmmO2dIUEECsCL3vVvjhk5Dx80Yb g@xelera.eu" # ########################### # DO NOT EDIT this variables # unless for debugging # (minimal) OS configuration file name export OS_CONFIG_FILE="bootstrap-config.scm" # Target OS mount point export TARGET_MOUNTPOINT="/mnt/guix" # Source os-release information test -e /etc/os-release && os_release='/etc/os-release' || os_release='/usr/lib/os-release' . "${os_release}" echo "### INFO - Detected GNU/Linux distribution: ${PRETTY_NAME}." # --------------------------------------------------------------------- # Prepare the target system filesystem # Wipe the disks # TODO: use the array TARGET_DISKS[] echo "### START - Wiping disks." wipefs -af ${TARGET_DISK1}* echo "### STOP - Wiping disks." # Partition the disks # FIXME: detect if on EFI platform looking at /sys/firmware/efi and # perform disk partitioning and filesystem formatting accordingly ## Disk 1 echo "### START - partitioning ${TARGET_DISK1}." parted ${TARGET_DISK1} --align=opt -s -m -- mklabel gpt # BIOS grub system partition parted ${TARGET_DISK1} --align=opt -s -m -- \ mkpart grub 1MiB 5MiB \ name 1 grub-1 \ set 1 bios_grub on # partition p2 will be swap parted ${TARGET_DISK1} --align=opt -s -m -- \ mkpart linux-swap 5MiB ${TARGET_SWAP_SIZE} \ name 2 swap-1 # partition p3 will be LUKS encrypted device parted ${TARGET_DISK1} --align=opt -s -m -- \ mkpart ext4 ${TARGET_SWAP_SIZE} 100% \ name 3 luks-1 echo "### END - partitioning ${TARGET_DISK1}." # Create LUKS device on encrypted partition, backup LUKS header and open it echo "### START - Making encrypted ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3." # FIXME: LUKS2 non supported? # cryptsetup luksFormat --type luks2 --pbkdf pbkdf2 ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3 cryptsetup luksFormat --type luks1 ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3 cryptsetup luksHeaderBackup --header-backup-file `basename ${TARGET_DISK1}3`.luksHeader ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3 echo "### END - Making encrypted ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3." # Opening encrypted device, ready to be formatted echo "### START - Opening encrypted ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3." cryptsetup open ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3 cryptroot echo "### END - Opening encrypted ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3." # Make swap on p2 partitions and turn them on echo "### START - Making swap." mkswap ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}2 swapon ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}2 echo "### END - Making swap." # Create BTRFS filesystem echo "### START - Making BTRFS flesystem and subvolumes." mkfs.btrfs -f /dev/mapper/cryptroot # Mount the target Guix System root mkdir -p ${TARGET_MOUNTPOINT} mount -o compress=zstd /dev/mapper/cryptroot ${TARGET_MOUNTPOINT} # Create subvolumes for target system btrfs subvolume create ${TARGET_MOUNTPOINT}/var btrfs subvolume create ${TARGET_MOUNTPOINT}/home btrfs subvolume create ${TARGET_MOUNTPOINT}/srv btrfs subvolume create ${TARGET_MOUNTPOINT}/root btrfs subvolume create ${TARGET_MOUNTPOINT}/gnu echo "### END - Making BTRFS flesystem and subvolumes." # --------------------------------------------------------------------- # Prepare basic OS configuration cat > ${OS_CONFIG_FILE} << EOF ;; Very basic Guix System (use-modules (gnu)) (use-service-modules admin networking ssh linux) ;; Definitions (define (sysadmin name full-name) (user-account (name name) (comment full-name) (group "users") (supplementary-groups '("wheel" "kvm")) (home-directory (string-append "/home/" name)))) (define %accounts (list (sysadmin "${TARGET_USERNAME}" "${TARGET_USERGECOS}"))) ;; operating-system (operating-system (locale "en_US.utf8") (timezone "Europe/Rome") (keyboard-layout (keyboard-layout "it" "winkeys")) (host-name "${TARGET_HOSTNAME}") (bootloader (bootloader-configuration (bootloader grub-bootloader) (targets (list "${TARGET_DISK1}")) (keyboard-layout keyboard-layout))) (mapped-devices (list (mapped-device (source (uuid "`blkid -o value -s UUID ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3`")) (target "cryptroot") (type luks-device-mapping)))) (file-systems (append (list (file-system (mount-point "/") (device "/dev/mapper/cryptroot") (type "btrfs") (options "compress=zstd") (dependencies mapped-devices))) %base-file-systems)) (swap-devices (list (swap-space (target (uuid "`blkid -o value -s UUID ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}2`"))))) (issue ;; Default contents for /etc/issue. "\\ This a Guix system. Welcome.\n") (users (append %accounts %base-user-accounts)) (sudoers-file (plain-file "sudoers" "\\ root ALL=(ALL) ALL %wheel ALL=(ALL) ALL\n")) ;; Globally-installed packages. (packages (append (list (specification->package "st") (specification->package "nss-certs")) %base-packages)) (services (append %base-services (list (service dhcp-client-service-type) (service unattended-upgrade-service-type) (service openssh-service-type (openssh-configuration (port-number 22) (password-authentication? #f) (permit-root-login 'prohibit-password) (extra-content "ListenAddress 0.0.0.0") (authorized-keys \`(("${TARGET_USERNAME}" ,(plain-file "${TARGET_USERNAME}.pub" "${TARGET_USERKEY}")) ("root" ,(plain-file "${TARGET_USERNAME}.pub" "${TARGET_USERKEY}")))))))))) EOF # --------------------------------------------------------------------- # Mount the /gnu store copy-on-write using ${TARGET_MOUNTPOINT} echo "### START - Mounting cow-store" if [ "${ID:-linux}" = "guix" ]; then herd start cow-store ${TARGET_MOUNTPOINT} else # Make the store copy-on-write, using TARGET as the backing store. # This is useful when TARGET is on a hard disk, whereas the current # store is on a RAM disk. Ported from mount-cow-store in # gnu/build/install.scm, used by "herd start cow-store". mkdir -p /gnu/store # TMPDIR=${TARGET_MOUNTPOINT}/tmp # mkdir -p $TMPDIR # mount -o bind $TMPDIR /tmp RWDIR=${TARGET_MOUNTPOINT}/tmp/guix-inst WORKDIR=${RWDIR}/../.overlayfs-workdir mkdir -p ${RWDIR} mkdir -p ${WORKDIR} chmod 775 ${RWDIR} mount -t overlay -o lowerdir=/gnu/store,upperdir=${RWDIR},workdir=${WORKDIR} overlay /gnu/store systemctl daemon-reload fi echo "### END - Mounting cow-store" # Collect some partitioning and mount points info mount > bootstrap-mount-points.txt lsblk -f ${TARGET_DISK1} -J > bootstrap-lsblk-`basename ${TARGET_DISK1}`.json # --------------------------------------------------------------------- # Install GNU Guix if needed if [ "${ID:-linux}" = "guix" ]; then echo "### INFO - No need to install the guix binary." else # --------------------------------------------------------------------- # Install guix using binary installation echo "### START - Installing guix binary." wget https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh chmod +x guix-install.sh ./guix-install.sh hash guix echo "### END - Installing guix binary." fi # Update guix, needed for grub LUKS2 support??? # echo "### START - Updating Guix." # guix pull # hash guix # echo "### STOP - Updating Guix." guix describe > bootstrap-guix-version.txt # --------------------------------------------------------------------- # Install Guix on target filesystem echo "### START - Installing Guix on ${TARGET_MOUNTPOINT}" mkdir ${TARGET_MOUNTPOINT}/etc cp ${OS_CONFIG_FILE} ${TARGET_MOUNTPOINT}/etc/config.scm echo guix system init ${TARGET_MOUNTPOINT}/etc/config.scm ${TARGET_MOUNTPOINT} echo "### END - Installing Guix on ${TARGET_MOUNTPOINT}" # FIXME: umount cow-store and delete tmp files # (define (unmount-cow-store target backing-directory) # "Unmount copy-on-write store." # (let ((tmp-dir "/remove")) # (mkdir-p tmp-dir) # (mount (%store-directory) tmp-dir "" MS_MOVE) # ;; We might get EBUSY at this point, possibly because of lingering # ;; processes with open file descriptors. Use 'umount*' to retry upon # ;; EBUSY, leaving a bit of time. See . # (umount* tmp-dir) # (rmdir tmp-dir) # (delete-file-recursively # (string-append target backing-directory)))) # # -------------------------------------------------------------------- # # Unmount and close encrypted partition, swapoff # umount /gnu # umount ${TARGET_MOUNTPOINT} # cryptsetup close --type luks2 `basename ${TARGET_DISK1}3`_luks # swapoff ${TARGET_DISK1}2 # --------------------------------------------------------------------- # End game echo "" echo "### DONE! - Target system in ${TARGET_MOUNTPOINT} is ready..." echo "" echo "Please remember to copy ${OS_CONFIG_FILE} to a safe remote location" echo "" echo "...and reboot to start your new Guix System! Bye."