Installing

sudo ./deploy.sh (or sudo ./install.sh, which just runs it) is the only supported install path. This page covers what it does, how to build from source instead of using the release binaries, camera pinning, and removal.

Requirements

  • An IR camera on uvcvideo (Windows Hello style). See hardware.md.
  • PAM with pam_exec.so, standard everywhere.
  • On SELinux systems, policycoreutils to compile the lock-screen policy. Installed by default on Fedora.

No build toolchain is required. If deploy.sh finds no Rust toolchain and no local build, it downloads the static musl binaries from GitHub Releases and verifies them against the release’s SHA256SUMS. Build from source for an unreleased change or the NPU backend.

What deploy.sh does

Step What Details
Build Picks the first that applies OpenVINO + cargo: NPU build. Prebuilt binaries in target/: use them, unless cargo is available and the sources are newer than they are (FACE_AUTH_FORCE_BUILD=1 rebuilds regardless; it covers the tray too). Cargo: static musl build. Otherwise: download and checksum-verify the release binaries
Binaries Installs to /usr/local/bin vinoauthface-auth (set-group-ID face-auth, see security.md) and vinoauthface, plus its bash, zsh and fish completions under /usr/local/share (regenerate with vinoauthface completions bash, zsh or fish)
Models Downloads and SHA-256 verifies Recognition model (w600k_r50.onnx for an NPU build, w600k_mbf.onnx otherwise) and the det_500m.onnx (SCRFD) detector, to /usr/local/share/face-auth/. Installing the detector over an older install means re-enrolling; deploy.sh says so. A copy in models/ is used first, and verified too
Config Installs default config /etc/face-auth.toml, kept if it already exists; the deploy appends any settings from config/face-auth.toml.example it lacks, commented out at their defaults
PAM Patches PAM service files See pam.md. Each file is backed up with a .face-auth.bak suffix (for you; uninstall.sh strips the line instead of restoring it)
Bitwarden Only if installed Adds Bitwarden’s polkit unlock action
SELinux Compiles and loads policy Lets the greeters (xdm_t) use the camera, NPU, template store and lockout state. See SELinux
NPU cache Empties and refills it /var/cache/face-auth, root-owned. Emptied because a new model or driver leaves stale entries, then refilled with vinoauthface-auth --warm-cache (NPU builds only)
Login screen --login=off\|both\|face The Plasma login screen’s mode, off on a fresh install and kept on re-deploy. See pam.md
Tray Unless --no-tray Tray binary, root helper, polkit actions, autostart and launchers. See tray.md. Every deploy also installs uninstall.sh to /usr/local/share/face-auth/
Storage Secures the template store /var/lib/face-auth, root:face-auth 2750, templates 0640, per-user lockout/ 2770. An existing store is re-secured in place

Building from source

Core (static musl, no runtime deps)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add x86_64-unknown-linux-musl

git clone https://github.com/karanshukla/vinoAuthFace.git
cd vinoAuthFace
cargo build --release --locked --target x86_64-unknown-linux-musl -p face-auth -p face-enroll
sudo ./deploy.sh

If you installed Rust with rustup, cargo lives in ~/.cargo/bin, which is not on root’s PATH. deploy.sh looks there for the invoking user and runs the build as that user, so it does not leave root-owned files in target/.

OpenVINO / NPU backend

The npu feature swaps pure-Rust tract inference for OpenVINO on an Intel NPU, GPU or CPU. It links OpenVINO’s glibc libraries, so it can’t be a static musl build.

deploy.sh handles it. If it finds OpenVINO and a Rust toolchain, it builds with --features npu and sets backend = "openvino" in /etc/face-auth.toml. Pick the device with npu_device = "NPU" | "GPU" | "CPU". It looks for OpenVINO in this order:

Source How it’s found Runtime lookup
ovfetch 0.5.0+ On PATH or in ~/.cargo/bin rpath baked into vinoauthface-auth
System package libopenvino_c.so* in a standard lib dir The package’s own ldconfig entry
Extracted archive ~/.local/opt or /opt/intel, with setupvars.sh Copied, then registered with ldconfig

ovfetch is the recommended one. It picks the OpenVINO build your NPU and its installed driver need, and refuses anything whose hash independent sources don’t agree on. It installs to /usr/local/lib/face-auth/openvino, and later deploys only download again when a different build resolves. ovfetch runs as you, into a staging directory; once root has copied that into place, deploy.sh checks every file against ovfetch’s SHA256SUMS and removes the directory if anything doesn’t match or isn’t listed (face unlock then falls back to the password). It installs OpenVINO alone (ovfetch install --openvino-only), without ONNX Runtime, which face-auth doesn’t use: that is the newest OpenVINO the NPU driver supports, the same release the machine’s other NPU programs run, with nothing to compile. (--build-runtime, which compiled ONNX Runtime to get there, is no longer needed and is ignored.) If ovfetch installed the current runtime and is now missing or older than 0.5.0, deploy stops and asks you to upgrade it rather than switch to the CPU backend. Install it with cargo install ovfetch --locked, or grab the attested binary from its releases.

If the NPU driver has no compiler library (Fedora’s 1.32.0 rpm ships none), OpenVINO can’t compile anything for the NPU. deploy.sh then builds the tract backend instead of shipping a build that would silently fall through to the password on every unlock. Run ovfetch detect to check.

Container build (no toolchain installed)

If deploy.sh finds no toolchain and the release download fails, it prints this command rather than running it: deploy.sh runs under sudo, and rootless podman driven through sudo -u often fails on a missing XDG_RUNTIME_DIR.

podman run --rm -v "$PWD":/src:Z -w /src docker.io/library/rust:alpine \
  sh -c 'apk add --no-cache musl-dev && \
         cargo build --release --locked --target x86_64-unknown-linux-musl \
           -p face-auth -p face-enroll'
sudo ./deploy.sh

rust:alpine targets musl natively, so the result is the same static binary. Run the container as your own user so the files in target/ stay yours.

Immutable distros via distrobox

distrobox create --image registry.fedoraproject.org/fedora:latest --name authface-dev
distrobox enter authface-dev

# Fedora does not package a musl std for Rust, so use rustup, not the distro `rust` package.
sudo dnf install -y gcc musl-gcc cmake
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
  --target x86_64-unknown-linux-musl
source "$HOME/.cargo/env"

cd ~/Projects/vinoAuthFace
cargo build --release --locked --target x86_64-unknown-linux-musl -p face-auth -p face-enroll

exit
sudo ./deploy.sh   # on the host
sudo ./pin-camera.sh /dev/videoN

Run it after enrolment and a test unlock both work: it pins whatever device you give it, and vinoauthface enroll prints the exact command with the camera it used. It writes a udev rule for a stable /dev/face-auth-ir symlink and records the camera’s physical identity in /etc/face-auth.toml. Re-run it if you replace the hardware on purpose. What this defends against: security.md.

SELinux

With SELinux enforcing, the greeters (GDM, SDDM, Plasma Login) run face-auth in the confined xdm_t domain. sudo, the KDE lock screen and polkit agents run in your unconfined session and don’t need it. deploy.sh installs a module, selinux/face-auth.te plus the labels in selinux/face-auth.fc, that:

