My .dotfiles; A work in progress.
Reconfigured straight from this repo.
cd /home/franz/dotfiles
sudo guix system -L system reconfigure system/framework.scmHost 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.
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"
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.
cd /home/franz/dotfiles && guix repl -L system system/thinkpad.scmPer-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/bichonscripts/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 envhome/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.
echo"Subject: Hi"| msmtp -a gofranz.com mail@gofranz.com -vAvailable: 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.cssgtk-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/.
- Darkman - Automatic dark/light theme switching at sunrise/sunset (toggle:
darkman toggleor 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-profileforces 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-policyand held against drift bymullvad-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-portalputs 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) -
fstrimdaily at 04:00 and once ~5 min after boot, since mcron never catches up a job missed while the laptop was asleep;cargo sweepover~/giton Sundays.fstrimneeds#:allow-discards? #ton 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, sobdev_allow_write_mounted=0must stay out of its kernel arguments andresume_offsethas 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
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- 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.logand the baseline is never updated on its own - review the diff, thensudo 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
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/logThe 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).
- Set WiFi country code to
PT(Portugal). Check withiw reg get.
Enroll:
fprintd-enrollVerify:
fprintd-verifySwaylock: Press Enter, then scan fingerprint. Password login doesn't work due to swaywm/swaylock#61.
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_mappingsThe 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 passwordTo add a second key, append its line to system/u2f_mappings (one line per
user, credentials comma-separated).
Via LVFS using fwupd (capsule-on-disk):
guix shell fwupd -- fwupdmgr get-updates
guix shell fwupd -- sudo fwupdmgr updateRequires AC power. Stages a UEFI capsule to /boot/efi/EFI/UpdateCapsule/ and flashes on next reboot.
- Framework: AMD GPU, USBGuard (device authorization), NVMe/PCIe runtime-PM and wakeup-source udev rules.
fw-fanctrlis commented out inhome/common.scm - Thinkpad: Intel i915, SSH server, backlight udev rules
usbguard list-devices # see what's plugged in
usbguard list-rules # active policy
sudo tail -f /var/log/usbguard/usbguard-audit.logwheel 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.