Skip to content

Repository files navigation

My .dotfiles; A work in progress.

System Configuration

Reconfigured straight from this repo.

Reconfigure

cd /home/franz/dotfiles
sudo guix system -L system reconfigure system/framework.scm

Host configs are framework.scm and thinkpad.scm. common.scm is a shared module, not an entry point.

framework is the primary. thinkpad is the secondary.

-L system is required, not optional: it puts the host config's directory on the load path so (use-modules (common)) resolves, and lets guix find the package modules the config defines.

Why not from /run/current-system/configuration.scm

Guix stores only the top-level config there, without the data files its local-file calls reference (aide-system.conf, security-aide-*, security-lynis-weekly, u2f_mappings). Reconfiguring from it dies during evaluation with:

guix system: error: canonicalize-path: No such file or directory: "/gnu/store/security-lynis-weekly"

Note on trust

guix system reconfigure evaluates this config as root, from a directory you can write without sudo. Anything running as franz that can append Scheme to common.scm gets root at your next reconfigure. git status is what catches that for tracked files, so keep the tree clean and don't reconfigure with unexplained modifications sitting in it.

Debug

cd /home/franz/dotfiles && guix repl -L system system/thinkpad.scm

Home Configuration

Per-host variants that import the (common) module from home/common.scm. They add their own directory to the load path, so no -L is needed:

cd /home/franz/dotfiles/home
guix home reconfigure framework.scm # framework: full stack
guix home reconfigure thinkpad.scm # thinkpad: no mail/calendar/bichon

scripts/home-manifest.scm exposes the same package set as a manifest, so the ordinary tooling works against it without re-declaring anything:

guix weather -m scripts/home-manifest.scm # substitute availability
guix shell -m scripts/home-manifest.scm # throwaway env

home/sway/ keeps the sway/swayfx configs as a fallback compositor. They are not installed by guix home — restore them by hand if niri needs replacing.

Mail

echo"Subject: Hi"| msmtp -a gofranz.com mail@gofranz.com -v

Themes

Available: fleet, ibm-5151, macos-classic

Change theme in home/common.scm:

(definecurrent-theme "fleet")

To add a new theme, create home/themes/<name>/ with:

  • foot.ini, niri-colors.kdl, sway-colors, waybar-light.css, waybar-dark.css
  • gtk-settings-light.ini, gtk-settings-dark.ini
  • Darkman scripts: foot-dark, foot-light, niri-dark, niri-light, sway-dark, sway-light, waybar-dark, waybar-light, dunst-dark, dunst-light, vscode-dark, vscode-light, gtk-dark, gtk-light

The sway-* files are only needed for the fallback compositor in home/sway/.

Features

  • Darkman - Automatic dark/light theme switching at sunrise/sunset (toggle: darkman toggle or Mod+T)
  • niri (Wayland) with greetd/wlgreet, waybar, swaylock. The greeter itself runs a sway session; home/sway/ is kept as a fallback compositor
  • Mail stack - himalaya + isync/mbsync (hourly) + msmtp, plus bichon archiving IMAP for full-text search. Framework only: the accounts and the mcron jobs live in home/framework.scm
  • Contacts/Calendar - pimsync, syncing every 3h (khal is currently commented out in home/common.scm)
  • Bluetooth audio - Nothing Ear buds. A2DP preferred, auto-switching to HFP for calls (home/wireplumber-bluetooth.conf); bt-toggle-profile forces a profile by hand
  • Rootless Podman - Container runtime
  • Mullvad VPN (both hosts) - the only route out, with Tailscale inside the tunnel. Lockdown, auto-connect and LAN sharing are asserted every boot by mullvad-policy and held against drift by mullvad-reconcile (system/common.scm). LAN sharing is asserted deliberately: Mullvad defaults it off, and a host that boots with lockdown on and LAN blocked is unreachable over the local segment until tailscaled is up. captive-portal puts one throwaway browser outside the tunnel for hotel logins
  • broot - File navigator with multi-scheme theming
  • Chrony (NTS) - Authenticated time sync (RFC 8915), replaces default ntpd
  • LUKS encryption - Full disk encryption
  • Power management - thinkpad: TLP with custom AC/battery profiles, plus thermald. framework: power-profiles-daemon (powerprofilesctl set power-saver)
  • ZRAM - Compressed swap (8G thinkpad, 24G framework)
  • Chrome with Hardware Acceleration
  • Housekeeping (both hosts) - fstrim daily at 04:00 and once ~5 min after boot, since mcron never catches up a job missed while the laptop was asleep; cargo sweep over ~/git on Sundays. fstrim needs #:allow-discards? #t on the host's cryptroot or dm-crypt swallows the discard and the job is a silent no-op
  • Suspend - suspend-then-hibernate, hibernating after 5h of standby (elogind, system/common.scm). The framework hibernates to /swapfile, so bdev_allow_write_mounted=0 must stay out of its kernel arguments and resume_offset has to be recomputed (filefrag -e /swapfile) if the swapfile is ever recreated. On AC the thinkpad ignores the lid entirely (handle-lid-switch-external-power), so an offloaded build survives it being closed; on battery it still suspends

Voxtype (voice-to-text) - disabled

Push-to-talk voice-to-text, base.en model. voxtype-vulkan and its wtype backend are commented out in home/common.scm, and the niri keybind, autostart and waybar module were dropped in April 2026. Re-enabling means restoring those too, then:

voxtype setup --download
voxtype setup --check

Security

  • Smart card support (pcscd)
  • lxqt-policykit agent
  • block-facebook-hosts
  • Yubikey FIDO2/U2F touch for sudo (pam_u2f, falls back to password)
  • Fingerprint login (greetd, swaylock)
  • Unprivileged guix-daemon (see below)
  • auditd watches on credential paths (ausearch -k sensitive-files)
  • AIDE file-integrity check, daily at 03:00 (both hosts). Diffs go to /var/log/security-aide.log and the baseline is never updated on its own - review the diff, then sudo security-aide-accept, or every run repeats it
  • Lynis audit, Mondays at 03:30 (both hosts). Summary plus the delta against last week in /var/log/security-lynis.log

Unprivileged guix-daemon

Runs guix-daemon as the guix-daemon user instead of root. Reduces blast radius of a daemon vulnerability — a compromised build can't escalate directly to root. Requires unprivileged user namespaces to stay enabled (kept on in the sysctl block in system/common.scm).

Enabled declaratively — set (privileged? #f) in guix-configuration and reconfigure:

(guix-service-type config =>
(guix-configuration
(inherit config)
(privileged? #f)))

The guix-ownership activation chowns /gnu/store, /etc/guix, and most of /var/guix (except per-user profiles) to guix-daemon:guix-daemon, and creates /var/log/guix. See the Guix manual and the HPC blog post for background.

Gotcha — /var/log must be traversable, and auditd breaks it. If guix shell, guix pull, or a reconfigure fails with:

guix shell: error: creating directory `/var/log/guix': Permission denied

check stat -c '%a %n' /var/log. It needs to be at least 751, ideally 755 (the Guix default). At 700 the daemon can't create /var/log/guix/drvs/... for build logs, and every build dies after the substitute check.

The cause is auditd, not Guix. fix_disk_permissions() (src/auditd-event.c) chmods dirname(log_file) to 0700 — on startup, on every rotation, and on every config reload. Guix's own auditd-service-type defaults to log_file = /var/log/audit.log, so dirname is /var/log and auditd hardens the entire directory. Harmless with a privileged daemon; fatal with (privileged? #f).

The fix here is to give auditd its own directory (system/common.scm), so it locks that down instead:

log_file = /var/log/audit/audit.log

auditd does not create that directory itself, so the auditd-config activation snippet does, and re-asserts 755 on /var/log. To repair a box that's already in this state (activation never runs, because the reconfigure fails first):

sudo chmod 755 /var/log

The directory mode doesn't weaken anything — audit.log, guix-daemon.log etc. stay 0600/0640 root-owned. Debian, Fedora, and Arch all ship /var/log as 755 with per-file restrictions (adm/systemd-journal groups).

Regulatory

  • Set WiFi country code to PT (Portugal). Check with iw reg get.

Fingerprint Setup (Login)

Enroll:

fprintd-enroll

Verify:

fprintd-verify

Swaylock: Press Enter, then scan fingerprint. Password login doesn't work due to swaywm/swaylock#61.

Yubikey Setup (FIDO2 touch for sudo)

pam_u2f is sufficient in the sudo stack, ahead of pam_unix — touch the key and sudo succeeds, otherwise it falls through to the password prompt.

Register the key and write the mapping:

guix shell pam-u2f -- pamu2fcfg -u franz -o pam://franz > system/u2f_mappings

The file holds public key material only. Reconfigure to install it — the u2f-mappings activation copies it to /etc/u2f_mappings as root-owned 0400. It is deliberately not installed via etc-service-type: that symlinks into the store, and pam_u2f rejects an authfile owned by anyone but root or the authenticating user. Since guix-daemon runs unprivileged here, store files are owned by guix-daemon and the check fails. The rejection is silent under sufficient — sudo just falls through to the password, so verify explicitly:

sudo -k && sudo true# should prompt for a touch, not a password

To add a second key, append its line to system/u2f_mappings (one line per user, credentials comma-separated).

Firmware Update (Framework)

Via LVFS using fwupd (capsule-on-disk):

guix shell fwupd -- fwupdmgr get-updates
guix shell fwupd -- sudo fwupdmgr update

Requires AC power. Stages a UEFI capsule to /boot/efi/EFI/UpdateCapsule/ and flashes on next reboot.

Hardware-specific

  • Framework: AMD GPU, USBGuard (device authorization), NVMe/PCIe runtime-PM and wakeup-source udev rules. fw-fanctrl is commented out in home/common.scm
  • Thinkpad: Intel i915, SSH server, backlight udev rules

USBGuard (Framework)

usbguard list-devices # see what's plugged in
usbguard list-rules # active policy
sudo tail -f /var/log/usbguard/usbguard-audit.log

wheel group can run usbguard without sudo.

Currently in trial mode and not enforcing.implicit-policy-target is 'allow and authorized-default is 'all in system/framework.scm, so the daemon logs devices but authorizes everything. Build up rules.conf from usbguard generate-policy, then flip implicit-policy-target to 'block.

Note that ipc-allowed-groups '("wheel") grants full IPC, including policy modification — once enforcing, anything running as franz can re-allow devices without sudo. Scope it with a file in /etc/usbguard/IPCAccessControl.d/ if that matters.

About

Guix dotflines

Topics

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages