Development
This page covers source builds, tests, packaging, and Flatpak workflows for contributors.
For pull request workflow, testing expectations, and safety notes, see Contributing.
Prerequisites
- Rust 1.85+ (or install current stable via
rustup) just1.51+ (https://github.com/casey/just) for task automationnfpm(https://nfpm.goreleaser.com) for packagingflatpak-builder(https://github.com/flatpak/flatpak-builder) for flatpak
sudo apt install build-essential pkg-config clang libclang-dev \
libopencv-dev libv4l-dev libpam0g-dev \
libgtk-4-dev libadwaita-1-dev \
libcairo2-dev libglib2.0-dev libgdk-pixbuf-2.0-dev \
libpango1.0-dev libgraphene-1.0-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-devsudo dnf install @development-tools pkg-config clang clang-devel \
opencv-devel libv4l-devel pam-devel \
gtk4-devel libadwaita-devel \
gstreamer1-devel gstreamer1-plugins-base-devel \
checkpolicy policycoreutilssudo pacman -S base-devel pkgconf clang llvm \
opencv v4l-utils pam \
gtk4 libadwaita \
gstreamer gst-plugins-baseSetup
git clone https://github.com/gundulabs/gaze
cd gaze
just setup-hooks
just --listGit hooks are local to each clone. just setup-hooks points Git at the tracked hook scripts so pre-commit checks stay up to date when the repo changes. CI still runs the same required checks for pushes and pull requests.
Workspace layout
gaze- thegazeddaemon, ML pipeline, and user database.gaze-cli- thegazeCLI binary. Lives in its own crate so the client binary does not statically link ONNX Runtime (see warning below).gaze-core- shared camera/config/DBus library. Face detection sits behind thedetectioncargo feature (on by default); client crates opt out withdefault-features = false.pam-gaze,pam-gaze-grosshack-cdylibPAM modules; shared FFI/auth logic lives inpam-gaze-core.gaze-gui- GTK4/libadwaita app.gnome-shell-extension/is packaged separately.
Build and test rust components
just build-rust
just test
just lint
just fmt-check
just audit # check dependencies for known CVEs
just fmt # apply formatting (fmt-check only checks)Build with just build-rust, not cargo build --workspace
just build-rust builds the daemon and the clients in separate cargo invocations so feature unification cannot link ONNX Runtime into the CLI, GUI, or PAM modules. ONNX Runtime's startup code requires AVX2, and a single workspace build would silently reintroduce crashes on older CPUs (issue #14).
Run a locally-built daemon
The daemon takes no CLI arguments; paths are compiled in:
- Config:
/etc/gaze/config.toml - User templates:
/var/lib/gaze/users - Models:
/var/cache/gaze
It also owns com.gundulabs.Gaze on the system DBus bus, which requires root. You cannot run a second daemon as your user.
Option A: link your build over the installed files (easier for repeated iteration):
just build-rust
just dev-link-system # symlinks /usr/bin/gazed, /usr/bin/gaze, PAM modules, and extension over your buildjust dev-unlink-system restores the package-installed files from backup. just dev-link-status shows what is currently linked.
Option B: run the daemon in the foreground:
sudo systemctl stop gazed
just build-rust
sudo RUST_LOG=debug ./target/release/gazedRUST_LOG accepts standard tracing filters (info, debug, gaze=trace, etc.). Ctrl-C to stop, then sudo systemctl start gazed when you're done to restore the system daemon.
If you've never installed Gaze on this machine, you also need the DBus policy and a config file in place before the daemon can claim its name or load. The simplest way is to install the package once, then iterate on the binary:
sudo install -Dm644 packaging/config/com.gundulabs.Gaze.conf \
/etc/dbus-1/system.d/com.gundulabs.Gaze.conf
sudo install -Dm644 packaging/config/config.toml /etc/gaze/config.toml
sudo systemctl reload dbusThe CLI and GUI need no special setup; they talk to whichever gazed currently owns the bus name:
./target/release/gaze list-faces
./target/release/gaze auth --verbose
./target/release/gaze-guiIterating on the PAM module
pam-gaze and pam-gaze-grosshack build as cdylibs. After just build-rust you'll have:
target/release/libpam_gaze.sotarget/release/libpam_gaze_grosshack.so
To exercise them through real PAM, copy into the system PAM library directory (path is distro-specific):
# Debian/Ubuntu up to 25.10
sudo cp target/release/libpam_gaze.so /lib/x86_64-linux-gnu/security/pam_gaze.so
# Ubuntu 26.04+ (libpam looks in /usr/lib/security)
sudo cp target/release/libpam_gaze.so /usr/lib/security/pam_gaze.so
# Fedora/RHEL
sudo cp target/release/libpam_gaze.so /lib64/security/pam_gaze.so
# Arch
sudo cp target/release/libpam_gaze.so /usr/lib/security/pam_gaze.soDon't lock yourself out
Before touching PAM files, keep a second terminal open with an active root shell (sudo -s). If the module crashes or misbehaves, you can revert from that shell. Test against a non-critical service first (e.g. add a line to /etc/pam.d/su or a custom service), not system-auth or sudo.
Quickest end-to-end test once the .so is in place:
sudo -k # invalidate cached sudo credentials
sudo -v # force a fresh PAM promptIterating on the GNOME extension
The extension source lives in gnome-shell-extension/. To run it from the tree without packaging:
mkdir -p ~/.local/share/gnome-shell/extensions
ln -sfn "$PWD/gnome-shell-extension" \
~/.local/share/gnome-shell/extensions/gaze@gundulabs.com
# compile the gsettings schema once
glib-compile-schemas ~/.local/share/gnome-shell/extensions/gaze@gundulabs.com/schemas
# on Xorg: Alt+F2 then `r`. On Wayland: log out and back in.
gnome-extensions enable gaze@gundulabs.com
gsettings set org.gnome.shell.extensions.gaze enable-face-authentication trueWatch shell logs while you iterate:
journalctl -f /usr/bin/gnome-shellFor the unlock-dialog session mode (lock screen), changes only take effect after a fresh lock, not a shell reload.
Packaging
just package <deb | rpm | archlinux>Package output:
dist/packages/
Flatpak build
just build-flatpakOutput bundle:
dist/packages/com.gundulabs.Gaze-<arch>.flatpak(e.g.com.gundulabs.Gaze-x86_64.flatpak)
Cleaning build artifacts
just clean