Skip to content

Instantly share code, notes, and snippets.

@daym
Last active August 23, 2025 13:59
Show Gist options
  • Save daym/6c0bcd137c9ba43790b4140cdf8bb7d9 to your computer and use it in GitHub Desktop.
Save daym/6c0bcd137c9ba43790b4140cdf8bb7d9 to your computer and use it in GitHub Desktop.
guix howdy
diff --git a/gnu/packages/linux.scm b/gnu/packages/linux.scm
index a3d02cf025..c6f9858eea 100644
--- a/gnu/packages/linux.scm
+++ b/gnu/packages/linux.scm
@@ -115,6 +115,7 @@ (define-module (gnu packages linux)
#:use-module (gnu packages boost)
#:use-module (gnu packages calendar)
#:use-module (gnu packages check)
+ #:use-module (gnu packages cmake)
#:use-module (gnu packages cpio)
#:use-module (gnu packages cpp)
#:use-module (gnu packages crates-io)
@@ -148,6 +149,7 @@ (define-module (gnu packages linux)
#:use-module (gnu packages haskell-apps)
#:use-module (gnu packages haskell-xyz)
#:use-module (gnu packages image)
+ #:use-module (gnu packages image-processing)
#:use-module (gnu packages kde-frameworks)
#:use-module (gnu packages libevent)
#:use-module (gnu packages libunwind)
@@ -2123,6 +2125,110 @@ (define-public pam-gnupg
GnuPG-based password manager like @code{pass}.")
(license license:gpl3+)))
+;; It seems that the PAM module just runs the Python detector as root ?! That is terrible.
+;; Instead, it should be a service (just like gpg-agent; although there is no reason it should run as the regular user and plenty of reasons it should run under a service user)--and the Howdy PAM module should connect to that.
+;; If you absolutely have to, setuid to that service user before executing the Python program.
+;; I don't think that would do serializable execution correctly, though--see also <https://github.com/boltgolt/howdy/issues/895>.
+;; Luckily, Howdy has -U --user option that allow per-user configuration, but it's not working as I thought. When I ran:
+;; sudo howdy -U silverbullet069_ftp disable 1 (silverbullet069_ftp is my FTP user) It also disables Howdy at my main account (which is silverbullet069)
+;; If there was a service, it would also be clear who reads a custom config.ini and where it was generated from.
+;; Howdy does not currenty support that, but has a concept called "recorders". Every recorder is responsible for getting the video feed from the configured video device and delivering it to OpenCV.
+(define-public howdy
+ (package
+ (name "howdy")
+ (version "2.6.1")
+ (source (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://github.com/boltgolt/howdy.git")
+ (commit "c4521c14ab8c672cadbc826a3dbec9ef95b7adb1"))) ; beta branch.
+ (file-name (git-file-name name version))
+ (sha256
+ (base32
+ "09gf8w7y06b1pwxfahxpbnl65rxcdmhrn100g949m7nxl27mbw6b"))))
+ (build-system meson-build-system)
+ (arguments
+ (list
+ #:imported-modules
+ `(,@%meson-build-system-modules
+ (guix build python-build-system))
+ #:modules
+ `((guix build meson-build-system)
+ ((guix build python-build-system) #:prefix python:)
+ (guix build utils))
+ #:configure-flags
+ #~(list "-Dwith_polkit=true" ; install admin interface's polkit policy
+ "-Dinstall_pam_config=false" ; some Debian thing
+ "-Dinstall_in_site_packages=true" ; others can import it in Python
+ ; not sure we need that. "-Dsysconfdir=/etc"
+ ;;; OK: pam_dir (default: prefix / "/lib/security")
+ ;;; OK: log_path (default: "/var/log/howdy")
+ (string-append "-Dconfig_dir=" "/etc/howdy") ; make it configurable.
+ "-Duser_models_dir=/var/lib/howdy/models" ; was: /etc/howdy/models
+ (string-append "-Dpython_path="
+ (search-input-file %build-inputs "bin/python3")))
+ #:phases
+ #~(modify-phases %standard-phases
+ (add-after 'unpack 'patch
+ (lambda* (#:key inputs #:allow-other-keys)
+ (write inputs)
+ (newline)
+ ;; Add python shebang.
+ (invoke "sed" "-i" "1i#!/usr/bin/python3"
+ "howdy/src/compare.py")
+ (chmod "howdy/src/compare.py" #o755)
+ (substitute* "howdy/src/pam/main.cc"
+ (("PYTHON_EXECUTABLE_PATH")
+ "\"/run/privileged/bin/pkexec\""))
+ ;; It seems that howdy has no support for changing what the config_dir is at runtime.
+ ;; We DO need that--so we'll have to figure out a way to do it.
+ ;; The safest and least likely way would be to adapt the package dynamically when it's used in PAM.
+ ;; See ./howdy/src/paths.py.in and ./howdy-gtk/src/paths.py.in
+ ;; and ./howdy/src/pam/paths.hh.in:const auto CONFIG_FILE_PATH = "@config_file_path@";
+ ;; Allow using global mutable config in /etc (for now).
+ ;; Prevent actually installing the config to /etc here.
+ (substitute* "howdy/src/meson.build"
+ ;; Note: install_tag: 'config'
+ (("install_data\\('config.ini'.*") "\n"))))
+ (add-after 'install 'install-system-models
+ (lambda* (#:key inputs outputs #:allow-other-keys)
+ (with-directory-excursion (string-append (assoc-ref outputs "out")
+ "/share/dlib-data")
+ (for-each (lambda (key)
+ (copy-file (assoc-ref inputs key) key)
+ (invoke "bunzip2" key))
+ '("dlib_face_recognition_resnet_model_v1.dat.bz2"
+ "mmod_human_face_detector.dat.bz2"
+ "shape_predictor_5_face_landmarks.dat.bz2")))))
+ (add-after 'glib-or-gtk-wrap 'wrap-python
+ (assoc-ref python:%standard-phases 'wrap)))))
+ (native-inputs
+ (list bzip2 cmake pkg-config sed
+ ;; We got those URL from howdy's "install.sh".
+ ;; The license of the data files is CC0.
+ ;; See also: <https://github.com/davisking/dlib-models/>.
+ (origin (method url-fetch)
+ (uri "https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2")
+ (sha256
+ (base32
+ "0fjm265l1fz5zdzx5n5yphl0v0vfajyw50ffamc4cd74848gdcdb")))
+ (origin (method url-fetch)
+ (uri "https://github.com/davisking/dlib-models/raw/master/mmod_human_face_detector.dat.bz2")
+ (sha256
+ (base32
+ "117wv582nsn585am2n9mg5q830qnn8skjr1yxgaiihcjy109x7nv")))
+ (origin (method url-fetch)
+ (uri "https://github.com/davisking/dlib-models/raw/master/shape_predictor_5_face_landmarks.dat.bz2")
+ (sha256
+ (base32
+ "0wm4bbwnja7ik7r28pv00qrl3i1h6811zkgnjfvzv7jwpyz7ny3f")))))
+ (inputs
+ (list opencv ffmpeg v4l-utils python python-elevate python-pamela libinih libevdev linux-pam))
+ (synopsis "Authentication via face recognition")
+ (description "This package allows authentication via face recognition.")
+ (home-page "https://github.com/boltgolt/howdy")
+ (license license:expat)))
+
;;;
;;; Kernel documentation
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 7331c030d7..9d772f8891 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -934,7 +934,9 @@ (define-record-type* <login-configuration>
;; Allow empty passwords by default so that first-time users can log in when
;; the 'root' account has just been created.
(allow-empty-passwords? login-configuration-allow-empty-passwords?
- (default #t))) ;Boolean
+ (default #t)) ;Boolean
+ (howdy? login-configuration-howdy?
+ (default #f))) ;Boolean
(define (login-pam-service config)
"Return the list of PAM service needed for CONF."
diff --git a/gnu/services/desktop.scm b/gnu/services/desktop.scm
index e544656182..a09803c9e5 100644
--- a/gnu/services/desktop.scm
+++ b/gnu/services/desktop.scm
@@ -116,6 +116,7 @@ (define-module (gnu services desktop)
gvfs-service-type
colord-service-type
+ howdy-service-type
geoclue-application
geoclue-configuration
@@ -944,6 +945,43 @@ (define colord-service-type
interface to manage the color profiles of input and output devices such as
screens and scanners.")))
+
+;;;
+;;; Howdy.
+;;;
+
+(define %howdy-pam-services
+ ;(list (unix-pam-service "howdy"))
+ (let ((deny (pam-entry
+ (control "required")
+ (module "pam_deny.so"))))
+ (pam-service
+ (name "other")
+ (account (list deny))
+ (auth (list deny))
+ (password (list deny))
+ (session (list deny)))))
+
+(define-record-type* <howdy-configuration>
+ howdy-configuration make-howdy-configuration
+ howdy-configuration?
+ (howdy howdy-configuration-howdy
+ (default howdy)))
+
+;; This is mostly that you can run the howdy interface.
+;; Unfortunately, the actual face detection via opencv is not run as a
+;; service--and it's run as root!
+(define howdy-service-type
+ (let ((udisks-package (lambda (config)
+ (list (howdy-configuration-howdy config)))))
+ (service-type (name 'howdy)
+ (extensions
+ (list (service-extension polkit-service-type howdy-package)))
+ (default-value (howdy-configuration))
+ (description
+ "Run @command{howdy}, a system service that allows
+user authentication by facial recognition."))))
+
;;;
;;; UDisks.
diff --git a/gnu/system/pam.scm b/gnu/system/pam.scm
index 07b84b04ef..72228992cb 100644
--- a/gnu/system/pam.scm
+++ b/gnu/system/pam.scm
@@ -221,7 +221,7 @@ (define unix-pam-service
(control "required")
(module "pam_env.so"))))
(lambda* (name #:key allow-empty-passwords? allow-root? motd
- login-uid? gnupg?)
+ login-uid? gnupg? howdy?)
"Return a standard Unix-style PAM service for NAME. When
ALLOW-EMPTY-PASSWORDS? is true, allow empty passwords. When ALLOW-ROOT? is
true, allow root to run the command without authentication. When MOTD is
@@ -229,7 +229,8 @@ (define unix-pam-service
When LOGIN-UID? is true, require the 'pam_loginuid' module; that module sets
/proc/self/loginuid, which the libc 'getlogin' function relies on. When
GNUPG? is true, require the 'pam_gnupg.so' module; that module hands over
-the login password to 'gpg-agent'."
+the login password to 'gpg-agent'. When HOWDY? is true, require the
+pam_howdy module; that module does authentication."
;; See <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-example.html>.
(pam-service
(name name)
@@ -239,6 +240,11 @@ (define unix-pam-service
(control "sufficient")
(module "pam_rootok.so")))
'())
+ (if howdy?
+ (list (pam-entry
+ (control "sufficient")
+ (module (file-append howdy "/lib/security/pam_howdy.so"))))
+ '())
(list (if allow-empty-passwords?
(pam-entry
(control "required")
@@ -289,19 +295,20 @@ (define (rootok-pam-service command)
(password (list unix))
(session (list unix)))))
-(define* (base-pam-services #:key allow-empty-passwords?)
+(define* (base-pam-services #:key allow-empty-passwords? howdy?)
"Return the list of basic PAM services everyone would want."
;; TODO: Add other Shadow programs?
(append (list %pam-other-services)
;; These programs are setuid-root.
(map (cut unix-pam-service <>
- #:allow-empty-passwords? allow-empty-passwords?)
+ #:allow-empty-passwords? allow-empty-passwords? #:howdy? howdy?)
'("passwd" "chfn" "sudo"))
;; This is setuid-root, as well. Allow root to run "su" without
;; authenticating.
(list (unix-pam-service "su"
#:allow-empty-passwords? allow-empty-passwords?
+ #:howdy? howdy?
#:allow-root? #t))
;; These programs are not setuid-root, and we want root to be able
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment