#!/bin/bash # 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 # Used variables MUST be initialized. set -o nounset # --------------------------------------------------------------------- # 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="6GB" # Hostname export TARGET_HOSTNAME="cornouiller" # User and pub key (only one admin user for basic installation) export TARGET_USERNAME="g" export TARGET_USERGECOS="Giovanni Biscuolo" TARGET_USERKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICqpr0unFxPo2PnQTmmO2dIUEECsCL3vVvjhk5Dx80Yb g@xelera.eu" # -------------------------------------------------------------------- # 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}." # --------------------------------------------------------------------- # Get package dependencies export AUTO_INSTALLED=0 if [ $AUTO_INSTALLED -eq 0 ]; then # Install dependencies with Guix, if available if command -v guix &> /dev/null; then echo "### INFO - Installing dependencies via guix..." guix install util-linux parted dosfstools btrfs-progs gnupg export AUTO_INSTALLED=1 echo "### END - Installing dependencies via guix." fi fi if [ $AUTO_INSTALLED -eq 0 ]; then # Install dependencies with apt, if available if command -v apt &> /dev/null; then echo "### INFO - Installing dependencies via apt..." apt install util-linux parted dosfstools btrfs-progs gnupg export AUTO_INSTALLED=1 echo "### END - Installing dependencies via apt." fi fi # Give up installing and notify users if [ $AUTO_INSTALLED -eq 0 ] ; then echo "### INFO - I'm not able to automatically install dependencies on ${PRETTY_NAME}." echo "Please check you have the following commands available: wipefs, parted, mkfs.fat, mkswap, mkfs.btrfs, btrfs, gpg." echo "### END - Checking dependencies." fi # Abort on any error, from now set -e # ########################### # 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" # --------------------------------------------------------------------- # Prepare the target system filesystem echo "### START - Downloading guix install script." wget https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh chmod +x guix-install.sh echo "### END - Downloading guix install script." # --------------------------------------------------------------------- # Prepare the target system filesystem # Wipe the disks # TODO: use the array TARGET_DISKS[] echo "### START - Wiping disks." wipefs -af ${TARGET_DISK1}* echo "### END - Wiping disks." # Partition the disks echo "### START - EFI system check." if [ -e "/sys/firmware/efi/efivars" ]; then IS_EFI=true echo "System is EFI based. ($IS_EFI)" else IS_EFI=false echo "System is BIOS grub based. ($IS_EFI)" fi echo "### END - EFI system check." ## Disk 1 echo "### START - partitioning ${TARGET_DISK1}." parted ${TARGET_DISK1} --align=opt -s -m -- mklabel gpt # partition p1 will be system boot if $IS_EFI; then # EFI system partition BOOT_SIZE="501MiB" parted ${TARGET_DISK1} --align=opt -s -m -- \ mkpart primary fat32 1MiB $BOOT_SIZE \ name 1 EFI-system \ set 1 esp on else # BIOS grub system partition BOOT_SIZE="5MiB" parted ${TARGET_DISK1} --align=opt -s -m -- \ mkpart grub 1MiB $BOOT_SIZE \ name 1 grub-1 \ set 1 bios_grub on fi # partition p2 will be swap parted ${TARGET_DISK1} --align=opt -s -m -- \ mkpart primary linux-swap $BOOT_SIZE ${TARGET_SWAP_SIZE} \ name 2 swap-1 # partition p3 will be BTRFS device parted ${TARGET_DISK1} --align=opt -s -m -- \ mkpart primary btrfs ${TARGET_SWAP_SIZE} 100% \ name 3 BTRFS partprobe ${TARGET_DISK1} sleep 5s # Needed sometimes to allow new partition access echo "### END - partitioning ${TARGET_DISK1}." # Create FAT32 filesystem on p1 if it's EFI system if $IS_EFI; then echo "### START - Making EFI filesystem" mkfs.fat -F32 ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}1 fatlabel ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}1 "EFI-system" echo "### END - Making EFI filesystem" fi # Make swap on p2 partitions and turn them on echo "### START - Making swap." mkswap ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}2 # swaplabel -L "swap-1" ${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 ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3 # btrfs filesystem label ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3 "root" # Mount the target Guix System root mkdir -p ${TARGET_MOUNTPOINT} mount -o compress=zstd ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3 ${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." # Mount the ESP on /boot/efi if $IS_EFI; then echo "### START - Mounting EFI filesystem" mkdir -p ${TARGET_MOUNTPOINT}/boot/efi mount ${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}1 ${TARGET_MOUNTPOINT}/boot/efi echo "### END - Mounting EFI filesystem" fi # --------------------------------------------------------------------- # Prepare basic OS configuration echo "### START - Creating basic Guix system config in ${OS_CONFIG_FILE}." if $IS_EFI; then BOOTLOADER_SCM=" (bootloader (bootloader-configuration (bootloader grub-efi-bootloader) (targets (list \"/boot/efi\")) (keyboard-layout keyboard-layout)))" else BOOTLOADER_SCM=" (bootloader (bootloader-configuration (bootloader grub-bootloader) (targets (list \"${TARGET_DISK1}\")) (keyboard-layout keyboard-layout)))" fi EFI_FS_SCM="" if $IS_EFI; then EFI_FS_SCM=" (file-system (mount-point "/boot/efi") (device (file-system-label "EFI-system" 'fat)) (type "vfat"))" fi # Heredoc with scheme OS configuration cat > ${OS_CONFIG_FILE} << EO_CONFIG ;; 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_SCM} (file-systems (append (list (file-system (mount-point "/") (device "${TARGET_DISK1}${TARGET_DISK_PART_SUFFIX}3") (type "btrfs") (options "compress=zstd")) ${EFI_FS_SCM}) %base-file-systems)) (swap-devices (list (swap-space (target "${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 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}")))))))))) EO_CONFIG echo "### END - Creating basic Guix system config." # --------------------------------------------------------------------- # 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 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 # FIXME: is this needed?!? 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." ./guix-install.sh hash guix echo "### END - Installing guix binary." fi guix describe > bootstrap-guix-version.txt # --------------------------------------------------------------------- # Install Guix on target filesystem echo "### START - Installing Guix on ${TARGET_MOUNTPOINT}" mkdir ${TARGET_MOUNTPOINT}/etc/ cp bootstrap-mount-points.txt ${TARGET_MOUNTPOINT}/etc/ cp bootstrap-lsblk-*.json ${TARGET_MOUNTPOINT}/etc/ cp bootstrap-guix-version.txt ${TARGET_MOUNTPOINT}/etc/ cp ${OS_CONFIG_FILE} ${TARGET_MOUNTPOINT}/etc/config.scm 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 # --------------------------------------------------------------------- # 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."