This repository contains GUIX package defintions maintained primarily by Franz Geffke.
(cons* (channel
(name 'pantherx)
(url "https://codeberg.org/gofranz/panther.git")
;; Enable signature verification
(introduction
(make-channel-introduction
"54b4056ac571611892c743b65f4c47dc298c49da"
(openpgp-fingerprint
"A36A D41E ECC7 A871 1003 5D24 524F EB1A 9D33 C9CB"))))
%default-channels)URL: https://substitutes.guix.gofranz.com
;; Public key
(public-key
(ecc
(curve Ed25519)
(q #0096373009D945F86C75DFE96FC2D21E2F82BA8264CB69180AA4F9D3C45BAA47#)))# Authorize
sudo guix archive --authorize < /path/to/key.pubAlready configured if using %os-base-services or %os-desktop-services.
Darkman is a framework for managing dark/light mode transitions. It automatically switches themes based on sunrise/sunset times.
Usage:
(use-modules (px home services darkman))
;; Default configuration (uses geoclue2 for location)
(service home-darkman-service-type)
;; With manual coordinates
(service home-darkman-service-type
(home-darkman-configuration
(latitude 52.52)
(longitude 13.405)
(use-geoclue? #f)))
;; Custom configuration
(service home-darkman-service-type
(home-darkman-configuration
(latitude 37.7749)
(longitude -122.4194)
(use-geoclue? #f)
(dbus-server? #t)
(portal? #f)))Mode-switching scripts:
Place executable scripts in:
~/.local/share/dark-mode.d/- Executed when switching to dark mode~/.local/share/light-mode.d/- Executed when switching to light mode
Manual control:
darkman get # Show current mode
darkman set dark # Switch to dark mode
darkman set light # Switch to light mode
darkman toggle # Toggle between modesFoot server mode runs the foot terminal emulator as a background daemon, allowing fast terminal startup with footclient.
Usage:
(use-modules (px home services foot))
;; Default configuration
(service home-foot-server-service-type)
;; With custom config file
(service home-foot-server-service-type
(home-foot-server-configuration
(config-file "/path/to/foot.ini")))
;; With hold option (remain open after child exits)
(service home-foot-server-service-type
(home-foot-server-configuration
(hold? #t)))Connecting to server:
footclient # Open new terminal window
footclient -- htop # Open terminal running specific commandService management:
herd start foot-server # Start server
herd stop foot-server # Stop server
herd status foot-server # Check statusPeriodically runs guix pull followed by guix home reconfigure. Drop-in home equivalent of the system unattended-upgrade-service-type with battery awareness — upgrades are skipped when the laptop is on battery power.
Usage:
(use-modules (px home services unattended-upgrade))
;; Minimal configuration (config-file is required)
(service home-unattended-upgrade-service-type
(home-unattended-upgrade-configuration
(config-file "/home/user/dotfiles/home/home.scm")
(channels #~
(cons* (channel
(name 'my-channel)
(url "https://example.com/channel.git"))
%default-channels))))
;; Full configuration
(service home-unattended-upgrade-service-type
(home-unattended-upgrade-configuration
(config-file "/home/user/dotfiles/home/home.scm")
(skip-on-battery? #t)
(schedule "0 19 * * *")
(channels #~ %default-channels)))Configuration options:
| Field | Default | Description |
|---|---|---|
config-file |
(required) | Path to home.scm configuration file |
channels |
%default-channels |
Gexp producing a list of channels for guix pull -C |
schedule |
"0 19 * * *" |
Cron schedule string |
system-expiration |
90 days | Max age of home generations before deletion |
maximum-duration |
3600 | Max seconds the upgrade may run |
skip-on-battery? |
#f |
Skip upgrade when on battery power |
log-file |
~/.local/state/unattended-home-upgrade.log |
Log file path |
warm-packages |
'() |
List of package names to guix build after reconfigure |
Service management:
herd status unattended-home-upgrade # Check status
herd trigger unattended-home-upgrade # Trigger upgrade nowRuns podman container healthchecks on systems without systemd.
Usage:
(use-modules (px services containers))
;; Default configuration
(service home-podman-healthcheckd-service-type)
;; With custom log level
(service home-podman-healthcheckd-service-type
(home-podman-healthcheckd-configuration
(log-level "debug")))Service management:
herd start podman-healthcheckd # Start daemon
herd stop podman-healthcheckd # Stop daemon
herd status podman-healthcheckd # Check statusDrop-in replacement for (gnu services admin) unattended-upgrade-service-type with battery awareness. All upstream fields are preserved; additions are skip-on-battery? and system-load-paths.
Usage:
(use-modules (px services unattended-upgrade))
(service unattended-upgrade-service-type
(unattended-upgrade-configuration
(schedule "0 17 * * *")
(skip-on-battery? #t)
(system-load-paths '("/home/user/dotfiles/system"))
(channels #~
(cons* (channel
(name 'my-channel)
(url "https://example.com/channel.git"))
%default-channels))))Additional configuration options:
| Field | Default | Description |
|---|---|---|
skip-on-battery? |
#f |
Skip upgrade when on battery power |
system-load-paths |
'() |
Extra -L load paths for guix system reconfigure |
All other fields (operating-system-file, schedule, channels, reboot?, services-to-restart, system-expiration, maximum-duration, log-file) match the upstream (gnu services admin) defaults.
Caveat on system-load-paths: Guix only stores the top-level configuration file in the store (/run/current-system/configuration.scm), not its imported modules. If your config imports local modules (e.g. (common)) that live outside a channel, you need system-load-paths so the unattended upgrade can find them. These modules are resolved from disk at upgrade time — not from a stored snapshot — so they should be kept in sync with your configuration.
Battery detection: Reads /sys/class/power_supply/*/type to locate AC adapters and checks their online status via sysfs. Desktops without battery info proceed normally.
Runs chronyd, the NTP daemon from the Chrony project. Keeps the system clock in sync with the configured time servers. The service creates a dedicated chrony:chrony system user and the drift directory at /var/lib/chrony — no manual setup required.
Default configuration uses NTS (RFC 8915) to authenticate time packets via TLS, preventing on-path attackers from forging NTP responses. The default sources are a geographically diverse mix of Stratum 1 servers:
server time.cloudflare.com iburst nts
server nts.netnod.se iburst nts
server ptbtime1.ptb.de iburst nts
server ptbtime2.ptb.de iburst nts
server ntppool1.time.nl iburst nts
driftfile /var/lib/chrony/drift
ntsdumpdir /var/lib/chrony
makestep 1.0 3
rtcsync
ntsdumpdir caches NTS cookies across restarts so the TLS handshake isn't repeated on every boot. There is currently no NTS pool — TLS certificates break the traditional pool.ntp.org pooling model, so sources are listed individually.
Firewall requirement: NTS needs outbound TCP/4460 (NTS-KE handshake) in addition to the usual UDP/123 (NTP). If TCP/4460 is blocked, chronyc -N authdata will show zeros in the KeyID/Type/KLen columns and the sources will never come up.
First-boot caveat: NTS certificate validation requires a roughly-correct clock. If the RTC is badly wrong, the TLS handshake will fail and chronyd won't be able to bootstrap. On fresh systems with unreliable RTCs, temporarily add an unauthenticated pool 2.pool.ntp.org iburst line until the clock is close enough for TLS to work.
Usage:
(use-modules (px services ntp))
;; Default — five NTS-enabled sources (see above)
(service chrony-service-type)
;; With a custom chrony.conf
(service chrony-service-type
(chrony-service-configuration
(config "server time.cloudflare.com iburst nts
server ptbtime1.ptb.de iburst nts
driftfile /var/lib/chrony/drift
ntsdumpdir /var/lib/chrony
makestep 1.0 3
rtcsync
")))Configuration options:
| Field | Default | Description |
|---|---|---|
package |
chrony |
The chrony package to use |
config |
Five NTS sources + driftfile / ntsdumpdir / makestep / rtcsync |
Raw chrony.conf contents |
Service management:
sudo herd status chrony # Check status
sudo herd configuration chrony # Print path to generated chrony.conf
sudo chronyc sources # Show NTP sources and reachability
sudo chronyc tracking # Show clock sync status
sudo chronyc -N authdata # Verify NTS: KeyID/Type/KLen should be non-zeroRun an IOTA full node or validator node.
Prerequisites:
- Create configuration file (e.g.,
/etc/iota/fullnode.yaml) - Download genesis blob for your network (mainnet/testnet/devnet)
- For validators: generate key pairs
Usage:
(use-modules (px services iota))
;; Basic full node
(service iota-node-service-type
(iota-node-configuration
(config-file "/etc/iota/fullnode.yaml")))
;; With custom settings
(service iota-node-service-type
(iota-node-configuration
(config-file "/etc/iota/validator.yaml")
(data-directory "/var/lib/iota")
(log-file "/var/log/iota-node.log")
(log-level "info,iota_core=debug,consensus=debug")))Service management:
herd status iota-node # Check status
herd start iota-node # Start node
herd stop iota-node # Stop nodeConfiguration options:
| Field | Default | Description |
|---|---|---|
config-file |
(required) | Path to fullnode.yaml or validator.yaml |
user |
"iota" |
System user to run as |
group |
"iota" |
System group |
data-directory |
"/var/lib/iota" |
Database and state storage |
log-file |
"/var/log/iota-node.log" |
Log output location |
log-level |
"info,iota_core=debug,..." |
Rust log levels |
Required ports:
- TCP/9000 - JSON-RPC
- UDP/8084 - P2P sync
RealtimeKit grants real-time scheduling to user processes on request. Required by PipeWire and PulseAudio for low-latency audio.
Usage:
(use-modules (px services audio))
(service rtkit-daemon-service-type)Tailscale is a zero-config VPN built on WireGuard.
Usage:
(use-modules (px services networking))
(service tailscale-service-type)After reconfiguration:
tailscale up # Authenticate and connect
tailscale status # Check connection statusRuns usbguard-daemon to enforce a USB device authorization policy — a whitelist for USB devices that blocks BadUSB-style attacks. The generated usbguard-daemon.conf lives in the store; rules are kept at /etc/usbguard/rules.conf so they can be updated without a reconfigure.
Usage:
(use-modules (px services usbguard))
;; Default: block everything not explicitly allowed, only root can use IPC
(service usbguard-service-type)
;; Allow members of the 'usbguard' group to manage rules via the CLI
(service usbguard-service-type
(usbguard-configuration
(ipc-allowed-groups '("usbguard"))))Add yourself to the usbguard group (created by the service) to use the CLI without sudo.
Managing rules without reconfiguring:
# Via the CLI — daemon persists changes to /etc/usbguard/rules.conf
sudo usbguard list-devices
sudo usbguard allow-device <id> -p # -p = permanent
sudo usbguard append-rule 'allow id 1d6b:0002'
# Or edit the file directly
sudo $EDITOR /etc/usbguard/rules.conf
sudo herd restart usbguardChanging fields in usbguard-configuration (policy targets, IPC allow-lists, audit settings) does require guix system reconfigure — those are baked into the store config.
Configuration options:
| Field | Default | Description |
|---|---|---|
package |
usbguard |
The usbguard package to use |
rule-file |
"/etc/usbguard/rules.conf" |
Persistent rules file |
rule-folder |
"/etc/usbguard/rules.d/" |
Directory of additional rule files |
implicit-policy-target |
'block |
Action for devices not matching any rule ('allow, 'block, 'reject) |
present-device-policy |
'apply-policy |
How to treat devices already connected at daemon start |
present-controller-policy |
'keep |
Same, for USB controllers |
inserted-device-policy |
'apply-policy |
How to treat newly-inserted devices |
authorized-default |
'none |
Default authorization for new devices ('none, 'all, 'keep, 'internal) |
device-manager-backend |
'uevent |
Backend ('uevent or 'umockdev) |
ipc-allowed-users |
'("root") |
Users permitted to use the IPC interface |
ipc-allowed-groups |
'() |
Groups permitted to use the IPC interface |
audit-backend |
'FileAudit |
'FileAudit or 'LinuxAudit |
audit-file-path |
"/var/log/usbguard/usbguard-audit.log" |
Audit log location |
hide-pii? |
#f |
Strip serial numbers and descriptor hashes from audit entries |
log-file |
"/var/log/usbguard.log" |
Shepherd log file |
auto-start? |
#t |
Start the daemon automatically at boot |
Service management:
herd status usbguard # Check status
herd restart usbguard # Reload after editing rules.conf by handHardening: The daemon is launched with -C (drop capabilities after startup) and -W (seccomp syscall allowlist). The D-Bus configuration and Polkit action from the usbguard package are registered automatically, so usbguard-dbus and desktop front-ends work without extra wiring.
This channel provides pre-configured building blocks for Guix system definitions. Import with:
(use-modules (px system os))| Variable | Description |
|---|---|
%os-base-packages |
Extends %base-packages with wpa-supplicant, libimobiledevice, neovim |
| Variable | Description |
|---|---|
%os-base-services |
Extends %base-services with panther channel and substitute servers |
%os-desktop-services |
Extends %desktop-services with panther channel and substitute servers |
%os-desktop-services-minimal |
Desktop services without login/display managers and audio (for custom greeter setups) |
| Variable | Description |
|---|---|
%os-base |
Minimal OS with %os-base-services and %os-base-packages |
- Headless server: Use
%os-basedirectly or inherit from it - Desktop with GDM/SDDM: Inherit
%os-baseand use%os-desktop-services - Desktop with custom greeter (greetd, etc.): Use
%os-desktop-services-minimalto avoid conflicts
Inherit from an OS definition and customize:
(operating-system
(inherit %os-base)
(host-name "my-workstation")
(timezone "Europe/Berlin")
;; Add your file-systems, users, etc.
(services
(cons* (service openssh-service-type)
%os-base-services)))Or use just the services/packages in your own OS:
(operating-system
;; ...your configuration...
(packages
(cons* my-extra-package
%os-base-packages))
(services
(modify-services %os-desktop-services-minimal
(elogind-service-type config =>
(elogind-configuration
(inherit config)
(handle-lid-switch 'suspend))))))When things break because of upstream changes, this will allow you to run a future guix commit, to fix and test the channel without updating the whole system.
Create a channels file that includes only the guix channel:
(list (channel
(name 'guix)
(url "https://codeberg.org/guix/guix.git")
(branch "master")
;; Specify commit
(commit "dc1a77267f03e37b331c8597b066c5ee52a75445")
(introduction
(make-channel-introduction
"9edb3f66fd807b096b48283debdcddccfea34bad"
(openpgp-fingerprint
"BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))))Spawn a shell for a clean environment:
guix shell --container --nesting --network openssl nss-certs coreutils guixAnd build the target package:
guix time-machine --channels=default-channel.scm -- build -L panther pimsync- Channel modules shadowed by system profile (bug #74396): After
guix pull, new package versions may not be available until you also runguix system reconfigure. Workaround: keep system and user guix in sync, or useguix time-machine -C ~/.config/guix/channels.scm -- shell <package>.
Packages here may be upstreamed to Guix if they meet Guix's free software requirements. Please include me in the copyright notice if you do.