Skip to content

franzos/panther

Repository files navigation

My guix channel "panther"

This repository contains GUIX package defintions maintained primarily by Franz Geffke.

Channel Definition

(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)

Substitute Server

URL: https://substitutes.guix.gofranz.com

;; Public key
(public-key
 (ecc
  (curve Ed25519)
  (q #0096373009D945F86C75DFE96FC2D21E2F82BA8264CB69180AA4F9D3C45BAA47#)))
# Authorize
sudo guix archive --authorize < /path/to/key.pub

Already configured if using %os-base-services or %os-desktop-services.

Home Services

Bichon

Bichon is a self-hosted email archiver: it syncs IMAP accounts, indexes messages for full-text search, and serves a REST API and web interface. It is not an email client — it can't send, compose, or reply. This runs it as a user daemon, with data under $XDG_DATA_HOME/bichon and the log under $XDG_LOG_HOME.

It reuses the shared bichon-configuration record from (px services mail) — see the system service for the full field list. The encryption password is read from a file you manage, never baked into the store. Differences from the system service:

  • No system user/group — runs as your user; user/group are ignored.
  • Data under XDGroot-dir defaults to $XDG_DATA_HOME/bichon (~/.local/share/bichon) instead of /var/lib/bichon, unless you set it explicitly. The full-text index and blob store nest under it (bichon-indices/, bichon-storage/), and the daemon log goes to $XDG_LOG_HOME/bichon.log (~/.local/var/log/bichon.log). The service creates the data and log directories on activation.
  • Binds localhostbind-ip defaults to 127.0.0.1, not all interfaces. Set it explicitly to expose the daemon on your network.

Encryption password: create the password file before first start (the daemon won't start without it):

install -d -m 700 ~/.config/bichon
printf '%s' "$(openssl rand -base64 48)" > ~/.config/bichon/encrypt-password
chmod 600 ~/.config/bichon/encrypt-password

printf '%s' keeps the trailing newline out so the file is exactly the secret. Back it up — this key encrypts your stored IMAP credentials, and it's write-once.

Usage:

(use-modules (px home services mail)
             (px services mail))

(service home-bichon-service-type
         (bichon-configuration
          (encrypt-password-file "/home/user/.config/bichon/encrypt-password")))

Default credentials: first start creates admin / admin@bichon — change it immediately via the web interface (Settings → Profile).

Service management:

herd start bichon    # Start daemon
herd stop bichon     # Stop daemon
herd status bichon   # Check status

Darkman

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 modes

Foot Server

Foot 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 command

Service management:

herd start foot-server    # Start server
herd stop foot-server     # Stop server
herd status foot-server   # Check status

Unattended Upgrade

Periodically 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 now

Podman Healthcheckd

Runs 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 status

System Services

Bichon

Bichon is a self-hosted email archiver written in Rust: it syncs IMAP accounts, indexes messages for full-text search (embedded storage — no external database), and serves a REST API and web interface on port 15630. It is not an email client — it cannot send, compose, or reply. The service creates a dedicated bichon:bichon system user, the data directory at /var/lib/bichon, and rotates /var/log/bichon.log — no manual setup beyond the encryption password.

Encryption password: Bichon encrypts stored credentials with a password you provide. The service takes a path to a file holding that password (encrypt-password-file), so the secret never lands in the world-readable store. Create it before first start:

sudo install -d -m 700 /etc/bichon
openssl rand -base64 48 | tr -d '\n' | sudo tee /etc/bichon/encrypt-password >/dev/null
sudo chmod 600 /etc/bichon/encrypt-password

tr -d '\n' strips the trailing newline so the file is exactly the secret. Back it up — this key encrypts your stored IMAP credentials, and it's write-once: regenerating it against an existing data directory makes the stored data undecryptable. Activation warns (without failing) if the file is missing.

Usage:

(use-modules (px services mail))

;; Minimal — encrypt-password-file is required
(service bichon-service-type
         (bichon-configuration
          (encrypt-password-file "/etc/bichon/encrypt-password")))

;; Behind a reverse proxy, with the SMTP receiver enabled
(service bichon-service-type
         (bichon-configuration
          (encrypt-password-file "/etc/bichon/encrypt-password")
          (bind-ip "127.0.0.1")
          (public-url "https://archive.example.org")
          (enable-smtp? #t)))

Default credentials: first start creates admin / admin@bichon — change it immediately via the web interface (Settings → Profile).

Configuration options:

Field Default Description
package bichon The bichon package to use
root-dir "/var/lib/bichon" Data directory (BICHON_ROOT_DIR)
encrypt-password-file (required) Path to a file holding the credential-encryption password (BICHON_ENCRYPT_PASSWORD_FILE)
http-port 15630 HTTP / web interface port (BICHON_HTTP_PORT)
bind-ip unset → 0.0.0.0 Listen address; bichon binds all interfaces when unset (BICHON_BIND_IP)
public-url unset Public URL used in generated links (BICHON_PUBLIC_URL)
base-url unset Base path when behind a reverse proxy (BICHON_BASE_URL)
log-level "info" trace / debug / info / warn / error (BICHON_LOG_LEVEL)
enable-smtp? #f Enable the SMTP receiver (BICHON_ENABLE_SMTP)
smtp-port 2525 SMTP receiver port (BICHON_SMTP_PORT)
index-dir {root}/bichon-indices Full-text index directory (BICHON_INDEX_DIR)
data-dir {root}/bichon-storage Blob storage directory (BICHON_DATA_DIR)
extra-env '() Extra BICHON_* settings as an alist of (string . string)
user "bichon" System user to run as
group "bichon" System group

Any setting not exposed above can be passed through extra-env, e.g. (extra-env '(("BICHON_SYNC_CONCURRENCY" . "8"))).

Required ports:

  • TCP/15630 — HTTP REST API and web interface
  • TCP/2525 — SMTP receiver (only when enable-smtp? is #t)

Storage caveat: Bichon does not support writing data to network filesystems (NFS, CIFS/SMB) — root-dir must be on local storage.

Service management:

herd status bichon   # Check status
herd start bichon    # Start daemon
herd stop bichon     # Stop daemon

Unattended Upgrade

Drop-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.

Chrony

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-zero

IOTA Node

Run an IOTA full node or validator node.

Prerequisites:

  1. Create configuration file (e.g., /etc/iota/fullnode.yaml)
  2. Download genesis blob for your network (mainnet/testnet/devnet)
  3. 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 node

Configuration 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

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

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 status

vpnmux

Runs the vpnmux daemon, a control loop that keeps Mullvad and Tailscale from conflicting at the netfilter and DNS layer. It continuously reconciles the system to a desired provider state by driving nft, mullvad, and tailscale. The service wires those binaries via VPNMUX_NFT/VPNMUX_MULLVAD/VPNMUX_TAILSCALE, creates /run/vpnmux and /var/lib/vpnmux, and installs the vpnmux CLI into the system profile.

By default the activation creates a vpnmux system group and chowns the runtime dirs to root:vpnmux with mode 02770 (setgid, group rwx), so members of the group can drive vpnmux set/status without sudo. Set group to "" to keep the dirs root-only.

Usage:

(use-modules (px services networking))

(service vpnmux-service-type)

;; Verbose logging in the shepherd log
(service vpnmux-service-type
         (vpnmux-configuration
          (log-level "debug")))

;; Root-only CLI (sudo required for set/status)
(service vpnmux-service-type
         (vpnmux-configuration
          (group "")))

Add yourself to the vpnmux group to use the CLI without sudo.

Configuration options:

Field Default Description
vpnmux vpnmux The vpnmux package to run
nftables nftables Package providing nft (VPNMUX_NFT)
mullvad mullvad-vpn-desktop Package providing the mullvad CLI (VPNMUX_MULLVAD)
tailscale tailscale Package providing the tailscale CLI (VPNMUX_TAILSCALE)
log-level unset When set, exported as VPNMUX_LOG (e.g. "debug")
group "vpnmux" System group permitted to drive the CLI without sudo. Empty string keeps dirs root-only. Exported as VPNMUX_GROUP.

After reconfiguration:

vpnmux status                 # Show current state (no sudo if in vpnmux group)
vpnmux set mullvad            # Desired: Mullvad only
vpnmux set tailscale          # Desired: Tailscale only
vpnmux set mullvad tailscale  # Both, coexisting
vpnmux set                    # Neither (empty set)
herd status vpnmux            # Daemon status

USBGuard

Runs 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.

Before first start:

With implicit-policy-target set to 'block (the default), the daemon will block everything that isn't explicitly allowed — including devices already plugged in when it starts. Generate an initial policy from your currently-connected devices so you don't lock yourself out of your own keyboard:

sudo sh -c 'usbguard generate-policy > /etc/usbguard/rules.conf'
sudo herd restart usbguard

Run this once, with only the devices you trust plugged in. Review /etc/usbguard/rules.conf afterwards and trim anything you don't want whitelisted.

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 usbguard

Changing 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 hand

Hardening: 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.

System Configuration

This channel provides pre-configured building blocks for Guix system definitions. Import with:

(use-modules (px system os))

Packages

Variable Description
%os-base-packages Extends %base-packages with wpa-supplicant, libimobiledevice, neovim

Services

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)

Operating Systems

Variable Description
%os-base Minimal OS with %os-base-services and %os-base-packages

When to Use What

  • Headless server: Use %os-base directly or inherit from it
  • Desktop with GDM/SDDM: Inherit %os-base and use %os-desktop-services
  • Desktop with custom greeter (greetd, etc.): Use %os-desktop-services-minimal to avoid conflicts

Usage

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))))))

Time Travel

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 guix

Known Issues

  • Channel modules shadowed by system profile (bug #74396): After guix pull, new package versions may not be available until you also run guix system reconfigure. Workaround: keep system and user guix in sync, or use guix time-machine -C ~/.config/guix/channels.scm -- shell <package>.

Upstream

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.

About

[Mirror] My guix channel "panther"

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages