Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CompileFlags:
CompilationDatabase: "."
Add: [-std=gnu++17]
Compiler: /usr/bin/c++
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ else()
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
URL_HASH SHA256=1f357c27ca988c3f7c6b4bf68a9395005ac6761f034046e9dde0896e3aba00e4
${GTEST_DOWNLOAD_ARGS}
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(googletest)
endif()
Expand Down Expand Up @@ -254,7 +254,7 @@ set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_GENERATOR "DEB")
# Runtime dependencies (not -dev packages) for Ubuntu 24.04
# OpenCV is statically linked, so we don't need libopencv-* packages
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libpam0g, libpam-runtime, v4l-utils, systemd, libgfortran5, libatlas3-base, libatomic1")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libpam0g, libpam-runtime, v4l-utils, libgfortran5, libatlas3-base, libatomic1")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "rocm-opencl-runtime | intel-opencl-icd | nvidia-opencl-icd-340 | nvidia-opencl-icd")

set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_SOURCE_DIR}/debian/postinst")
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ deb:
# Docker Build Targets
docker-amd64:
@echo "Building Docker (amd64)..."
@docker build --platform linux/amd64 -t linuxcampam:amd64 .
@docker build --platform linux/amd64 -t linuxcampam:amd64 -f docker/Dockerfile.amd64 .
@echo "Verifying..."
@docker run --rm --platform linux/amd64 --entrypoint /usr/bin/linuxcampam linuxcampam:amd64 help

Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@

> Face unlock for Linux, like Windows Hello™. Any webcam works; IR recommended for best results.

LinuxCamPAM provides seamless face unlock for Linux `sudo`, `login`, and login/lock screens (GDM, SDDM, LightDM) using OpenCV and AI models (YuNet/SFace). I built it to solve my own need for speed and reliability, supporting hardware acceleration (OpenCL, CUDA) and smart dual-camera configurations (IR + RGB). Virtually any USB webcam works out of the box.
LinuxCamPAM provides seamless face unlock for Linux `sudo`, `login`, and login/lock screens (GDM, SDDM, LightDM) using OpenCV and AI models (YuNet/SFace). I built it to solve my own need for speed and reliability, supporting hardware acceleration (OpenCL) and smart dual-camera configurations (IR + RGB). Virtually any USB webcam works out of the box.

## Why This Exists Now

There are existing tools like `howdy`, but they often suffer from being slow, lacking hardware acceleration, or failing completely with modern Python environments and D-Bus integration under Wayland.

- **Why OpenCV C++**: It runs instantly. No lag when typing `sudo`.
- **Why ONNX + YuNet + SFace**: Replaces the sluggish `dlib` backend for state-of-the-art face detection and recognition.
- **Native Backends**: Native OpenVINO is not enabled in the default static build to maintain a small package size (~15MB vs ~300MB+). OpenCL provides near-native performance for this use case without the bloat.

## Motivation

Expand Down
68 changes: 34 additions & 34 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,78 +1,78 @@
linuxcampam (0.9.7.2-1) unstable; urgency=medium

* Fix: Correctly parse [Auth] and [Capture] configuration sections
* Fix: Ensure documentation consistency regarding permissions and configuration
* Fix: Add preinst script to archive conflicting manual installations to improve update stability
* Hotfix release for v0.9.7 issues
* Fix: Correctly parse [Auth] and [Capture] configuration sections
* Fix: Ensure documentation consistency regarding permissions and configuration
* Fix: Add preinst script to archive conflicting manual installations to improve update stability
* Hotfix release for v0.9.7 issues

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Fri, 30 Jan 2026 00:20:00 +0100

linuxcampam (0.9.7-1) unstable; urgency=medium

* Bump version to 0.9.7 to match project version
* Refined build process and docker optimization
* Upgrade OpenCV to 4.12.0 (statically linked)
* Update Face Detection models (YuNet 2023mar)
* Bump version to 0.9.7 to match project version
* Refined build process and docker optimization
* Upgrade OpenCV to 4.12.0 (statically linked)
* Update Face Detection models (YuNet 2023mar)

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Thu, 29 Jan 2026 13:50:00 +0100

linuxcampam (0.9.6-1) unstable; urgency=medium

* Dynamic debug logging via CLI (`linuxcampam debug on/off`)
* Version command for CLI and daemon
* Fix missing systemd service file in debian package
* Fix missing PAM config file in debian package
* Remove bloated -dev dependencies from runtime
* Dynamic debug logging via CLI (`linuxcampam debug on/off`)
* Version command for CLI and daemon
* Fix missing systemd service file in debian package
* Fix missing PAM config file in debian package
* Remove bloated -dev dependencies from runtime

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Tue, 07 Jan 2026 13:30:00 +0100

linuxcampam (0.9.5-1) unstable; urgency=medium

* GPU sync option to prevent OpenCL hangs on some AMD GPUs
* Fix missing i386/riscv64 debs in releases
* Add .dockerignore for cleaner cross-arch builds
* Auto-clear OpenCL kernel cache on startup to prevent upgrade issues
* GPU sync option to prevent OpenCL hangs on some AMD GPUs
* Fix missing i386/riscv64 debs in releases
* Add .dockerignore for cleaner cross-arch builds
* Auto-clear OpenCL kernel cache on startup to prevent upgrade issues

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Mon, 06 Jan 2026 15:00:00 +0100

linuxcampam (0.9.4-1) unstable; urgency=medium

* Feat: Implement security rate-limiting and lockout mechanism to prevent brute-force attacks.
* Feat: Add `show-config` CLI command for runtime configuration inspection.
* Security: Add comprehensive regression tests for lockout logic.
* Feat: Implement security rate-limiting and lockout mechanism to prevent brute-force attacks.
* Feat: Add `show-config` CLI command for runtime configuration inspection.
* Security: Add comprehensive regression tests for lockout logic.

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Sun, 05 Jan 2026 10:56:00 +0100

linuxcampam (0.9.3.3-1) unstable; urgency=medium

* Fix: Include AI models in Docker-based cross-arch builds (i386, riscv64).
* Fix: Preserve versioned .deb filenames in release artifacts.
* Note: Version bumped to clean test the release workflow.
* Fix: Include AI models in Docker-based cross-arch builds (i386, riscv64).
* Fix: Preserve versioned .deb filenames in release artifacts.
* Note: Version bumped to clean test the release workflow.

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Fri, 03 Jan 2026 21:41:00 +0100

linuxcampam (0.9.3.1-1) unstable; urgency=medium

* Multi-arch support (AARCH64, i386, RISC-V).
* Update Dockerfiles for packaging.
* Secure release workflow.
* Multi-arch support (AARCH64, i386, RISC-V).
* Update Dockerfiles for packaging.
* Secure release workflow.

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Sat, 03 Jan 2026 02:20:00 +0100

linuxcampam (0.9.2-1) unstable; urgency=medium

* Portable camera detection via V4L2 enumeration (no hardcoded paths).
* Portable GPU detection via sysfs (lspci no longer required).
* Setup fails with clear error if no cameras detected.
* Better IR emitter script guidance for non-Rust systems.
* Portable camera detection via V4L2 enumeration (no hardcoded paths).
* Portable GPU detection via sysfs (lspci no longer required).
* Setup fails with clear error if no cameras detected.
* Better IR emitter script guidance for non-Rust systems.

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Sat, 28 Dec 2025 23:35:00 +0100

linuxcampam (1.0.0-1) unstable; urgency=medium

* Initial release.
* Bundled stable ONNX models (2022mar/2021dec).
* Feature: Automatic PAM configuration via pam-auth-update.
* Feature: Rusticl OpenCL support enabled by default.
* Initial release.
* Bundled stable ONNX models (2022mar/2021dec).
* Feature: Automatic PAM configuration via pam-auth-update.
* Feature: Rusticl OpenCL support enabled by default.

-- Vladimir Orlinski <contact.github.submerge594@slmails.com> Fri, 19 Dec 2025 20:10:00 +0100
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Homepage: https://github.com/Vladush/LinuxCamPAM

Package: linuxcampam
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, libpam0g, systemd, v4l-utils
Depends: ${shlibs:Depends}, ${misc:Depends}, libpam0g, libpam-runtime, v4l-utils
Recommends: rocm-opencl-runtime | intel-opencl-icd | nvidia-opencl-icd-340 | nvidia-opencl-icd
Description: Face Authentication PAM Module (LinuxCamPAM)
LinuxCamPAM is a Pluggable Authentication Module (PAM) that provides face
Expand Down
1 change: 1 addition & 0 deletions docker/Dockerfile.aarch64
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ RUN dpkg-buildpackage -b -uc -us

# Verify man pages are in the generated deb
RUN dpkg -c ../linuxcampam_*.deb | grep share/man
RUN apt-get update && apt-get install -y ../linuxcampam_*.deb
3 changes: 1 addition & 2 deletions docker/Dockerfile.riscv64
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,10 @@ FROM debian:sid-slim
ENV DEBIAN_FRONTEND=noninteractive

# Install Runtime Dependencies
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y --no-install-recommends \
libpam0g \
libpam-runtime \
v4l-utils \
systemd \
libgfortran5 \
libgomp1 \
libatomic1 \
Expand Down
2 changes: 1 addition & 1 deletion man/linuxcampam.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ The file is in standard INI format. Sections and keys are case-insensitive.

**provider_priority** = *LIST*
: Comma-separated list of hardware backends to try.
(Default: `OpenCL,OpenVINO,CUDA,CPU`).
(Default: `OpenCL,CPU`).

**camera_path_ir** = *PATH*
: Path to IR camera (e.g., `/dev/video2`).
Expand Down
15 changes: 12 additions & 3 deletions scripts/install_ir_emitter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ if [ -f "Cargo.toml" ]; then
echo " sudo $0 $LATEST_TAG"
echo ""

echo "=== Installation Complete ==="
IRE_VERSION=$(linux-enable-ir-emitter -V 2>/dev/null || true)
if [ -n "$IRE_VERSION" ]; then
echo "=== Installation Complete ($IRE_VERSION) ==="
else
echo "=== Installation Complete ==="
fi
exit 0
fi

Expand All @@ -158,5 +163,9 @@ else
ninja -C build
ninja -C build install
fi

echo "=== Installation Complete ==="
IRE_VERSION=$(linux-enable-ir-emitter -V 2>/dev/null || true)
if [ -n "$IRE_VERSION" ]; then
echo "=== Installation Complete ($IRE_VERSION) ==="
else
echo "=== Installation Complete ==="
fi
40 changes: 25 additions & 15 deletions scripts/setup_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ if [ -n "$IR_CAM" ]; then
chmod +x /tmp/install_ir_emitter.sh
/tmp/install_ir_emitter.sh
echo ""
echo "[Setup] linux-enable-ir-emitter installed successfully."
IRE_VERSION=$(linux-enable-ir-emitter -V 2>/dev/null || echo "linux-enable-ir-emitter")
echo "[Setup] $IRE_VERSION installed successfully."
read -p "[Setup] Would you like to configure the IR emitter now? [Y/n] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
Expand Down Expand Up @@ -127,20 +128,29 @@ elif [ -n "$RGB_CAM" ]; then
else
echo ""
echo "=============================================="
echo "[Setup] FATAL: No cameras detected!"
echo "=============================================="
echo ""
echo "LinuxCamPAM requires at least one camera to function."
echo ""
echo "Troubleshooting:"
echo " 1. Check if cameras are connected: ls -la /dev/video*"
echo " 2. Check camera details: v4l2-ctl --list-devices"
echo " 3. Verify permissions: groups \$USER | grep video"
echo ""
echo "If you have a camera but it wasn't detected, please"
echo "manually configure it in: $CONFIG_FILE"
echo ""
exit 1
if [ ! -t 0 ] || [ "$DEBIAN_FRONTEND" = "noninteractive" ]; then
echo "[Setup] WARNING: No cameras detected!"
echo "=============================================="
echo ""
echo "Running non-interactively. Ignoring missing camera to continue setup."
echo "Please manually configure cameras later in: $CONFIG_FILE"
echo ""
else
echo "[Setup] FATAL: No cameras detected!"
echo "=============================================="
echo ""
echo "LinuxCamPAM requires at least one camera to function."
echo ""
echo "Troubleshooting:"
echo " 1. Check if cameras are connected: ls -la /dev/video*"
echo " 2. Check camera details: v4l2-ctl --list-devices"
echo " 3. Verify permissions: groups \$USER | grep video"
echo ""
echo "If you have a camera but it wasn't detected, please"
echo "manually configure it in: $CONFIG_FILE"
echo ""
exit 1
fi
fi

echo "[Setup] Checking System RAM and CPU..."
Expand Down
82 changes: 52 additions & 30 deletions src/service/auth_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,26 @@ bool AuthEngine::init(const fs::path &config_path) {
void AuthEngine::initializeActiveCameras() {
if (active_cameras.empty()) {
active_cameras.reserve(config.camera_defs.size());
std::transform(config.camera_defs.begin(), config.camera_defs.end(),
std::back_inserter(active_cameras), [&](const auto &def) {
ActiveCamera ac;
ac.config = def; // Copy config
log_info("Initializing Camera: " + def.id + " (" +
def.type + ") at " + def.path);
ac.cam = camera_factory_(def);
return ac;
});
std::transform(
config.camera_defs.begin(), config.camera_defs.end(),
std::back_inserter(active_cameras), [&](const auto &def) {
ActiveCamera ac;
ac.config = def; // Copy config
log_info("Initializing Camera: " + def.id + " (" + def.type +
") at " + def.path);
if (def.type == "ir") {
std::string ir_ver = linuxcampam::getIREmitterVersion(
config.ir_emitter_path.string());
if (!ir_ver.empty()) {
log_info("IR Emitter Engine: " + config.ir_emitter_path.string() +
" (" + ir_ver + ")");
} else {
log_info("IR Emitter Engine: Not Installed");
}
}
ac.cam = camera_factory_(def);
return ac;
});
}
}

Expand All @@ -118,22 +129,8 @@ bool AuthEngine::loadModels() {
int backend_id = cv::dnn::DNN_BACKEND_OPENCV;
int target_id = cv::dnn::DNN_TARGET_CPU;

for (const auto &prov : config.provider_priority) {
if (prov == "CUDA") {
/*
if (cv::cuda::getCudaEnabledDeviceCount() > 0) {
backend_id = cv::dnn::DNN_BACKEND_CUDA;
target_id = cv::dnn::DNN_TARGET_CUDA;
log_info("Selecting CUDA Backend.");
break;
}
*/
} else if (prov == "OpenVINO") {
backend_id = cv::dnn::DNN_BACKEND_INFERENCE_ENGINE;
target_id = cv::dnn::DNN_TARGET_CPU;
log_info("Selecting OpenVINO Backend.");
break;
} else if (prov == "OpenCL") {
for (std::string_view prov : config.provider_priority) {
if (prov == "OpenCL") {
if (cv::ocl::haveOpenCL()) {
cv::ocl::setUseOpenCL(true);
backend_id = cv::dnn::DNN_BACKEND_OPENCV;
Expand All @@ -142,13 +139,19 @@ bool AuthEngine::loadModels() {
// Log the OpenCL device name for assurance
cv::ocl::Device dev = cv::ocl::Device::getDefault();
log_info("Hardware Device: " + dev.name() + " " + dev.version());
break;
} else {
log_warn("OpenCL requested but not "
"detected. Falling back to CPU.");
backend_id = cv::dnn::DNN_BACKEND_OPENCV;
target_id = cv::dnn::DNN_TARGET_CPU;
log_warn("OpenCL requested but not detected. Trying next provider "
"fallback.");
}
} else if (prov == "CPU") {
backend_id = cv::dnn::DNN_BACKEND_OPENCV;
target_id = cv::dnn::DNN_TARGET_CPU;
log_info("Selecting CPU Backend.");
break;
} else {
log_warn("Unsupported or unrecognized backend requested: " +
std::string(prov));
}
}

Expand Down Expand Up @@ -1042,6 +1045,25 @@ void AuthEngine::recordAuthAttempt(const std::string &username, bool success) {
}
}

std::string AuthEngine::getActiveProvider() const {
for (std::string_view prov : config.provider_priority) {
if (prov == "OpenCL") {
if (cv::ocl::haveOpenCL()) {
return "OpenCL";
}
} else if (prov == "CPU") {
return "CPU";
}
}
return "CPU (Default)";
}

std::string AuthEngine::getConfigString() const {
return config.toString();
std::string config_output = config.toString();

std::string provider = getActiveProvider();
config_output +=
" Active Provider: " + (provider.empty() ? "None" : provider) + "\n\n";

return config_output;
}
Loading
Loading