Grants xdm_t Why
map on the camera (v4l_device_t) Frame capture
/dev/accel/* labelled dri_device_t, the GPU’s type Stock policy leaves the NPU unlabelled (device_t), so OpenVINO can’t open it
Read on /var/lib/face-auth (face_auth_var_lib_t) Templates
Write and lock on <user>/lockout (face_auth_lockout_t) Without it the lockout never advances at the greeter
Read and map on /var/cache/face-auth (face_auth_cache_t) The compiled NPU models
getattr on /usr/lib64/games A cold NPU cache recompiles, and the driver’s compiler stats every directory there

deploy.sh relabels the NPU node, the cache and the store after loading it, and the OpenVINO runtime after copying it in.

Sealed templates (seal_embeddings = true) need the TPM and systemd’s host credential key, which decrypts every systemd credential on the machine, so xdm_t gets neither. The greeter instead execs /usr/local/libexec/vinoauthface-unseal, labelled face_auth_unseal_exec_t, which moves into its own domain, face_auth_unseal_t, the only one with /dev/tpmrm0 and credential.secret. It takes a UID and one of face-auth’s credential names, and only ever runs systemd-creds decrypt on them. xdm_t (and local_login_t, for a TTY login) gains nothing but the right to exec it.

If deploy.sh reported missing SELinux tools, install the module by hand:

sudo dnf install -y policycoreutils checkpolicy
sudo checkmodule -M -m -o face_auth.mod selinux/face-auth.te
sudo semodule_package -o face_auth.pp -m face_auth.mod -f selinux/face-auth.fc
sudo semodule -i face_auth.pp
sudo restorecon -R /dev/accel /var/lib/face-auth /var/cache/face-auth /usr/local/lib/face-auth

Remove it with sudo semodule -r face_auth.

Updating

Nothing updates itself. vinoauthface doctor prints an update line, and the tray adds an “Update to vN” entry plus one notification per new release, when a newer release exists. Both ask GitHub’s public releases API through curl (tray: after a minute, then daily). Turn it off with update_check = false in /etc/face-auth.toml. Dev builds never check.

To update, no checkout needed:

sudo vinoauthface-upgrade          # the newest release
sudo vinoauthface-upgrade v3       # a specific one, also to go back
sudo vinoauthface-upgrade --force  # reinstall the one you're on

vinoauthface-update is the same command under another name.

It downloads the release’s source bundle (vinoauthface-source.tar.gz), checks it against the release’s SHA256SUMS, unpacks it into ~/.cache/vinoauthface/src/<tag> and runs that release’s own deploy.sh. Every upgrade is a full reinstall by the release’s deploy logic, so anything a release changes beyond the binaries (models, config, PAM, SELinux) comes with it. A --no-tray install stays one. Older releases’ unpacked sources are deleted once it succeeds, but the build directory, ~/.cache/vinoauthface/target, is kept and shared by every release, so an upgrade recompiles only vinoAuthFace’s own crates, not its dependencies. It can take a few GB (more for an OpenVINO build); uninstall.sh removes it, and deleting it by hand is safe. Releases before the command existed have no bundle; use a checkout for those.

An OpenVINO install builds from the bundle as you, with your Rust and ovfetch, the same as a checkout. That’s why the tray can’t upgrade one: its helper runs as root only. The tray’s “Update to vN” entry is for CPU installs, and runs the same command through pkexec (see tray.md).

From a checkout, git pull then sudo ./deploy.sh still works. A checkout on a release tag installs that release’s binaries (or builds them, with Rust installed).

Either way, your config, templates and PAM setup are kept. New settings are appended to /etc/face-auth.toml as commented defaults.

Updating after a code change

sudo ./update.sh            # rebuild and replace the binaries only
sudo ./update.sh --no-tray  # leave the tray alone

For iterating on the code. It rebuilds from source with the same backend as the installed vinoauthface-auth (an NPU build if it links OpenVINO, static musl if not), then replaces vinoauthface-auth (keeping it set-group-ID face-auth), vinoauthface, and the tray and its helper if they’re installed. Unchanged binaries are left alone. Models, config, PAM, SELinux policy, the OpenVINO runtime, the NPU cache and the template store aren’t touched, and it needs a Rust toolchain and an existing deploy.sh install. After pulling changes to any of those, run sudo ./deploy.sh instead.

Uninstalling

sudo ./uninstall.sh           # binaries, models, config, PAM changes, camera pin
sudo ./uninstall.sh --purge   # ...plus face templates and the face-auth group

PAM goes first, before any binary is removed: it strips the vinoAuthFace line from each service and deletes the .face-auth.bak backups rather than restoring them, so edits made since the install (a distro update, your own) are kept. It removes a PAM override (polkit-1, KDE, COSMIC) only if deploy.sh created it, and removes the Bitwarden action only if deploy.sh installed it. It also cleans up leftovers from older installs that had the GTK GUI.