diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 781f575..fa9550a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -30,6 +30,26 @@ jobs: mkdir -p _site/docs cp website/* _site/ cp -r book/book/* _site/docs/ + - name: Download APT repository from latest release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + mkdir -p /tmp/apt-download + + # Check if latest release has the apt-repo asset + if gh release view --json assets --jq '.assets[].name' --repo "${{ github.repository }}" 2>/dev/null | grep -qx 'apt-repo.tar.gz'; then + # Asset exists — download must succeed or we fail to avoid wiping the existing APT repo + gh release download --pattern 'apt-repo.tar.gz' --dir /tmp/apt-download --repo "${{ github.repository }}" + echo "Downloaded apt-repo.tar.gz from latest release" + mkdir -p _site/apt + tar -xzf /tmp/apt-download/apt-repo.tar.gz -C _site/apt/ + echo "=== APT repo files included in site ===" + find _site/apt -type f | sort + else + echo "No apt-repo.tar.gz found in any release. Deploying docs only." + echo "This is expected before the first stable release with APT publishing." + fi - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54df4cb..751e757 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: permissions: contents: write + actions: write env: # ONNX Runtime version to bundle in .deb and .rpm packages. @@ -20,18 +21,7 @@ jobs: - uses: actions/checkout@v4 - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - libv4l-dev \ - libpam0g-dev \ - pkg-config \ - libssl-dev \ - clang \ - libxkbcommon-dev \ - libwayland-dev \ - libtss2-dev \ - libtss2-tcti-tabrmd-dev + run: .github/workflows/scripts/install-ubuntu-deps.sh - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -96,18 +86,7 @@ jobs: - uses: actions/checkout@v4 - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - libv4l-dev \ - libpam0g-dev \ - pkg-config \ - libssl-dev \ - clang \ - libxkbcommon-dev \ - libwayland-dev \ - libtss2-dev \ - libtss2-tcti-tabrmd-dev + run: .github/workflows/scripts/install-ubuntu-deps.sh - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -131,108 +110,97 @@ jobs: id: version run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - - name: Build .deb package - run: | - PKG_VERSION="${{ steps.version.outputs.VERSION }}" - PKG_DIR="facelock_${PKG_VERSION}_amd64" - - mkdir -p "${PKG_DIR}/DEBIAN" - mkdir -p "${PKG_DIR}/usr/bin" - mkdir -p "${PKG_DIR}/lib/security" - mkdir -p "${PKG_DIR}/etc/facelock" - mkdir -p "${PKG_DIR}/usr/lib/systemd/system" - mkdir -p "${PKG_DIR}/usr/lib/sysusers.d" - mkdir -p "${PKG_DIR}/usr/lib/tmpfiles.d" - mkdir -p "${PKG_DIR}/usr/lib/facelock" - mkdir -p "${PKG_DIR}/usr/share/pam-configs" - mkdir -p "${PKG_DIR}/usr/share/dbus-1/system.d" - mkdir -p "${PKG_DIR}/usr/share/dbus-1/system-services" - mkdir -p "${PKG_DIR}/usr/share/facelock/quirks.d" - mkdir -p "${PKG_DIR}/usr/share/doc/facelock" - - # Control file - cat > "${PKG_DIR}/DEBIAN/control" < - Depends: libpam-runtime, dbus - Section: admin - Priority: optional - Homepage: https://github.com/tyvsmith/facelock - Description: Face authentication for Linux PAM - Facelock provides Windows Hello-style face authentication for Linux - using IR anti-spoofing, ONNX inference, and PAM integration. - . - After installation, run 'sudo facelock setup' to download face - recognition models, then 'sudo facelock enroll' to register your face. - CTRL - # Remove leading whitespace from heredoc - sed -i 's/^ //' "${PKG_DIR}/DEBIAN/control" - - # postinst and prerm scripts - cp dist/debian/postinst "${PKG_DIR}/DEBIAN/postinst" - chmod 755 "${PKG_DIR}/DEBIAN/postinst" - cp dist/debian/prerm "${PKG_DIR}/DEBIAN/prerm" - chmod 755 "${PKG_DIR}/DEBIAN/prerm" - - # Binaries - install -m755 target/release/facelock "${PKG_DIR}/usr/bin/facelock" - if [ -f target/release/facelock-polkit-agent ]; then - install -m755 target/release/facelock-polkit-agent "${PKG_DIR}/usr/bin/facelock-polkit-agent" - fi - - # PAM module - install -m755 target/release/libpam_facelock.so "${PKG_DIR}/lib/security/pam_facelock.so" + - name: Build .deb package (legacy) + run: .github/workflows/scripts/build-deb.sh "${{ steps.version.outputs.VERSION }}" "legacy" - # Configuration - install -m644 config/facelock.toml "${PKG_DIR}/etc/facelock/config.toml" + - name: Validate .deb contents + run: .github/workflows/scripts/validate-deb.sh facelock_*.deb - # Quirks database - install -m644 -t "${PKG_DIR}/usr/share/facelock/quirks.d/" config/quirks.d/*.toml + - name: Upload .deb artifact + uses: actions/upload-artifact@v4 + with: + name: release-deb-legacy + path: facelock_*.deb - # systemd units - install -m644 systemd/facelock-daemon.service "${PKG_DIR}/usr/lib/systemd/system/facelock-daemon.service" + - name: Upload .deb to release + uses: softprops/action-gh-release@v2 + with: + files: facelock_*.deb - # D-Bus policy and activation service - install -m644 dbus/org.facelock.Daemon.conf "${PKG_DIR}/usr/share/dbus-1/system.d/org.facelock.Daemon.conf" - install -m644 dbus/org.facelock.Daemon.service "${PKG_DIR}/usr/share/dbus-1/system-services/org.facelock.Daemon.service" + build-deb-tpm: + name: Build Debian Package (TPM) + runs-on: ubuntu-latest + needs: [build, download-ort] + container: + image: debian:trixie + steps: + - uses: actions/checkout@v4 - # sysusers.d and tmpfiles.d - install -m644 dist/facelock.sysusers "${PKG_DIR}/usr/lib/sysusers.d/facelock.conf" - install -m644 dist/facelock.tmpfiles "${PKG_DIR}/usr/lib/tmpfiles.d/facelock.conf" + - name: Install system dependencies + run: | + apt-get update + apt-get install -y \ + build-essential \ + curl \ + pkg-config \ + libssl-dev \ + clang \ + libv4l-dev \ + libpam0g-dev \ + libxkbcommon-dev \ + libwayland-dev \ + libtss2-dev \ + libtss2-tcti-tabrmd-dev - # pam-auth-update profile - if [ -f dist/debian/pam-auth-update ]; then - install -m644 dist/debian/pam-auth-update "${PKG_DIR}/usr/share/pam-configs/facelock" + - name: Detect libtss2-esys package name and version + id: tss + run: | + TSS_PKG=$(dpkg -l 'libtss2-esys*' 2>/dev/null | awk '/^ii/{print $2}' | head -1) + TSS_VER=$(dpkg -l 'libtss2-esys*' 2>/dev/null | awk '/^ii/{print $3}' | head -1) + if [ -z "$TSS_PKG" ]; then + echo "ERROR: No libtss2-esys package found" + exit 1 fi + echo "TSS_PKG=${TSS_PKG}" >> "$GITHUB_OUTPUT" + echo "TSS_VER=${TSS_VER}" >> "$GITHUB_OUTPUT" + echo "Found: ${TSS_PKG} ${TSS_VER}" - # Bundled CPU ONNX Runtime - if [ -f onnxruntime/lib/libonnxruntime.so ]; then - install -m755 onnxruntime/lib/libonnxruntime.so "${PKG_DIR}/usr/lib/facelock/libonnxruntime.so" - fi + - name: Install Rust toolchain + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: Build release with TPM + run: | + . "$HOME/.cargo/env" + cargo build --release --workspace --features tpm + + - name: Download ORT bundle + uses: actions/download-artifact@v4 + with: + name: onnxruntime-bundle + path: onnxruntime + + - name: Verify ORT bundle exists + run: test -s onnxruntime/lib/libonnxruntime.so - # Copyright - install -m644 dist/debian/copyright "${PKG_DIR}/usr/share/doc/facelock/copyright" + - name: Extract version from tag + id: version + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - dpkg-deb --build "${PKG_DIR}" + - name: Build .deb package (TPM) + run: | + . "$HOME/.cargo/env" + .github/workflows/scripts/build-deb.sh "${{ steps.version.outputs.VERSION }}" "tpm" "${{ steps.tss.outputs.TSS_PKG }} (>= ${{ steps.tss.outputs.TSS_VER }})" - name: Validate .deb contents - run: | - DEB_FILE=$(ls facelock_*.deb) - echo "=== .deb contents ===" - dpkg-deb -c "$DEB_FILE" - echo "" - echo "=== Checking required files ===" - dpkg-deb -c "$DEB_FILE" | grep -q "usr/bin/facelock" && echo "OK: facelock binary" - dpkg-deb -c "$DEB_FILE" | grep -q "lib/security/pam_facelock.so" && echo "OK: PAM module" - dpkg-deb -c "$DEB_FILE" | grep -q "etc/facelock/config.toml" && echo "OK: config" - dpkg-deb -c "$DEB_FILE" | grep -q "dbus-1/system.d/org.facelock.Daemon.conf" && echo "OK: D-Bus policy" - dpkg-deb -c "$DEB_FILE" | grep -q "dbus-1/system-services/org.facelock.Daemon.service" && echo "OK: D-Bus activation" - dpkg-deb -c "$DEB_FILE" | grep -q "sysusers.d/facelock.conf" && echo "OK: sysusers" - dpkg-deb -c "$DEB_FILE" | grep -q "tmpfiles.d/facelock.conf" && echo "OK: tmpfiles" - dpkg-deb -c "$DEB_FILE" | grep -q "usr/lib/facelock/libonnxruntime.so" && echo "OK: bundled ORT" - echo "=== .deb validation passed ===" + run: .github/workflows/scripts/validate-deb.sh facelock_*.deb + + - name: Upload .deb artifact + uses: actions/upload-artifact@v4 + with: + name: release-deb-tpm + path: facelock_*.deb - name: Upload .deb to release uses: softprops/action-gh-release@v2 @@ -275,57 +243,13 @@ jobs: run: cargo build --release --workspace --features tpm - name: Build RPM - run: | - PKG_VERSION_RAW="${{ steps.version.outputs.VERSION }}" - PKG_VERSION="$PKG_VERSION_RAW" - PKG_RELEASE='1%{?dist}' - - # RPM Version cannot contain '-'. For prereleases, keep the base - # version and move the prerelease marker into Release so it sorts - # before final releases. - if [ "${PKG_VERSION_RAW#*-}" != "$PKG_VERSION_RAW" ]; then - PRERELEASE_SUFFIX="${PKG_VERSION_RAW#*-}" - PKG_VERSION="${PKG_VERSION_RAW%%-*}" - PRERELEASE_SUFFIX="${PRERELEASE_SUFFIX//-/.}" - PKG_RELEASE="0.${PRERELEASE_SUFFIX}%{?dist}" - fi - - mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} - - # Copy spec file and set version/release - cp dist/facelock.spec ~/rpmbuild/SPECS/facelock.spec - sed -i "s|^Version:.*|Version: ${PKG_VERSION}|" ~/rpmbuild/SPECS/facelock.spec - sed -i "s|^Release:.*|Release: ${PKG_RELEASE}|" ~/rpmbuild/SPECS/facelock.spec - - # Build source tarball expected by Source0 so rpmbuild can run the - # full %prep/%build/%install pipeline. - tar --exclude=.git --exclude=target \ - --transform "s|^|facelock-${PKG_VERSION}/|" \ - -czf "${HOME}/rpmbuild/SOURCES/facelock-${PKG_VERSION}.tar.gz" . - - # Build RPM using spec-defined build/install steps. - rpmbuild --define "_topdir $HOME/rpmbuild" \ - -bb ~/rpmbuild/SPECS/facelock.spec + run: .github/workflows/scripts/build-rpm.sh "${{ steps.version.outputs.VERSION }}" - name: Copy RPM to workspace run: cp ~/rpmbuild/RPMS/x86_64/*.rpm ./ 2>/dev/null || cp ~/rpmbuild/RPMS/**/*.rpm ./ 2>/dev/null || true - name: Validate .rpm contents - run: | - RPM_FILE=$(ls *.rpm | head -1) - echo "=== .rpm contents ===" - rpm -qlp "$RPM_FILE" - echo "" - echo "=== Checking required files ===" - rpm -qlp "$RPM_FILE" | grep -q "usr/bin/facelock" && echo "OK: facelock binary" - rpm -qlp "$RPM_FILE" | grep -q "security/pam_facelock.so" && echo "OK: PAM module" - rpm -qlp "$RPM_FILE" | grep -q "etc/facelock/config.toml" && echo "OK: config" - rpm -qlp "$RPM_FILE" | grep -q "dbus-1/system.d/org.facelock.Daemon.conf" && echo "OK: D-Bus policy" - rpm -qlp "$RPM_FILE" | grep -q "dbus-1/system-services/org.facelock.Daemon.service" && echo "OK: D-Bus activation" - rpm -qlp "$RPM_FILE" | grep -q "sysusers.d/facelock.conf" && echo "OK: sysusers" - rpm -qlp "$RPM_FILE" | grep -q "tmpfiles.d/facelock.conf" && echo "OK: tmpfiles" - rpm -qlp "$RPM_FILE" | grep -q "authselect/vendor/facelock" && echo "OK: authselect" - echo "=== .rpm validation passed ===" + run: .github/workflows/scripts/validate-rpm.sh "$(ls *.rpm | head -1)" - name: Upload .rpm to release uses: softprops/action-gh-release@v2 @@ -357,7 +281,7 @@ jobs: publish-aur: name: Publish to AUR runs-on: ubuntu-latest - needs: [build, build-deb, build-rpm] + needs: [build, build-deb, build-deb-tpm, build-rpm] if: ${{ !contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') }} steps: - uses: actions/checkout@v4 @@ -375,37 +299,7 @@ jobs: - name: Publish to AUR env: AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} - run: | - if [ -z "$AUR_SSH_KEY" ]; then - echo "AUR_SSH_KEY secret not configured. Skipping AUR publish." - echo "See docs/releasing.md for setup instructions." - exit 0 - fi - - mkdir -p ~/.ssh - echo "$AUR_SSH_KEY" > ~/.ssh/aur - chmod 600 ~/.ssh/aur - echo "Host aur.archlinux.org" >> ~/.ssh/config - echo " IdentityFile ~/.ssh/aur" >> ~/.ssh/config - echo " User aur" >> ~/.ssh/config - ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null - - git clone ssh://aur@aur.archlinux.org/facelock.git aur-facelock - - cp dist/PKGBUILD aur-facelock/PKGBUILD - cp dist/facelock.install aur-facelock/facelock.install - sed -i "s/^pkgver=.*/pkgver=${{ steps.version.outputs.VERSION }}/" aur-facelock/PKGBUILD - sed -i "s/sha256sums=('SKIP')/sha256sums=('${{ steps.checksum.outputs.SHA256 }}')/" aur-facelock/PKGBUILD - - cd aur-facelock - docker run --rm -v "$(pwd):/pkg" archlinux:base-devel bash -c \ - "pacman -Sy --noconfirm pacman-contrib && cd /pkg && makepkg --printsrcinfo > .SRCINFO" - - git config user.name "facelock-bot" - git config user.email "facelock@users.noreply.github.com" - git add PKGBUILD facelock.install .SRCINFO - git diff --cached --quiet || git commit -m "Update to v${{ steps.version.outputs.VERSION }}" - git push + run: .github/workflows/scripts/publish-aur.sh "${{ steps.version.outputs.VERSION }}" "${{ steps.checksum.outputs.SHA256 }}" publish-copr: name: Trigger COPR Build @@ -413,15 +307,67 @@ jobs: needs: [build, build-rpm] if: ${{ !contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') }} steps: + - uses: actions/checkout@v4 + - name: Trigger COPR rebuild env: COPR_WEBHOOK_URL: ${{ secrets.COPR_WEBHOOK_URL }} + run: .github/workflows/scripts/publish-copr.sh + + publish-apt: + name: Publish APT Repository + runs-on: ubuntu-latest + needs: [build-deb, build-deb-tpm] + if: ${{ !contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta') && !contains(github.ref_name, 'rc') }} + steps: + - uses: actions/checkout@v4 + + - name: Install tools + run: sudo apt-get update && sudo apt-get install -y reprepro gnupg + + - name: Download TPM .deb artifact + uses: actions/download-artifact@v4 + with: + name: release-deb-tpm + path: debs/tpm + + - name: Download legacy .deb artifact + uses: actions/download-artifact@v4 + with: + name: release-deb-legacy + path: debs/legacy + + - name: Build APT repository + env: + APT_GPG_PRIVATE_KEY: ${{ secrets.APT_GPG_PRIVATE_KEY }} + APT_GPG_PASSPHRASE: ${{ secrets.APT_GPG_PASSPHRASE }} run: | - if [ -z "$COPR_WEBHOOK_URL" ]; then - echo "COPR_WEBHOOK_URL secret not configured. Skipping COPR trigger." - echo "See docs/releasing.md for setup instructions." - exit 0 - fi - curl -X POST "$COPR_WEBHOOK_URL" \ - --fail --silent --show-error - echo "COPR build triggered for ${{ github.ref_name }}" + TPM_DEB=$(ls debs/tpm/facelock_*.deb) + LEGACY_DEB=$(ls debs/legacy/facelock_*.deb) + .github/workflows/scripts/publish-apt.sh "${TPM_DEB}" "${LEGACY_DEB}" "$(pwd)/apt-repo" + + - name: Package APT repo artifact + run: | + REPO_DIR="$(pwd)/apt-repo" + # Remove reprepro internals (conf/, db/) — only ship dists/, pool/, and keyring + tar -czf apt-repo.tar.gz -C "${REPO_DIR}" \ + --exclude='conf' --exclude='db' \ + dists pool tysmith-archive-keyring.gpg + echo "APT repo tarball: $(du -h apt-repo.tar.gz | cut -f1)" + + - name: Upload APT repo to release + uses: softprops/action-gh-release@v2 + with: + files: apt-repo.tar.gz + + trigger-pages: + name: Trigger Pages Rebuild + runs-on: ubuntu-latest + needs: [publish-apt] + steps: + - name: Trigger GitHub Pages workflow + env: + GH_TOKEN: ${{ github.token }} + run: | + gh workflow run pages.yml --repo "${{ github.repository }}" + echo "Triggered Pages rebuild to include updated APT repository" diff --git a/.github/workflows/scripts/build-deb.sh b/.github/workflows/scripts/build-deb.sh new file mode 100755 index 0000000..83c7f10 --- /dev/null +++ b/.github/workflows/scripts/build-deb.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +set -euo pipefail + +VERSION="${1:?Usage: build-deb.sh [EXTRA_DEPENDS]}" +VARIANT="${2:?Usage: build-deb.sh [EXTRA_DEPENDS]}" +EXTRA_DEPENDS="${3:-}" + +# Set version suffix and description based on variant +case "$VARIANT" in + legacy) + PKG_VERSION="${VERSION}-1~legacy1" + DESCRIPTION="Face authentication for Linux PAM (legacy, no TPM)" + DESCRIPTION_LONG=" Facelock provides Windows Hello-style face authentication for Linux + using IR anti-spoofing, ONNX inference, and PAM integration. + . + This is the legacy build without TPM support, for systems that lack + libtss2-esys >= 4.1.3 (e.g. Ubuntu 24.04, Debian bookworm). + . + After installation, run 'sudo facelock setup' to download face + recognition models, then 'sudo facelock enroll' to register your face." + DEPENDS="libpam-runtime, dbus" + ;; + tpm) + PKG_VERSION="${VERSION}-1" + DESCRIPTION="Face authentication for Linux PAM (TPM enabled)" + DESCRIPTION_LONG=" Facelock provides Windows Hello-style face authentication for Linux + using IR anti-spoofing, ONNX inference, and PAM integration. + . + This build includes TPM 2.0 support for hardware-sealed face + template encryption. Requires libtss2-esys >= 4.1.3. + . + After installation, run 'sudo facelock setup' to download face + recognition models, then 'sudo facelock enroll' to register your face." + DEPENDS="libpam-runtime, dbus" + if [ -n "$EXTRA_DEPENDS" ]; then + DEPENDS="${DEPENDS}, ${EXTRA_DEPENDS}" + fi + ;; + *) + echo "ERROR: Unknown variant '$VARIANT'. Use 'legacy' or 'tpm'." + exit 1 + ;; +esac + +PKG_DIR="facelock_${PKG_VERSION}_amd64" +echo "=== Building .deb package (${VARIANT}) ===" +echo "Version: ${PKG_VERSION}" +echo "Package dir: ${PKG_DIR}" + +# Create directory tree +mkdir -p "${PKG_DIR}/DEBIAN" +mkdir -p "${PKG_DIR}/usr/bin" +mkdir -p "${PKG_DIR}/lib/security" +mkdir -p "${PKG_DIR}/etc/facelock" +mkdir -p "${PKG_DIR}/usr/lib/systemd/system" +mkdir -p "${PKG_DIR}/usr/lib/sysusers.d" +mkdir -p "${PKG_DIR}/usr/lib/tmpfiles.d" +mkdir -p "${PKG_DIR}/usr/lib/facelock" +mkdir -p "${PKG_DIR}/usr/share/pam-configs" +mkdir -p "${PKG_DIR}/usr/share/dbus-1/system.d" +mkdir -p "${PKG_DIR}/usr/share/dbus-1/system-services" +mkdir -p "${PKG_DIR}/usr/share/facelock/quirks.d" +mkdir -p "${PKG_DIR}/usr/share/doc/facelock" + +# Control file +cat > "${PKG_DIR}/DEBIAN/control" < +Depends: ${DEPENDS} +Section: admin +Priority: optional +Homepage: https://github.com/tyvsmith/facelock +Description: ${DESCRIPTION} +${DESCRIPTION_LONG} +CTRL + +# postinst and prerm scripts +cp dist/debian/postinst "${PKG_DIR}/DEBIAN/postinst" +chmod 755 "${PKG_DIR}/DEBIAN/postinst" +cp dist/debian/prerm "${PKG_DIR}/DEBIAN/prerm" +chmod 755 "${PKG_DIR}/DEBIAN/prerm" + +# Binaries +install -m755 target/release/facelock "${PKG_DIR}/usr/bin/facelock" +if [ -f target/release/facelock-polkit-agent ]; then + install -m755 target/release/facelock-polkit-agent "${PKG_DIR}/usr/bin/facelock-polkit-agent" +fi + +# PAM module +install -m755 target/release/libpam_facelock.so "${PKG_DIR}/lib/security/pam_facelock.so" + +# Configuration +install -m644 config/facelock.toml "${PKG_DIR}/etc/facelock/config.toml" + +# Quirks database +install -m644 -t "${PKG_DIR}/usr/share/facelock/quirks.d/" config/quirks.d/*.toml + +# systemd units +install -m644 systemd/facelock-daemon.service "${PKG_DIR}/usr/lib/systemd/system/facelock-daemon.service" + +# D-Bus policy and activation service +install -m644 dbus/org.facelock.Daemon.conf "${PKG_DIR}/usr/share/dbus-1/system.d/org.facelock.Daemon.conf" +install -m644 dbus/org.facelock.Daemon.service "${PKG_DIR}/usr/share/dbus-1/system-services/org.facelock.Daemon.service" + +# sysusers.d and tmpfiles.d +install -m644 dist/facelock.sysusers "${PKG_DIR}/usr/lib/sysusers.d/facelock.conf" +install -m644 dist/facelock.tmpfiles "${PKG_DIR}/usr/lib/tmpfiles.d/facelock.conf" + +# pam-auth-update profile +if [ -f dist/debian/pam-auth-update ]; then + install -m644 dist/debian/pam-auth-update "${PKG_DIR}/usr/share/pam-configs/facelock" +fi + +# Bundled CPU ONNX Runtime +if [ -f onnxruntime/lib/libonnxruntime.so ]; then + install -m755 onnxruntime/lib/libonnxruntime.so "${PKG_DIR}/usr/lib/facelock/libonnxruntime.so" +fi + +# Copyright +install -m644 dist/debian/copyright "${PKG_DIR}/usr/share/doc/facelock/copyright" + +dpkg-deb --build "${PKG_DIR}" + +echo "=== .deb package built: ${PKG_DIR}.deb ===" diff --git a/.github/workflows/scripts/build-rpm.sh b/.github/workflows/scripts/build-rpm.sh new file mode 100755 index 0000000..231b0bc --- /dev/null +++ b/.github/workflows/scripts/build-rpm.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +PKG_VERSION_RAW="${1:?Usage: build-rpm.sh }" +PKG_VERSION="$PKG_VERSION_RAW" +PKG_RELEASE='1%{?dist}' + +echo "=== Building RPM package ===" +echo "Raw version: ${PKG_VERSION_RAW}" + +# RPM Version cannot contain '-'. For prereleases, keep the base +# version and move the prerelease marker into Release so it sorts +# before final releases. +if [ "${PKG_VERSION_RAW#*-}" != "$PKG_VERSION_RAW" ]; then + PRERELEASE_SUFFIX="${PKG_VERSION_RAW#*-}" + PKG_VERSION="${PKG_VERSION_RAW%%-*}" + PRERELEASE_SUFFIX="${PRERELEASE_SUFFIX//-/.}" + PKG_RELEASE="0.${PRERELEASE_SUFFIX}%{?dist}" +fi + +echo "RPM Version: ${PKG_VERSION}" +echo "RPM Release: ${PKG_RELEASE}" + +mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + +# Copy spec file and set version/release +cp dist/facelock.spec ~/rpmbuild/SPECS/facelock.spec +sed -i "s|^Version:.*|Version: ${PKG_VERSION}|" ~/rpmbuild/SPECS/facelock.spec +sed -i "s|^Release:.*|Release: ${PKG_RELEASE}|" ~/rpmbuild/SPECS/facelock.spec + +# Build source tarball expected by Source0 so rpmbuild can run the +# full %prep/%build/%install pipeline. +tar --exclude=.git --exclude=target \ + --transform "s|^|facelock-${PKG_VERSION}/|" \ + -czf "${HOME}/rpmbuild/SOURCES/facelock-${PKG_VERSION}.tar.gz" . + +# Build RPM using spec-defined build/install steps. +rpmbuild --define "_topdir $HOME/rpmbuild" \ + -bb ~/rpmbuild/SPECS/facelock.spec + +echo "=== RPM package built ===" diff --git a/.github/workflows/scripts/install-ubuntu-deps.sh b/.github/workflows/scripts/install-ubuntu-deps.sh new file mode 100755 index 0000000..2100848 --- /dev/null +++ b/.github/workflows/scripts/install-ubuntu-deps.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "=== Installing Ubuntu build dependencies ===" + +sudo apt-get update +sudo apt-get install -y \ + libv4l-dev \ + libpam0g-dev \ + pkg-config \ + libssl-dev \ + clang \ + libxkbcommon-dev \ + libwayland-dev \ + libtss2-dev \ + libtss2-tcti-tabrmd-dev + +echo "=== Ubuntu dependencies installed ===" diff --git a/.github/workflows/scripts/publish-apt.sh b/.github/workflows/scripts/publish-apt.sh new file mode 100755 index 0000000..999d8b0 --- /dev/null +++ b/.github/workflows/scripts/publish-apt.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +TPM_DEB="${1:?Usage: publish-apt.sh }" +LEGACY_DEB="${2:?Usage: publish-apt.sh }" +REPO_DIR="${3:?Usage: publish-apt.sh }" + +echo "=== Building APT repository ===" + +if [ -z "${APT_GPG_PRIVATE_KEY:-}" ]; then + echo "APT_GPG_PRIVATE_KEY secret not configured." + echo "See docs/releasing.md for setup instructions." + exit 1 +fi + +if [ -z "${APT_GPG_PASSPHRASE:-}" ]; then + echo "APT_GPG_PASSPHRASE secret not configured." + echo "See docs/releasing.md for setup instructions." + exit 1 +fi + +# Configure GPG agent for non-interactive signing +mkdir -p ~/.gnupg +chmod 700 ~/.gnupg +echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf +echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf +gpgconf --kill gpg-agent +gpg-agent --daemon + +# Import key +echo "$APT_GPG_PRIVATE_KEY" | gpg --batch --import + +# Trust the imported key ultimately +KEY_FPR=$(gpg --list-keys --with-colons | awk -F: '/^pub/{found=1} found && /^fpr/{print $10; exit}') +echo "${KEY_FPR}:6:" | gpg --import-ownertrust + +# Preset passphrase into gpg-agent so reprepro can sign non-interactively +KEY_GRIP=$(gpg --list-keys --with-keygrip --with-colons | awk -F: '/^grp/{print $10; exit}') +/usr/lib/gnupg/gpg-preset-passphrase --preset --passphrase "${APT_GPG_PASSPHRASE}" "${KEY_GRIP}" + +echo "GPG key imported and passphrase preset: ${KEY_FPR}" + +# Set up reprepro base directory +mkdir -p "${REPO_DIR}/conf" +cp dist/apt/conf/distributions "${REPO_DIR}/conf/distributions" + +# Add TPM .deb to 'main' suite +echo "Adding TPM .deb to main: ${TPM_DEB}" +reprepro -b "${REPO_DIR}" includedeb main "${TPM_DEB}" + +# Add legacy .deb to 'legacy' suite +echo "Adding legacy .deb to legacy: ${LEGACY_DEB}" +reprepro -b "${REPO_DIR}" includedeb legacy "${LEGACY_DEB}" + +# Export only the signing key (not the entire keyring) +gpg --export "${KEY_FPR}" > "${REPO_DIR}/tysmith-archive-keyring.gpg" +echo "Public keyring exported ($(du -h "${REPO_DIR}/tysmith-archive-keyring.gpg" | cut -f1))" + +echo "=== APT repo structure ===" +find "${REPO_DIR}" -type f | sort +echo "" +echo "=== Release file (main) ===" +cat "${REPO_DIR}/dists/main/Release" || true + +echo "=== APT repository built ===" diff --git a/.github/workflows/scripts/publish-aur.sh b/.github/workflows/scripts/publish-aur.sh new file mode 100755 index 0000000..5f11f24 --- /dev/null +++ b/.github/workflows/scripts/publish-aur.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +VERSION="${1:?Usage: publish-aur.sh }" +CHECKSUM="${2:?Usage: publish-aur.sh }" + +echo "=== Publishing to AUR ===" + +if [ -z "${AUR_SSH_KEY:-}" ]; then + echo "AUR_SSH_KEY secret not configured. Skipping AUR publish." + echo "See docs/releasing.md for setup instructions." + exit 0 +fi + +# Set up SSH for AUR +mkdir -p ~/.ssh +echo "$AUR_SSH_KEY" > ~/.ssh/aur +chmod 600 ~/.ssh/aur +echo "Host aur.archlinux.org" >> ~/.ssh/config +echo " IdentityFile ~/.ssh/aur" >> ~/.ssh/config +echo " User aur" >> ~/.ssh/config +ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null + +# Clone AUR repo +git clone ssh://aur@aur.archlinux.org/facelock.git aur-facelock + +# Copy and update PKGBUILD +cp dist/PKGBUILD aur-facelock/PKGBUILD +cp dist/facelock.install aur-facelock/facelock.install +sed -i "s/^pkgver=.*/pkgver=${VERSION}/" aur-facelock/PKGBUILD +sed -i "s/sha256sums=('SKIP')/sha256sums=('${CHECKSUM}')/" aur-facelock/PKGBUILD + +# Generate .SRCINFO via Arch container +cd aur-facelock +docker run --rm -v "$(pwd):/pkg" archlinux:base-devel bash -c \ + "pacman -Sy --noconfirm pacman-contrib && cd /pkg && makepkg --printsrcinfo > .SRCINFO" + +# Commit and push if changed +git config user.name "facelock-bot" +git config user.email "facelock@users.noreply.github.com" +git add PKGBUILD facelock.install .SRCINFO +git diff --cached --quiet || git commit -m "Update to v${VERSION}" +git push + +echo "=== AUR publish complete ===" diff --git a/.github/workflows/scripts/publish-copr.sh b/.github/workflows/scripts/publish-copr.sh new file mode 100755 index 0000000..bf09368 --- /dev/null +++ b/.github/workflows/scripts/publish-copr.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "=== Triggering COPR build ===" + +if [ -z "${COPR_WEBHOOK_URL:-}" ]; then + echo "COPR_WEBHOOK_URL secret not configured. Skipping COPR trigger." + echo "See docs/releasing.md for setup instructions." + exit 0 +fi + +curl -X POST "$COPR_WEBHOOK_URL" \ + --fail --silent --show-error + +echo "COPR build triggered successfully" diff --git a/.github/workflows/scripts/validate-deb.sh b/.github/workflows/scripts/validate-deb.sh new file mode 100755 index 0000000..c789976 --- /dev/null +++ b/.github/workflows/scripts/validate-deb.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +DEB_FILE="${1:?Usage: validate-deb.sh }" + +CONTENTS=$(dpkg-deb -c "$DEB_FILE") +echo "=== .deb contents ===" +echo "$CONTENTS" +echo "" +echo "=== Checking required files ===" + +CHECKS=( + "usr/bin/facelock:facelock binary" + "lib/security/pam_facelock.so:PAM module" + "etc/facelock/config.toml:config" + "dbus-1/system.d/org.facelock.Daemon.conf:D-Bus policy" + "dbus-1/system-services/org.facelock.Daemon.service:D-Bus activation" + "sysusers.d/facelock.conf:sysusers" + "tmpfiles.d/facelock.conf:tmpfiles" + "usr/lib/facelock/libonnxruntime.so:bundled ORT" +) + +FAILED=0 +for check in "${CHECKS[@]}"; do + pattern="${check%%:*}" + label="${check#*:}" + if echo "$CONTENTS" | grep -q "$pattern"; then + echo "OK: $label" + else + echo "FAIL: $label (missing $pattern)" + FAILED=1 + fi +done + +if [ "$FAILED" -ne 0 ]; then + echo "=== .deb validation FAILED ===" + exit 1 +fi + +echo "=== .deb validation passed ===" diff --git a/.github/workflows/scripts/validate-rpm.sh b/.github/workflows/scripts/validate-rpm.sh new file mode 100755 index 0000000..1b54688 --- /dev/null +++ b/.github/workflows/scripts/validate-rpm.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPM_FILE="${1:?Usage: validate-rpm.sh }" + +CONTENTS=$(rpm -qlp "$RPM_FILE") +echo "=== .rpm contents ===" +echo "$CONTENTS" +echo "" +echo "=== Checking required files ===" + +CHECKS=( + "usr/bin/facelock:facelock binary" + "security/pam_facelock.so:PAM module" + "etc/facelock/config.toml:config" + "dbus-1/system.d/org.facelock.Daemon.conf:D-Bus policy" + "dbus-1/system-services/org.facelock.Daemon.service:D-Bus activation" + "sysusers.d/facelock.conf:sysusers" + "tmpfiles.d/facelock.conf:tmpfiles" + "authselect/vendor/facelock:authselect" +) + +FAILED=0 +for check in "${CHECKS[@]}"; do + pattern="${check%%:*}" + label="${check#*:}" + if echo "$CONTENTS" | grep -q "$pattern"; then + echo "OK: $label" + else + echo "FAIL: $label (missing $pattern)" + FAILED=1 + fi +done + +if [ "$FAILED" -ne 0 ]; then + echo "=== .rpm validation FAILED ===" + exit 1 +fi + +echo "=== .rpm validation passed ===" diff --git a/README.md b/README.md index fa6ebf3..8fac5e9 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,50 @@ A modern face authentication system for Linux PAM. Provides Windows Hello-style facial auth with IR anti-spoofing, configurable as a persistent daemon or daemonless one-shot. All inference runs locally on your hardware -- no cloud services, no network requests, no telemetry. Your biometric data never leaves your machine. -## Quick Start +## Install + +### Arch Linux (AUR) + +```bash +yay -S facelock # or paru -S facelock +``` + +### Debian / Ubuntu (APT) + +```bash +# Add signing key +sudo install -d -m 0755 /etc/apt/keyrings +curl -fsSL https://tysmith.me/facelock/apt/tysmith-archive-keyring.gpg \ + | sudo tee /etc/apt/keyrings/tysmith-archive-keyring.gpg >/dev/null + +# Add repository — pick ONE: +# Modern (Debian trixie+, Ubuntu 25.04+) — TPM enabled: +echo "deb [signed-by=/etc/apt/keyrings/tysmith-archive-keyring.gpg] https://tysmith.me/facelock/apt main facelock" \ + | sudo tee /etc/apt/sources.list.d/facelock.list + +# Legacy (Ubuntu 24.04, Debian bookworm) — no TPM: +# echo "deb [signed-by=/etc/apt/keyrings/tysmith-archive-keyring.gpg] https://tysmith.me/facelock/apt legacy facelock" \ +# | sudo tee /etc/apt/sources.list.d/facelock.list + +sudo apt update && sudo apt install facelock +``` + +### Fedora / RHEL (COPR) + +```bash +sudo dnf copr enable tyvsmith/facelock +sudo dnf install facelock +``` + +### From Source ```bash just install # build + install binaries, systemd, D-Bus, PAM +``` + +### Post-Install + +```bash sudo facelock setup # download face models (~170MB) sudo facelock enroll # register your face sudo facelock test # verify recognition @@ -166,7 +206,7 @@ just release 0.2.0 # bump version across all packaging files git push origin main --tags # trigger CI release workflow ``` -Tagging `vX.Y.Z` builds release binaries, `.deb`, and `.rpm` via GitHub Actions. See [docs/releasing.md](docs/releasing.md) for the full process and versioning contract. +Tagging `vX.Y.Z` builds release binaries, `.deb` (TPM + legacy), `.rpm`, publishes to AUR/COPR/APT via GitHub Actions. See [docs/releasing.md](docs/releasing.md) for the full process and versioning contract. ## License diff --git a/book/src/quickstart.md b/book/src/quickstart.md index b3cf660..c64bf49 100644 --- a/book/src/quickstart.md +++ b/book/src/quickstart.md @@ -1,6 +1,67 @@ # Quick Start -## Prerequisites +## Package Install + +### Arch Linux (AUR) + +```bash +yay -S facelock # or paru -S facelock +``` + +### Debian / Ubuntu (APT) + +For **modern systems** (Debian trixie+, Ubuntu 25.04+) with TPM support: + +```bash +# Add signing key +sudo install -d -m 0755 /etc/apt/keyrings +curl -fsSL https://tysmith.me/facelock/apt/tysmith-archive-keyring.gpg \ + | sudo tee /etc/apt/keyrings/tysmith-archive-keyring.gpg >/dev/null + +# Add repository (main = TPM enabled) +echo "deb [signed-by=/etc/apt/keyrings/tysmith-archive-keyring.gpg] https://tysmith.me/facelock/apt main facelock" \ + | sudo tee /etc/apt/sources.list.d/facelock.list + +# Install +sudo apt update +sudo apt install facelock +``` + +For **older systems** (Ubuntu 24.04, Debian bookworm) without TPM: + +```bash +# Add signing key (same as above) +sudo install -d -m 0755 /etc/apt/keyrings +curl -fsSL https://tysmith.me/facelock/apt/tysmith-archive-keyring.gpg \ + | sudo tee /etc/apt/keyrings/tysmith-archive-keyring.gpg >/dev/null + +# Add repository (legacy = non-TPM) +echo "deb [signed-by=/etc/apt/keyrings/tysmith-archive-keyring.gpg] https://tysmith.me/facelock/apt legacy facelock" \ + | sudo tee /etc/apt/sources.list.d/facelock.list + +# Install +sudo apt update +sudo apt install facelock +``` + +### Fedora / RHEL (COPR) + +```bash +sudo dnf copr enable tyvsmith/facelock +sudo dnf install facelock +``` + +### Post-Install + +```bash +sudo facelock setup # download models, configure PAM +sudo facelock enroll # register your face +sudo facelock test # verify recognition +``` + +--- + +## Prerequisites (Building from Source) - Rust 1.85+ (`rustup update`) - [just](https://github.com/casey/just) task runner diff --git a/dist/apt/conf/distributions b/dist/apt/conf/distributions new file mode 100644 index 0000000..7518830 --- /dev/null +++ b/dist/apt/conf/distributions @@ -0,0 +1,15 @@ +Origin: tysmith +Label: Ty Smith Packages +Codename: main +Architectures: amd64 +Components: facelock +Description: Facelock APT repository - TPM enabled (modern Debian/Ubuntu) +SignWith: default + +Origin: tysmith +Label: Ty Smith Packages +Codename: legacy +Architectures: amd64 +Components: facelock +Description: Facelock APT repository - non-TPM (older Debian/Ubuntu) +SignWith: default diff --git a/dist/debian/changelog b/dist/debian/changelog index e76925a..54c9bc1 100644 --- a/dist/debian/changelog +++ b/dist/debian/changelog @@ -8,4 +8,4 @@ facelock (0.1.0-1) unstable; urgency=medium * SQLite face embedding storage. * D-Bus activation support. - -- Facelock Contributors Mon, 10 Mar 2026 00:00:00 +0000 + -- Facelock Contributors Mon, 10 Mar 2026 00:00:00 +0000 diff --git a/docs/releasing.md b/docs/releasing.md index 4339d4c..d1cf550 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -46,17 +46,24 @@ The `.github/workflows/release.yml` workflow: 1. Builds release binaries and creates a GitHub Release 2. Downloads ONNX Runtime for bundling in non-Arch packages -3. Builds `.deb` package (with bundled ONNX Runtime) and validates contents +3. Builds `.deb` package — **TPM** (Debian trixie container) and **legacy** (Ubuntu 24.04) 4. Builds `.rpm` package in Fedora container (with TPM feature) and validates contents 5. Validates Nix flake evaluation 6. Publishes to AUR (if `AUR_SSH_KEY` secret is configured) 7. Triggers COPR rebuild (if `COPR_WEBHOOK_URL` secret is configured) +8. Publishes signed APT repository (if `APT_GPG_PRIVATE_KEY` and `APT_GPG_PASSPHRASE` are configured) +9. Triggers GitHub Pages rebuild to include updated APT repo -Pre-release tags (containing `alpha`, `beta`, or `rc`) skip AUR/COPR publishing. +Pre-release tags (containing `alpha`, `beta`, or `rc`) skip AUR, COPR, and APT publishing. -Current TPM note: -- Ubuntu-hosted `build` and `build-deb` jobs build without `--features tpm`. -- Fedora-container `build-rpm` builds with `--features tpm`. +#### Debian package channels + +| Channel | Build env | TPM | Version suffix | Use case | +|---------|-----------|-----|----------------|----------| +| `main` | Debian trixie container | Yes | `X.Y.Z-1` | Modern systems (trixie+, Ubuntu 25.04+) | +| `legacy` | Ubuntu 24.04 runner | No | `X.Y.Z-1~legacy1` | Older systems (bookworm, Ubuntu 24.04) | + +Both `.deb` packages are uploaded to the GitHub Release for direct download. For `apt install`, they are published to the signed APT repository at `https://tysmith.me/facelock/apt/`. ### Local distro validation @@ -86,7 +93,7 @@ just test-deb ``` `just release-preflight` checks local tools, required packaging files, and whether -`AUR_SSH_KEY` / `COPR_WEBHOOK_URL` are configured in GitHub secrets (via `gh`). +`AUR_SSH_KEY`, `COPR_WEBHOOK_URL`, `APT_GPG_PRIVATE_KEY`, and `APT_GPG_PASSPHRASE` are configured in GitHub secrets (via `gh`). ### Package repository setup (one-time) @@ -157,6 +164,40 @@ Automated after setup. The release workflow triggers a COPR rebuild when `COPR_W Alternatively, configure COPR to build from the GitHub source directly using the SCM integration (points at `dist/facelock.spec`). +#### APT (Debian/Ubuntu) + +Automated after setup. The release workflow publishes a signed APT repository to GitHub Pages when `APT_GPG_PRIVATE_KEY` and `APT_GPG_PASSPHRASE` are configured. + +**One-time setup (~15 minutes):** + +1. Generate a GPG signing key (if you don't have one): + ```bash + gpg --full-generate-key + # Select RSA 4096, expiry 3y + # UID: Ty Smith Package Signing + ``` + +2. Export and add the private key as a GitHub secret: + ```bash + gpg --armor --export-secret-keys "packages@tysmith.me" | gh secret set APT_GPG_PRIVATE_KEY + ``` + +3. Add the passphrase as a GitHub secret: + ```bash + gh secret set APT_GPG_PASSPHRASE --body "your-passphrase" + ``` + + Or use the web UI: https://github.com/tyvsmith/facelock/settings/secrets/actions + +The repository configuration lives in `dist/apt/conf/distributions`. Two suites are published: + +- **`main`**: TPM-enabled build (Debian trixie+, Ubuntu 25.04+) +- **`legacy`**: Non-TPM build (Ubuntu 24.04, Debian bookworm) + +The APT repo is hosted at `https://tysmith.me/facelock/apt/` alongside the docs site. The public keyring is at `https://tysmith.me/facelock/apt/tysmith-archive-keyring.gpg`. + +**GPG key rotation**: When the signing key expires, generate a new key, update the `APT_GPG_PRIVATE_KEY` and `APT_GPG_PASSPHRASE` secrets, and cut a new release. The public keyring is re-exported on every release, so users who re-fetch it will get the updated key. + #### Manual AUR update (fallback) If CI is not configured or fails: diff --git a/justfile b/justfile index a893c0f..558c757 100644 --- a/justfile +++ b/justfile @@ -348,7 +348,7 @@ release version: # 4. dist/debian/changelog (prepend new entry) if [ -f dist/debian/changelog ]; then DATE=$(date -R) - sed -i "1i facelock ($VERSION-1) unstable; urgency=medium\n\n * Release v$VERSION.\n\n -- Facelock Contributors $DATE\n" dist/debian/changelog + sed -i "1i facelock ($VERSION-1) unstable; urgency=medium\n\n * Release v$VERSION.\n\n -- Facelock Contributors $DATE\n" dist/debian/changelog echo " ✓ dist/debian/changelog" fi @@ -402,6 +402,65 @@ test-deb: build-release podman build -t facelock-deb-test -f test/Containerfile.ubuntu . podman run --rm facelock-deb-test +# Test APT repo generation locally (requires reprepro + gpg) +test-apt-repo: + #!/usr/bin/env bash + set -euo pipefail + + # Check tools + for cmd in reprepro dpkg-deb; do + command -v "$cmd" >/dev/null || { echo "Error: '$cmd' not found. Install it first."; exit 1; } + done + + # Verify config exists + if [ ! -f dist/apt/conf/distributions ]; then + echo "Error: dist/apt/conf/distributions not found" + exit 1 + fi + + TMPDIR=$(mktemp -d) + trap 'rm -rf "$TMPDIR"' EXIT + + REPO_DIR="${TMPDIR}/repo" + mkdir -p "${REPO_DIR}/conf" + cp dist/apt/conf/distributions "${REPO_DIR}/conf/distributions" + + # For local testing without GPG, strip SignWith lines + sed -i '/^SignWith:/d' "${REPO_DIR}/conf/distributions" + + # Find any .deb files (from just test-deb or CI artifacts) + DEB_FILES=$({ find . -maxdepth 1 -name 'facelock_*.deb'; find ./target -maxdepth 1 -name 'facelock_*.deb' 2>/dev/null; } | head -2) + if [ -z "$DEB_FILES" ]; then + echo "No .deb files found. Building a test .deb is not required." + echo "Validating reprepro config only..." + reprepro -b "${REPO_DIR}" check + echo "" + echo "APT repo config: OK" + echo "To test with real .deb files, build them first with CI or 'just test-deb'." + exit 0 + fi + + # Add first .deb to main, second to legacy (or same to both) + FIRST_DEB=$(echo "$DEB_FILES" | head -1) + SECOND_DEB=$(echo "$DEB_FILES" | tail -1) + + reprepro -b "${REPO_DIR}" includedeb main "$FIRST_DEB" + reprepro -b "${REPO_DIR}" includedeb legacy "$SECOND_DEB" + + echo "" + echo "=== APT repo structure ===" + find "${REPO_DIR}" -type f -not -path '*/db/*' -not -path '*/conf/*' | sort + + # Validate expected structure + for SUITE in main legacy; do + [ -f "${REPO_DIR}/dists/${SUITE}/Release" ] && echo "OK: dists/${SUITE}/Release" + [ -d "${REPO_DIR}/dists/${SUITE}/facelock/binary-amd64" ] && echo "OK: dists/${SUITE}/facelock/binary-amd64/" + done + [ -d "${REPO_DIR}/pool/facelock" ] && echo "OK: pool/facelock/" + + echo "" + echo "APT repo generation: OK" + # Quick preflight before tagging a release # Usage: # just release-preflight # assume stable release @@ -441,6 +500,7 @@ release-preflight tag='': dist/facelock.spec \ dist/debian/control \ dist/debian/rules \ + dist/apt/conf/distributions \ .github/workflows/release.yml; do if [ -f "$f" ]; then echo "OK: $f" @@ -476,6 +536,24 @@ release-preflight tag='': failed=1 fi fi + + if gh secret list | grep -q '^APT_GPG_PRIVATE_KEY\b'; then + echo "OK: APT_GPG_PRIVATE_KEY configured" + else + echo "MISSING: APT_GPG_PRIVATE_KEY" + if [ "$prerelease" -eq 0 ]; then + failed=1 + fi + fi + + if gh secret list | grep -q '^APT_GPG_PASSPHRASE\b'; then + echo "OK: APT_GPG_PASSPHRASE configured" + else + echo "MISSING: APT_GPG_PASSPHRASE" + if [ "$prerelease" -eq 0 ]; then + failed=1 + fi + fi else echo "SKIP: gh not installed or not authenticated; cannot verify repo secrets" if [ "$prerelease" -eq 0 ]; then