Guix supports the concept of channels which basically amount to Git repositories which contain Guix package definitions that can be installed on your machine. Aside from the %default-channels
list, I also use the Nonguix channel to install packages that aren’t included with Guix by default like the non-free Linux kernel.
I also generate my real .config/guix/channels.scm
file based on this base-channels.scm
file. This allows me to keep a pinned channel list in the real channels file while maintaining a canonical channel listing that I generate it from using the update-channels
script (defined later in this file).
.config/guix/base-channels.scm:
;; NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
(list (channel
(name 'channel-x)
(url "file:///home/daviwil/Projects/Code/channel-x"))
(channel
(name 'flat)
(url "https://github.com/flatwhatson/guix-channel.git")
(commit
"7b8353ebbcf486e3344924d1cac0fa7ba47c371d")
(introduction
(make-channel-introduction
"33f86a4b48205c0dc19d7c036c85393f0766f806"
(openpgp-fingerprint
"736A C00E 1254 378B A982 7AF6 9DBE 8265 81B6 4490"))))
(channel
(name 'rde)
(url "https://git.sr.ht/~abcdw/rde")
(introduction
(make-channel-introduction
"257cebd587b66e4d865b3537a9a88cccd7107c95"
(openpgp-fingerprint
"2841 9AC6 5038 7440 C7E9 2FFA 2208 D209 58C1 DEB0"))))
(channel
(name 'nonguix)
(url "https://gitlab.com/nonguix/nonguix"))
(channel
(name 'guix)
(url "https://git.savannah.gnu.org/git/guix.git")
;; (url "file:///home/daviwil/Projects/Code/guix"))
(introduction
(make-channel-introduction
"9edb3f66fd807b096b48283debdcddccfea34bad"
(openpgp-fingerprint
"BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))))
The following channel list can be used when testing patches to packages and services from a local clone of the Guix repo. You’ll have to create a branch and commit changes to it before guix pull
can pick them up, though. You can change the target branch using the branch
field of the channel
.
;; (list (channel
;; (name 'nonguix)
;; (url "https://gitlab.com/nonguix/nonguix"))
;; (channel
;; (name 'guix)
;; (branch "fix-glu-pkg-config")
;; (url "file:///home/daviwil/Projects/Code/guix")
;; (introduction
;; (make-channel-introduction
;; "d06d5db885e4b8399e878708862fbe3a67f0592c"
;; (openpgp-fingerprint
;; "53C4 1E6E 41AA FE55 335A CA5E 446A 2ED4 D940 BF14")))))
This base configuration is shared between all of the machines I manage with Guix. Since all of my machines are Lenovo ThinkPad laptops, the same basic configuration applies pretty cleanly across all of them. This may change in the future.
Any configuration that derives from base-operating-system
must invoke guix system
in a specific way to ensure it gets loaded correctly:
sudo -E guix system -L ~/.dotfiles/.config/guix/systems reconfigure ~/.dotfiles/.config/guix/systems/davinci.scm
.config/guix/systems/base-system.scm:
;; NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
(define-module (base-system)
#:use-module (gnu)
#:use-module (srfi srfi-1)
#:use-module (gnu system nss)
#:use-module (gnu services pm)
#:use-module (gnu services cups)
#:use-module (gnu services desktop)
#:use-module (gnu services docker)
#:use-module (gnu services networking)
#:use-module (gnu services virtualization)
#:use-module (gnu packages wm)
#:use-module (gnu packages cups)
#:use-module (gnu packages vim)
#:use-module (gnu packages gtk)
#:use-module (gnu packages xorg)
#:use-module (gnu packages emacs)
#:use-module (gnu packages file-systems)
#:use-module (gnu packages gnome)
#:use-module (gnu packages mtools)
#:use-module (gnu packages linux)
#:use-module (gnu packages audio)
#:use-module (gnu packages gnuzilla)
#:use-module (gnu packages pulseaudio)
#:use-module (gnu packages web-browsers)
#:use-module (gnu packages version-control)
#:use-module (gnu packages package-management)
#:use-module (nongnu packages linux)
#:use-module (nongnu system linux-initrd))
(use-service-modules nix)
(use-service-modules desktop xorg)
(use-package-modules certs)
(use-package-modules shells)
Add a udev
rule to enable members of the video
group to control screen brightness.
;; Allow members of the "video" group to change the screen brightness.
(define %backlight-udev-rule
(udev-rule
"90-backlight.rules"
(string-append "ACTION==\"add\", SUBSYSTEM==\"backlight\", "
"RUN+=\"/run/current-system/profile/bin/chgrp video /sys/class/backlight/%k/brightness\""
"\n"
"ACTION==\"add\", SUBSYSTEM==\"backlight\", "
"RUN+=\"/run/current-system/profile/bin/chmod g+w /sys/class/backlight/%k/brightness\"")))
Override the default %desktop-services
to add the udev
backlight configuration and include OpenVPN in the list of NetworkManager plugins.
(define %my-desktop-services
(modify-services %desktop-services
(elogind-service-type config =>
(elogind-configuration (inherit config)
(handle-lid-switch-external-power 'suspend)))
(udev-service-type config =>
(udev-configuration (inherit config)
(rules (cons %backlight-udev-rule
(udev-configuration-rules config)))))
(network-manager-service-type config =>
(network-manager-configuration (inherit config)
(vpn-plugins (list network-manager-openvpn))))))
Use the libinput
driver for all input devices since it’s a bit more modern than the default.
(define %xorg-libinput-config
"Section \"InputClass\"
Identifier \"Touchpads\"
Driver \"libinput\"
MatchDevicePath \"/dev/input/event*\"
MatchIsTouchpad \"on\"
Option \"Tapping\" \"on\"
Option \"TappingDrag\" \"on\"
Option \"DisableWhileTyping\" \"on\"
Option \"MiddleEmulation\" \"on\"
Option \"ScrollMethod\" \"twofinger\"
EndSection
Section \"InputClass\"
Identifier \"Keyboards\"
Driver \"libinput\"
MatchDevicePath \"/dev/input/event*\"
MatchIsKeyboard \"on\"
EndSection
")
Define the base-operating-system
which will be inherited by all machine configurations.
(define-public base-operating-system
(operating-system
(host-name "hackstock")
;(timezone "America/Los_Angeles")
(timezone "Europe/Athens")
(locale "en_US.utf8")
;; Use non-free Linux and firmware
(kernel linux)
(firmware (list linux-firmware))
(initrd microcode-initrd)
;; Choose US English keyboard layout. The "altgr-intl"
;; variant provides dead keys for accented characters.
(keyboard-layout (keyboard-layout "us" "altgr-intl" #:model "thinkpad"))
;; Use the UEFI variant of GRUB with the EFI System
;; Partition mounted on /boot/efi.
(bootloader (bootloader-configuration
(bootloader grub-efi-bootloader)
(target "/boot/efi")
(keyboard-layout keyboard-layout)))
;; Guix doesn't like it when there isn't a file-systems
;; entry, so add one that is meant to be overridden
(file-systems (cons*
(file-system
(mount-point "/tmp")
(device "none")
(type "tmpfs")
(check? #f))
%base-file-systems))
(users (cons (user-account
(name "daviwil")
(comment "David Wilson")
(group "users")
(home-directory "/home/daviwil")
(supplementary-groups '(
"wheel" ;; sudo
"netdev" ;; network devices
"kvm"
"tty"
"input"
"docker"
"realtime" ;; Enable realtime scheduling
"lp" ;; control bluetooth devices
"audio" ;; control audio devices
"video"))) ;; control video devices
%base-user-accounts))
;; Add the 'realtime' group
(groups (cons (user-group (system? #t) (name "realtime"))
%base-groups))
;; Install bare-minimum system packages
(packages (append (list
git
ntfs-3g
exfat-utils
fuse-exfat
stow
vim
emacs
xterm
bluez
bluez-alsa
pulseaudio
tlp
xf86-input-libinput
nss-certs ;; for HTTPS access
gvfs) ;; for user mounts
%base-packages))
;; Use the "desktop" services, which include the X11 log-in service,
;; networking with NetworkManager, and more
(services (cons* (service slim-service-type
(slim-configuration
(xorg-configuration
(xorg-configuration
(keyboard-layout keyboard-layout)
(extra-config (list %xorg-libinput-config))))))
(service tlp-service-type
(tlp-configuration
(cpu-boost-on-ac? #t)
(wifi-pwr-on-bat? #t)))
(pam-limits-service ;; This enables JACK to enter realtime mode
(list
(pam-limits-entry "@realtime" 'both 'rtprio 99)
(pam-limits-entry "@realtime" 'both 'memlock 'unlimited)))
(extra-special-file "/usr/bin/env"
(file-append coreutils "/bin/env"))
(service thermald-service-type)
(service docker-service-type)
(service libvirt-service-type
(libvirt-configuration
(unix-sock-group "libvirt")
(tls-port "16555")))
(service cups-service-type
(cups-configuration
(web-interface? #t)
(extensions
(list cups-filters))))
(service nix-service-type)
(bluetooth-service #:auto-enable? #t)
(remove (lambda (service)
(eq? (service-kind service) gdm-service-type))
%my-desktop-services)))
;; Allow resolution of '.local' host names with mDNS
(name-service-switch %mdns-host-lookup-nss)))
Because I’m lame, all of my machines are named from characters, things, and places from the movie Hackers.
Some settings need to be customized on a per-system basis without tweaking individual configuration files. Thanks to org-mode’s noweb
functionality, I can define a set of variables that can be tweaked for each system and applied across these configuration files when they get generated.
I also define a function called dw/system-settings-get
which can retrieve these settings appropriately.
(require 'map) ;; Needed for map-merge
(setq dw/system-settings
(map-merge
'list
'((desktop/dpi . 180)
(desktop/background . "samuel-ferrara-uOi3lg8fGl4-unsplash.jpg")
(emacs/default-face-size . 220)
(emacs/variable-face-size . 245)
(emacs/fixed-face-size . 200)
(polybar/height . 35)
(polybar/font-0-size . 18)
(polybar/font-1-size . 14)
(polybar/font-2-size . 20)
(polybar/font-3-size . 13)
(dunst/font-size . 20)
(dunst/max-icon-size . 88)
(vimb/default-zoom . 180)
(qutebrowser/default-zoom . 200))
<<system-settings>>))
zerocool
is a 5th Generation ThinkPad X1 Carbon that I use for system testing and Guix demonstrations for System Crafters.
.config/guix/systems/zerocool.scm:
;; NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
(define-module (zerocool)
#:use-module (base-system)
#:use-module (gnu))
(operating-system
(inherit base-operating-system)
(host-name "zerocool")
(mapped-devices
(list (mapped-device
(source (uuid "039d3ff8-0f90-40bf-89d2-4b2454ada6df"))
(target "system-root")
(type luks-device-mapping))))
(file-systems (cons*
(file-system
(device (file-system-label "zerocool"))
(mount-point "/")
(type "ext4")
(dependencies mapped-devices))
(file-system
(device "/dev/nvme0n1p1")
(mount-point "/boot/efi")
(type "vfat"))
%base-file-systems)))
acidburn
is a 1st Generation ThinkPad X1 Nano that I use for most of my writing and hacking at home.
.config/guix/systems/acidburn.scm:
;; NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
(define-module (acidburn)
#:use-module (base-system)
#:use-module (gnu)
#:use-module (nongnu packages linux))
(operating-system
(inherit base-operating-system)
(host-name "acidburn")
(firmware (list linux-firmware sof-firmware))
(mapped-devices
(list (mapped-device
(source (uuid "15ece913-c423-49aa-ac42-3bad39fdd966"))
(target "system-root")
(type luks-device-mapping))))
(file-systems (cons*
(file-system
(device (file-system-label "system-root"))
(mount-point "/")
(type "ext4")
(dependencies mapped-devices))
(file-system
(device "/dev/nvme0n1p1")
(mount-point "/boot/efi")
(type "vfat"))
%base-file-systems)))
System Settings
(when (equal system-name "acidburn")
'((desktop/dpi . 180)
(emacs/default-face-size . 190)
(emacs/variable-face-size . 200)
(emacs/fixed-face-size . 190)
(polybar/height . 30)
(polybar/font-0-size . 16)
(polybar/font-1-size . 12)
(polybar/font-2-size . 18)
(polybar/font-3-size . 11)
(dunst/font-size . 20)
(dunst/max-icon-size . 88)
(vimb/default-zoom . 160)
(qutebrowser/default-zoom . 180)))
davinci
is a ThinkPad T480s that I use at my day job.
.config/guix/systems/davinci.scm:
;; NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
(define-module (davinci)
#:use-module (base-system)
#:use-module (gnu))
(operating-system
(inherit base-operating-system)
(host-name "davinci")
(mapped-devices
(list (mapped-device
(source (uuid "eaba53d9-d7e5-4129-82c8-df28bfe6527e"))
(target "system-root")
(type luks-device-mapping))))
(file-systems (cons*
(file-system
(device (file-system-label "system-root"))
(mount-point "/")
(type "ext4")
(dependencies mapped-devices))
(file-system
(device "/dev/nvme0n1p2")
(mount-point "/boot/efi")
(type "vfat"))
%base-file-systems)))
System Settings
(when (equal system-name "davinci")
'((desktop/dpi . 130)
(emacs/default-face-size . 165)
(emacs/fixed-face-size . 165)
(emacs/variable-face-size . 190)
(polybar/height . 25)
(polybar/font-0-size . 12)
(polybar/font-1-size . 8)
(polybar/font-2-size . 14)
(polybar/font-3-size . 9)
(dunst/font-size . 14)
(dunst/max-icon-size . 64)
(vimb/default-zoom . 150)
(qutebrowser/default-zoom . 150)))
;; When booted into Windows
(when (equal system-name "daviwil-t480")
'((emacs/default-face-size . 110)
(emacs/fixed-face-size . 110)
(emacs/variable-face-size . 134)))
phantom
is a ThinkPad X1 Extreme that I use for music production and video editing. For whatever reason, loading the nouveau
driver crashes the machine upon booting so I’ve blacklisted it for now until I figure out how to get it working correctly.
.config/guix/systems/phantom.scm:
;; NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
(define-module (phantom)
#:use-module (base-system)
#:use-module (gnu))
(operating-system
(inherit base-operating-system)
(host-name "phantom")
(mapped-devices
(list (mapped-device
(source (uuid "091b8ad5-efb3-4c5b-8370-7db99c404a30"))
(target "system-root")
(type luks-device-mapping))))
(file-systems (cons*
(file-system
(device (file-system-label "system-root"))
(mount-point "/")
(type "ext4")
(dependencies mapped-devices))
(file-system
(device "/dev/nvme0n1p1")
(mount-point "/boot/efi")
(type "vfat"))
%base-file-systems)))
System Settings
(when (equal system-name "phantom")
'((desktop/dpi . 240)
(polybar/height . 40)
(vimb/default-zoom . 200)))
To install Guix on another machine, you first to build need a USB image. Since I use modern laptops that require non-free components, I have to build a custom installation image with the full Linux kernel. I also include a few other programs that are useful for the installation process. I adapted this image from one found on the Nonguix repository, hence the copyright header.
.config/guix/systems/install.scm:
;;; Copyright © 2019 Alex Griffin <[email protected]>
;;; Copyright © 2019 Pierre Neidhardt <[email protected]>
;;; Copyright © 2019 David Wilson <[email protected]>
;;;
;;; This program 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.
;;;
;;; This program 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.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;; Generate a bootable image (e.g. for USB sticks, etc.) with:
;; $ guix system disk-image nongnu/system/install.scm
(define-module (nongnu system install)
#:use-module (gnu system)
#:use-module (gnu system install)
#:use-module (gnu packages version-control)
#:use-module (gnu packages vim)
#:use-module (gnu packages curl)
#:use-module (gnu packages emacs)
#:use-module (gnu packages linux)
#:use-module (gnu packages mtools)
#:use-module (gnu packages package-management)
#:use-module (nongnu packages linux)
#:export (installation-os-nonfree))
(define installation-os-nonfree
(operating-system
(inherit installation-os)
(kernel linux)
(firmware (list linux-firmware))
;; Add the 'net.ifnames' argument to prevent network interfaces
;; from having really long names. This can cause an issue with
;; wpa_supplicant when you try to connect to a wifi network.
(kernel-arguments '("quiet" "modprobe.blacklist=radeon" "net.ifnames=0"))
;; Add some extra packages useful for the installation process
(packages
(append (list exfat-utils fuse-exfat git curl stow vim emacs-no-x-toolkit)
(operating-system-packages installation-os)))))
installation-os-nonfree
I like to separate my packages into separate manifests that get installed as profiles which can be updated independently. These profiles get installed under the ~/.guix-extra-profiles
path and sourced by my ~/.profile
when I log in.
To make the management of multiple profiles easier, I’ve created a couple of shell scripts:
This script accepts a space-separated list of manifest file names (without extension) under the ~/.config/guix/manifests
folder and then installs those profiles for the first time. For example:
activate-profiles desktop emacs music
.bin/activate-profiles:
# NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
GREEN='\033[1;32m'
RED='\033[1;30m'
NC='\033[0m'
GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles
profiles=$*
if [[ $# -eq 0 ]]; then
profiles="$HOME/.config/guix/manifests/*.scm";
fi
for profile in $profiles; do
# Remove the path and file extension, if any
profileName=$(basename $profile)
profileName="${profileName%.*}"
profilePath="$GUIX_EXTRA_PROFILES/$profileName"
manifestPath=$HOME/.config/guix/manifests/$profileName.scm
if [ -f $manifestPath ]; then
echo
echo -e "${GREEN}Activating profile:" $manifestPath "${NC}"
echo
mkdir -p $profilePath
guix package --manifest=$manifestPath --profile="$profilePath/$profileName"
# Source the new profile
GUIX_PROFILE="$profilePath/$profileName"
if [ -f $GUIX_PROFILE/etc/profile ]; then
. "$GUIX_PROFILE"/etc/profile
else
echo -e "${RED}Couldn't find profile:" $GUIX_PROFILE/etc/profile "${NC}"
fi
else
echo "No profile found at path" $profilePath
fi
done
This script accepts a space-separated list of manifest file names (without extension) under the ~/.config/guix/manifests
folder and then installs any updates to the packages contained within them. If no profile names are provided, it walks the list of profile directories under ~/.guix-extra-profiles
and updates each one of them.
update-profiles emacs
.bin/update-profiles:
# NOTE: This file is generated from ~/.dotfiles/System.org. Please see commentary there.
GREEN='\033[1;32m'
NC='\033[0m'
GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles
profiles=$*
if [[ $# -eq 0 ]]; then
profiles="$GUIX_EXTRA_PROFILES/*";
fi
for profile in $profiles; do
profileName=$(basename $profile)
profilePath=$GUIX_EXTRA_PROFILES/$profileName
echo
echo -e "${GREEN}Updating profile:" $profilePath "${NC}"
echo
guix package --profile="$profilePath/$profileName" --manifest="$HOME/.config/guix/manifests/$profileName.scm"
done
This script makes it easy to update all channels to the latest commit based on an original channel file (see the Channels section at the top of this document).
guix pull --channels=$HOME/.config/guix/base-channels.scm
guix describe --format=channels > ~/.config/guix/channels.scm
Since I keep all of my important configuration files in Org Mode code blocks, I have to ensure that the real configuration files are kept up to date when I sync the latest changes to my dotfiles repo. I’ve written a couple of scripts to simplify that process:
When I want to sync my dotfiles repo into my local clone which likely has uncommitted changes, I run sync-dotfiles
. This script first makes sure that all Org files are saved in a running Emacs instance and then stashes everything before pulling the latest changes from origin
. After pulling, the stash is popped and then the script verifies there are no merge conflicts from the stash before proceeding. If there are no conflicts, update-dotfiles
is run, otherwise I’ll fix the merge conflicts manually and run update-dotfiles
myself.
.bin/sync-dotfiles
# Sync dotfiles repo and ensure that dotfiles are tangled correctly afterward
GREEN='\033[1;32m'
BLUE='\033[1;34m'
RED='\033[1;30m'
NC='\033[0m'
# Navigate to the directory of this script (generally ~/.dotfiles/.bin)
cd $(dirname $(readlink -f $0))
cd ..
echo
echo -e "${BLUE}Saving Org buffers if Emacs is running...${NC}"
emacsclient -u -e "(org-save-all-org-buffers)" -a "echo 'Emacs is not currently running'"
echo -e "${BLUE}Stashing existing changes...${NC}"
stash_result=$(git stash push -m "sync-dotfiles: Before syncing dotfiles")
needs_pop=1
if [ "$stash_result" = "No local changes to save" ]; then
needs_pop=0
fi
echo -e "${BLUE}Pulling updates from dotfiles repo...${NC}"
echo
git pull origin master
echo
if [[ $needs_pop -eq 1 ]]; then
echo -e "${BLUE}Popping stashed changes...${NC}"
echo
git stash pop
fi
unmerged_files=$(git diff --name-only --diff-filter=U)
if [[ ! -z $unmerged_files ]]; then
echo -e "${RED}The following files have merge conflicts after popping the stash:${NC}"
echo
printf %"s\n" $unmerged_files # Ensure newlines are printed
else
update-dotfiles
fi
Updating my dotfiles requires running a script in Emacs to loop over all of my literate configuration .org
files and run org-babel-tangle-file
to make sure all of my configuration files are up to date.
.bin/update-dotfiles
# Navigate to the directory of this script (generally ~/.dotfiles/.bin)
cd $(dirname $(readlink -f $0))
cd ..
# The heavy lifting is done by an Emacs script
emacs -Q --script ./.emacs.d/tangle-dotfiles.el
# Make sure any running Emacs instance gets updated settings
emacsclient -e '(load-file "~/.emacs.d/per-system-settings.el")' -a "echo 'Emacs is not currently running'"
# Update configuration symlinks
stow .
.emacs.d/tangle-dotfiles.el
(require 'org)
(load-file "~/.dotfiles/.emacs.d/lisp/dw-settings.el")
;; Don't ask when evaluating code blocks
(setq org-confirm-babel-evaluate nil)
(let* ((dotfiles-path (expand-file-name "~/.dotfiles"))
(org-files (directory-files dotfiles-path nil "\\.org$")))
(defun dw/tangle-org-file (org-file)
(message "\n\033[1;32mUpdating %s\033[0m\n" org-file)
(org-babel-tangle-file (expand-file-name org-file dotfiles-path)))
;; Tangle Systems.org first
(dw/tangle-org-file "Systems.org")
(dolist (org-file org-files)
(unless (member org-file '("README.org" "Systems.org"))
(dw/tangle-org-file org-file))))
In an ironic twist of fate, I’ve found that certain tools I need to use are more easily available in the Nix package repository, so I use it to install them.
https://nixos.org/channels/nixpkgs-unstable nixpkgs
The channel needs to be updated before any packages can be installed:
nix-channel --update
Installing packages:
nix-env -i nodejs dotnet-sdk gh hledger
# nix-env -iA nixpkgs.nodejs-12_x # For a specific version
Here’s a guide for how I install my GNU Guix systems from scratch. This process is simplified because I’ve already prepared a reusable system configuration so you might need to do extra work if you end up following this for your own system install.
Since I use modern Thinkpads, I have to use the non-free kernel and firmware blobs from the nonguix channel. After cloning the repo, the installation image can be built with this command:
# Create a slightly larger install image to have some headroom
# for temporary file creation and avoid "no space free" errors
guix system image ./install.scm --image-size=5G
NOTE: It can take an hour or more for this to complete, so be patient…
Once the build is complete, Guix will print out the path to the disk image file that was created. You can now write the installation image to a USB stick using dd
:
sudo dd if=/gnu/store/nyg6jv3a4l0pbcvb0x7jfsb60k9qalga-disk-image of=/dev/sdX status=progress
With the newly “burned” installation image, boot from the USB drive and choose “Install using the shell based process.”
Use an editor (or echo
) to create a new file called wifi.conf
to store the wifi configuration. Make sure to set ssid
to the name of your wifi access point and psk
to the passphrase for your wifi. You may also need to change the key_mgmt
parameter depending on the type of authentication your wifi router supports (some examples on Arch Wiki).
network={ ssid="ssid-name" key_mgmt=WPA-PSK psk="unencrypted passphrase" }
First, run the following commands to unblock the wifi card, determine its device name, and connect using the device name you received from ifconfig -a
. In my case it’s wlp4s0
so I run the command like so:
rfkill unblock all
ifconfig -a
wpa_supplicant -c wifi.conf -i wlp4s0 -B
NOTE: If for any reason running
wpa_supplicant
fails, make sure to kill any background instances of it before trying to run it again because the old instances will block new runs from working. This wasted a couple hours of my time the first time I tried installing Guix!
The last step to set up networking is to run dhclient
to turn on DNS for your wifi connection:
dhclient -v wlp4s0
Since we’re installing on a ThinkPad with UEFI, follow the instructions in the Guix manual for disk partitioning. The short of it is that you need to use cfdisk
to create a partition in your free space:
cfdisk /dev/nvme0n1
Once you have your Linux root partition set up, you can enable LUKS to encrypt that partition by running the following commands (where /dev/nvme0n1p5
is your root partition and system-root
is an arbitrary label you’d like to use for it):
cryptsetup luksFormat /dev/nvme0n1p5
cryptsetup open --type luks /dev/nvme0n1p5 system-root
mkfs.ext4 -L system-root /dev/mapper/system-root
mount LABEL=system-root /mnt
Finally, make sure to mount your EFI partition to /mnt/boot
so that the installer can install the bootloader. The Guix installation instructions obscure this step slightly so it’s easy to miss:
mkdir -p /mnt/boot/efi
mount /dev/<EFI partition> /mnt/boot/efi
Now your EFI and encrypted root filesystems are mounted so you can proceed with system installation. You must now set up the installation enviornment using herd
:
herd start cow-store /mnt
If you’ve got a system configuration prepared already, you can use git
to pull it down into the current directory (the one you’re already in, not /mnt
):
git clone https://github.com/daviwil/dotfiles
One important step before you attempt system installation is to set up the nonguix
channel so that the system can be installed from it. Once you’ve cloned your dotfiles repo, you can place your channels.scm
file into the root user’s .config/guix
path and then run guix pull
to activate it:
mkdir -p ~/.config/guix
cp dotfiles/guix/channels.scm ~/.config/guix
guix pull
hash guix # This is necessary to ensure the updated profile path is active!
The pull operation may take a while depending on how recently you generated your installation USB image (if packages in the main Guix repository have been updated since then).
Once your channels are set up, you will need to tweak your configuration to reflect the partition UUIDs and labels for the system that you are installing. To figure out the UUID of your encrypted root partition, you can use the following command:
cryptsetup luksUUID /dev/<root partition>
**TIP:** To make it easier to copy the UUID into your config file, you can switch to another tty using
Ctrl-Alt-F4
and pressEnter
to get to another root prompt. You can then switch back and forth between the previous TTY onF3
.
Now you can initialize your system using the following command:
guix system -L ~/.dotfiles/.config/guix/systems init path/to/config.scm /mnt
This could take a while, so make sure your laptop is plugged in and let it run. If you see any errors during installation, don’t fret, you can usually resume from where you left off because your Guix store will have any packages that were already installed.
Congrats! You now have a new Guix system installed, reboot now to complete the initial setup of your user account.
The first thing you’ll want to do when you land at the login prompt is login as root
and immediately change the root
and user passwords using passwd
(there isn’t a root password by default!):
passwd # Set passwd for 'root'
passwd <username> # Set password for your user account (no angle brackets)
Now log into your user account and clone your dotfiles repository.
Since we used the nonguix
channel to install the non-free Linux kernel, we’ll need to make sure that channel is configured in our user account so that we have access to those packages the next time we guix pull
. At the moment I just symlink the Guix config folder from my .dotfiles
to ~/.config/guix
:
ln -sf ~/.dotfiles/guix ~/.config/guix
Verify that your channels.scm
file is in the target path (~/.config/guix/channels.scm
) and then run guix pull
to sync in the new channel.
Now you can install the packages that you want to use for day-to-day activities. I separate different types of packages into individual manifest files and manage them with my activate-profiles
script:
activate-profiles desktop emacs
Now the packages for these manifests will be installed and usable. They can be updated in the future by using the update-profiles
script.