#+TITLE: GUIX System Configuration #+AUTHOR: Daniel Ziltener #+PROPERTY: scheme-implementation guile #+PROPERTY: header-args:scheme :comments none :session *guile* :prologue "(use-modules (ice-9 pretty-print))" * Daniel Ziltener's System Configuration This is an attempt to fit my entire system's configuration in a file. Let's see how that turns out. ** Emacs Lisp Helpers To be able to use Org-Mode lists and tables, I need a few helpers for the conversion. *** Converting Lists A sample list for testing purposes: #+NAME: list-sample - Entry 1 - Entry 2 First, a function that converts org lists into guile ~use-~ calls. #+NAME: org-list-to-use #+begin_src scheme :var use-call="use-modules" :var entries='() :var all-parens=0 :results output (pretty-print `(,(string->symbol use-call) ,@(map (lambda (x) (let ((splits (string-split x #\ ))) (if (and (= (length splits) 1) (= 0 all-parens)) (string->symbol (car splits)) (map (lambda (y) (string->symbol y)) splits)))) entries))) #+end_src Sometimes, I need lists of ~string->symbol~ calls. #+NAME: org-to-scheme-sym-list #+begin_src scheme :var input=list-sample :results output (pretty-print `(list ,@(map string->symbol input))) #+end_src *** Converting Tables **** Services This function converts a table of simple services into service definitions. #+NAME: service-converter #+begin_src scheme :var input='() :colnames yes :results output (pretty-print `(list ,@(map (lambda (row) (let ((service-name (car row)) (configuration (cadr row))) `(service ,(string->symbol (string-append service-name "-service-type")) (,(string->symbol (string-append service-name "-configuration")) ,@(call-with-input-string configuration read))))) input))) #+end_src ** Makefile #+begin_src makefile :tangle Makefile reconfigure: guix pull sudo guix archive --authorize < keys/non-guix.pub; \ guix system reconfigure ./config.scm --substitute-urls='https://ci.guix.gnu.org https://bordeaux.guix.gnu.org https://substitutes.nonguix.org' guix home reconfigure ./home/home-configuration.scm make -C /home/zilti/.guix-home/profile/lib/browserpass hosts-firefox-user flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo #+end_src ** System Installation Script This is to be run after setting up the partitions. #+begin_src sh :tangle sysinst.sh #!/bin/sh set euxo -pipefail mount /dev/disk/by-label/guix /mnt mkdir -p /mnt/boot/efi mount /dev/disk/by-label/EFI /mnt/boot/efi herd start cow-store /mnt guix pull -C channels.scm GUIX_PROFILE="/root/.config/guix/current" . "$GUIX_PROFILE/etc/profile" hash guix guix system -L. init config.scm /mnt --substitute-urls="https://ci.guix.gnu.org https://bordeaux.guix.gnu.org https://substitutes.nonguix.org" #+end_src ** The Feature Record Type This record type exists to facilitate grouping together configuration related to a specific feature. #+NAME: feature-record #+begin_src scheme (define-record-type* feature make-feature feature? (name feature-name (default #f)) (root-packages feature-root-packages (default '())) (packages feature-packages (default '())) (files feature-files (default '())) (xdg-config-files feature-xdg-config-files (default '())) (service feature-service (default '())) (home-service feature-home-service (default '())) (environment feature-environment (default '()))) #+end_src Since the records get written into a separate file to be consumed later, this helper function is necessary to do exactly that: #+NAME: record-load-helper #+begin_src scheme :noweb strip-export <> (define* (consume-file fobj #:optional (result '())) (let ((next-elem (read fobj))) (if (eof-object? next-elem) (reverse result) (consume-file fobj (cons next-elem result))))) <> #+end_src And ultimately, this function turns a list of features into a list of whatever is needed at that place. #+NAME: features-to-list #+begin_src scheme (define (features->list features get-fn) (filter (negate unspecified?) (fold append '() (map get-fn (filter (negate unspecified?) features))))) #+end_src ** Features *** Git Git configuration file: #+begin_src :tagle xdg-config/git/config :mkdir-p yes [commit] gpgSign = true user = dziltener@lyrion.ch [core] pager = "diff-so-fancy | less '--tabs=4' '-RFX'" [diff-so-fancy] changeHunkIndicators = true markEmptyLines = true stripLeadingSymbols = true useUnicodeRuler = true [gpg] program = "gpg" [interactive] diffFilter = "diff-so-fancy --patch" [pull] rebase = true [push] autoSetupRemote = true [rebase] autoStash = true [sendemail] # sendmailCmd = msmtp -t annotate = yes smtpServer = lyrion.ch smtpUser = dziltener smtpEncryption = ssl smtpServerPort = 465 [credential] helper = cache helper = !pass-git-helper $@ [tag] gpgSign = true [user] email = "dziltener@lyrion.ch" name = "Daniel Ziltener" signingKey = "37F655BAF43BC0FF300A91A1B38976E82C9DAE42" [init] defaultBranch = master [github] user = dziltener@lyrion.ch [gitlab] user = zilti #+end_src I am using [[https://github.com/languitar/pass-git-helper][pass-git-helper]] to use Pass for git credentials. #+begin_src :tangle xdg-config/pass-git-helper/git-pass-mapping.ini :mkdir-p yes [*dziltener@lyrion.ch*] target=Privat/Mailaccount #+end_src Git service configuration: #+begin_src scheme :tangle features.scm (feature (name 'git) (root-packages '(git)) (packages '(git git:send-email pass-git-helper)) (xdg-config-files '(("git/config" ,(local-file "./xdg-config/git/config")) ("pass-git-helper/git-pass-mapping.ini" ,(local-file "./xdg-config/pass-git-helper/git-pass-mapping.ini"))))) #+end_src *** Hyprland An amazing [[https://hyprland.org/][tiling compositor for Wayland]]. #+begin_src :tangle xdg-config/hypr/hyprland.conf :mkdir-p yes # See https://wiki.hyprland.org/Configuring/Monitors/ monitor=eDP-1,2880x1800@90.0,0x1930,1.333333 # See https://wiki.hyprland.org/Configuring/Keywords/ for more # Execute your favorite apps at launch # exec-once = waybar & hyprpaper & firefox # Source a file (multi-file configs) # source = ~/.config/hypr/myColors.conf # Set programs that you use $terminal = kitty $fileManager = dolphin $menu = fuzzel # Some default env vars. env = XCURSOR_SIZE,24 env = HYPRCURSOR_SIZE,24 env = HYPRCURSOR_THEME,hyprcursor_Dracula env = QT_QPA_PLATFORMTHEME,qt5ct # change to qt6ct if you have that env = MOZ_ENABLE_WAYLAND,1 env = XDG_SESSION_TYPE,wayland env = CLUTTER_BACKEND,wayland env = SDL_VIDEODRIVER,wayland env = LIBGL_DRI3_ENABLE,1 env = GDK_BACKEND,wayland env = XKB_DEFAULT_RULES,evdev env = QT_QPA_PLATFORM,wayland env = WLR_DRM_NO_ATOMIC,1 env = SSH_AUTH_SOCK,/run/user/1000/gnupg/S.gpg-agent.ssh # For all categories, see https://wiki.hyprland.org/Configuring/Variables/ input { kb_layout = de kb_variant = kb_model = kb_options = caps:swapescape kb_rules = follow_mouse = 1 touchpad { natural_scroll = no } sensitivity = 0 # -1.0 - 1.0, 0 means no modification. } general { # See https://wiki.hyprland.org/Configuring/Variables/ for more gaps_in = 15 gaps_out = 20 border_size = 2 col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg col.inactive_border = rgba(595959aa) layout = master # Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on allow_tearing = false } decoration { # See https://wiki.hyprland.org/Configuring/Variables/ for more rounding = 10 blur { enabled = true size = 3 passes = 1 } drop_shadow = yes shadow_range = 4 shadow_render_power = 3 col.shadow = rgba(1a1a1aee) } animations { enabled = yes # Some default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more bezier = myBezier, 0.05, 0.9, 0.1, 1.05 animation = windows, 1, 7, myBezier animation = windowsOut, 1, 7, default, popin 80% animation = border, 1, 10, default animation = borderangle, 1, 8, default animation = fade, 1, 7, default animation = workspaces, 1, 6, default } dwindle { # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more pseudotile = no # master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below preserve_split = yes # you probably want this force_split = 0 default_split_ratio = 1.5 } master { # See https://wiki.hyprland.org/Configuring/Master-Layout/ for more new_is_master = false orientation = center workspace = w[t2], layoutopt:orientation:left always_center_master = true mfact = 0.5 special_scale_factor = 0.8 } gestures { # See https://wiki.hyprland.org/Configuring/Variables/ for more workspace_swipe = off } misc { # See https://wiki.hyprland.org/Configuring/Variables/ for more force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers vfr = true vrr = 1 animate_manual_resizes = true } # Example windowrule v1 # windowrule = float, ^(kitty)$ # Example windowrule v2 # windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ # See https://wiki.hyprland.org/Configuring/Window-Rules/ for more #windowrulev2 = nomaximizerequest, class:.* # You'll probably like this. # See https://wiki.hyprland.org/Configuring/Keywords/ for more $mainMod = SUPER # Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more bind = $mainMod, Q, exec, $terminal bind = $mainMod, C, killactive, bind = $mainMod, M, exec, wlogout bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod SHIFT, L, exec, hyprlock bind = $mainMod, PRINT, exec, grim -l 9 -g "`slurp`" - | swappy -f - bind = $mainMod, P, pseudo, # dwindle bind = $mainMod, J, togglesplit, # dwindle bind = $mainMod, T, togglegroup, bind = $mainMod, A, layoutmsg, swapwithmaster master bind = $mainMod, I, layoutmsg, orientationcenter bind = $mainMod, H, layoutmsg, orientationleft bind = $mainMod, L, layoutmsg, orientationright # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l bind = $mainMod, right, movefocus, r bind = $mainMod, up, movefocus, u bind = $mainMod, down, movefocus, d # Move windows inside a workspace bind = $mainMod SHIFT, a, movewindoworgroup, l bind = $mainMod SHIFT, d, movewindoworgroup, r # Handle tabbed windows bind = $mainMod, tab, changegroupactive, bind = $mainMod SHIFT, t, lockactivegroup, toggle # Switch workspaces with mainMod + [0-9] bind = $mainMod, 1, split:workspace, 1 bind = $mainMod, 2, split:workspace, 2 bind = $mainMod, 3, split:workspace, 3 bind = $mainMod, 4, split:workspace, 4 bind = $mainMod, 5, split:workspace, 5 bind = $mainMod, 6, split:workspace, 6 bind = $mainMod, 7, split:workspace, 7 bind = $mainMod, 8, split:workspace, 8 bind = $mainMod, 9, split:workspace, 9 bind = $mainMod, 0, split:workspace, 10 # Move active window to a workspace with mainMod + SHIFT + [0-9] bind = $mainMod SHIFT, 1, split:movetoworkspace, 1 bind = $mainMod SHIFT, 2, split:movetoworkspace, 2 bind = $mainMod SHIFT, 3, split:movetoworkspace, 3 bind = $mainMod SHIFT, 4, split:movetoworkspace, 4 bind = $mainMod SHIFT, 5, split:movetoworkspace, 5 bind = $mainMod SHIFT, 6, split:movetoworkspace, 6 bind = $mainMod SHIFT, 7, split:movetoworkspace, 7 bind = $mainMod SHIFT, 8, split:movetoworkspace, 8 bind = $mainMod SHIFT, 9, split:movetoworkspace, 9 bind = $mainMod SHIFT, 0, split:movetoworkspace, 10 # Example special workspace (scratchpad) bind = ALT SHIFT, RETURN, togglespecialworkspace, magic bind = $mainMod SHIFT, S, movetoworkspace, special:magic # Scroll through existing workspaces with mainMod + scroll bind = $mainMod, mouse_down, workspace, e+1 bind = $mainMod, mouse_up, workspace, e-1 # Move/resize windows with mainMod + LMB/RMB and dragging bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow # Autostart exec-once = hyprctl dispatch togglespecialworkspace; sleep 1; $terminal; hyprctl dispatch layoutmsg orientationleft; hyprctl dispatch togglespecialworkspace exec-once = hyprpaper exec-once = waybar exec-once = daemon syncthing serve exec-once = hypridle & exec-once = swaync & exec-once = emacs --daemon exec-once = hyprctl plugin load "$(find /gnu/store/ -name 'libhyprsplit.so' | head -n1)" #+end_src Wallpaper via Hyprpaper #+begin_src :tangle xdg-config/hypr/hyprland.conf :mkdir-p yes preload = /home/zilti/config/wallpapers/Next/contents/images/5120x2880.png wallpaper = ,/home/zilti/config/wallpapers/Next/contents/images/5120x2880.png splash = true #+end_src Main configuration #+begin_src scheme :tangle features.scm (feature (name 'hyprland) (root-packages 'hyprland-xwayland) (packages '(hyprcursor hyprpaper hypridle hyprlock hyprland-xwayland-plugin-hyprsplit))) #+end_src ** Operating System Definitions *** File System Update the file system labels: #+begin_src shell # XFS xfs_io -c "label -s NEWLABEL" / # FAT fatlabel /dev/device NEWLABEL #+end_src #+NAME: config-filesystems #+begin_src scheme :noweb yes (file-systems (append (list (file-system (device (file-system-label "EFI")) (mount-point "/boot/efi") (type "vfat")) (file-system (device (file-system-label "guix")) (mount-point "/") (type "xfs"))) %base-file-systems)) #+end_src #+NAME: config-swap #+begin_src scheme :noweb yes (swap-devices (list (swap-space (target (file-system-label "swap"))))) #+end_src *** Channels This adds useful package channels. #+NAME: root-channels #+begin_src scheme :tangle channels.scm (cons* (channel (name 'nonguix) (url "https://gitlab.com/nonguix/nonguix") ;; Enable signature verification: (introduction (make-channel-introduction "897c1a470da759236cc11798f4e0a5f7d4d59fbc" (openpgp-fingerprint "2A39 3FFF 68F4 EF7A 3D29 12AF 6F51 20A0 22FB B2D5")))) (channel (name 'emacs) (url "https://github.com/babariviere/guix-emacs") (introduction (make-channel-introduction "72ca4ef5b572fea10a4589c37264fa35d4564783" (openpgp-fingerprint "261C A284 3452 FB01 F6DF 6CF4 F9B7 864F 2AB4 6F18")))) (channel (name 'rosenthal) (url "https://codeberg.org/hako/rosenthal.git") (branch "trunk") (introduction (make-channel-introduction "7677db76330121a901604dfbad19077893865f35" (openpgp-fingerprint "13E7 6CD6 E649 C28C 3385 4DF5 5E5A A665 6149 17F7")))) (channel (name 'ziltis-channel) (url "https://gitea.lyrion.ch/zilti/guixchannel")) %default-channels) #+end_src Here are the corresponding pubkeys: #+NAME: nonguix-pubkey #+begin_src scheme :tangle keys/non-guix.pub :mkdirp yes (public-key (ecc (curve Ed25519) (q #C1FD53E5D4CE971933EC50C9F307AE2171A2D3B52C804642A7A35F84F3A4EA98#) ) ) #+end_src #+NAME: guix-ci-pubkey #+begin_src scheme :tangle keys/guix-ci.pub :mkdirp yes (public-key (ecc (curve Ed25519) (q #8D156F295D24B0D9A86FA5741A840FF2D24F60F7B6C4134814AD55625971B394#) ) ) #+end_src #+NAME: guix-bordeaux-pubkey #+begin_src scheme :tangle keys/guix-bordeaux.pub :mkdirp yes (public-key (ecc (curve Ed25519) (q #7D602902D3A2DBB83F8A0FB98602A754C5493B0B778C8D1DD4E0F41DE14DE34F#) ) ) #+end_src *** Modules There are many community modules at [[https://whereis.みんな/][Whereis]]. #+NAME: module-list - gnu - gnu image - gnu system nss - guix channels - rosenthal packages wm - zilti packages hyprland - nongnu packages firmware - nongnu packages linux - nongnu system linux-initrd #+begin_src scheme :noweb yes :exports none :results output :tangle config.scm <> #+end_src **** Service Modules #+NAME: service-module-list - admin - authentication - base - configuration - dbus - desktop - docker - linux - networking - nix - pm - sddm - sound - virtualization - xorg #+begin_src scheme :noweb yes :exports none :results output :tangle config.scm <> #+end_src **** Package Modules #+NAME: package-module-list - bootloaders - certs - containers - freedesktop - fonts - gl - gnome - kde-frameworks - linux - pciutils - qt - readline - terminals - version-control - virtualization - wm - xdisorg - xorg #+begin_src scheme :noweb yes :exports none :results output :tangle config.scm <> #+end_src *** Root Packages #+NAME: root-packages - bluez-firmware - egl-gbm - egl-wayland - eglexternalplatform - font-terminus - fwupd-nonfree - git - glu - hwdata - hyprland-xwayland - i915-firmware - libdrm - libglvnd - linux-pam - mesa - nss-certs - network-manager - kwayland - qtwayland - readline - tuxedo-keyboard - xdg-desktop-portal-hyprland - xdg-desktop-portal - xf86-video-amdgpu - xf86-video-intel - xorg-server-xwayland - xorg-server - wayland - amdgpu-firmware - amd-microcode - intel-microcode #+NAME: root-package-block #+begin_src scheme :noweb no-export (packages (append <> %base-packages)) #+end_src #+RESULTS: root-package-block *** Services #+NAME: root-services-block #+begin_src scheme :noweb yes :exports none :results code (services (append <> <> (list polkit-wheel-service) (list <>) (list <>) (list <>))) #+end_src **** Simple Services These services are unmodified, or have just few settings. #+NAME: root-simple-services | Service | Options | |-------------+------------------------------------------------------| | tlp | () | | thermald | ((adaptive? #t)) | | bluetooth | () | | earlyoom | ((minimum-available-memory 5) (minimum-free-swap 5)) | | inputattach | () | | libvirt | ((unix-sock-group "libvirt")) | | nix | () | | virtlog | () | | fstrim | () | | fprintd | () | #+NAME: root-simple-service-block #+begin_src scheme :noweb yes :exports none :results output <> #+end_src **** Unattended Upgrade Service #+NAME: unattended-upgrade #+begin_src scheme :noweb no-export (service unattended-upgrade-service-type (unattended-upgrade-configuration (schedule "5 12 * * 1") (channels #~ <>))) #+end_src **** Hosts File #+NAME: hosts-file-service #+begin_src scheme :noweb no-export (simple-service 'add-extra-hosts hosts-service-type (list (host "127.0.0.1" "l.redsky.io" '("ld.redsky.io")) (host "::1" "l.redsky.io" '("ld.redsky.io")))) #+end_src **** Modified Desktop Services #+NAME: root-modified-desktop-services #+begin_src scheme :exports none :results code :noweb no-export (modify-services %desktop-services (delete screen-locker-service-type) (delete pulseaudio-service-type) (guix-service-type config => (guix-configuration (inherit config) (channels <>) (substitute-urls (append (list "https://substitutes.nonguix.org") %default-substitute-urls)) (authorized-keys (append (list (local-file "./keys/non-guix.pub")) %default-authorized-guix-keys))))) #+end_src **** Screen Locker Service For some reason, this service runs on root level for Guix. #+NAME: screen-lock-service #+begin_src scheme (service screen-locker-service-type (screen-locker-configuration (name "swaylock") (program (file-append swaylock-effects "/bin/swaylock")) (using-setuid? #f))) #+end_src *** Operating System This is the full operating system specification. #+begin_src scheme :noweb no-export :results code :tangle config.scm (operating-system (host-name "ziltis-machine") (timezone "Europe/Berlin") (locale "de_DE.utf8") (keyboard-layout (keyboard-layout "de" #:options '("caps:swapescape"))) (kernel linux) (initrd microcode-initrd) (firmware (list linux-firmware)) (bootloader (bootloader-configuration (bootloader grub-efi-bootloader) (targets '("/boot/efi")) (keyboard-layout keyboard-layout))) #;(file-systems %local-filesystem) #;(swap-devices %local-swap) <> <> (users (cons* (user-account (name "zilti") (group "users") (supplementary-groups '("avahi" "users" "input" "wheel" "netdev" "audio" "cdrom" "video" "libvirt" "lp"))) %base-user-accounts)) <> <> (name-service-switch %mdns-host-lookup-nss)) #+end_src ** Guix Home This is the main home configuration. First, all the imports: #+NAME: home-module-list - gnu packages - gnu packages gnupg - gnu packages linux - gnu packages shellutils - nongnu packages game-client - gnu services - guix gexp - guix channels - gnu home - gnu home services - gnu home services desktop - gnu home services gnupg - gnu home services guix - gnu home services mail - gnu home services mcron - gnu home services pm - gnu home services shells - gnu home services shepherd - gnu home services sound - gnu home services ssh - gnu home services syncthing #+begin_src scheme :noweb yes :exports none :results output :mkdir-p yes :tangle home/home-configuration.scm <> #+end_src