Compare commits

..

10 Commits

Author SHA1 Message Date
michael
fb4322fd33 Committing local changes before initial push to remote
Some checks failed
CI / rust (push) Has been cancelled
CI / pam (push) Has been cancelled
CI / deb (push) Has been cancelled
2026-05-01 07:12:11 -07:00
michael
f5879e1541 docs: phase 13 closeout notes 2026-04-27 20:03:07 -07:00
michael
6cef32df4d feat(pkg): bump changelog UNRELEASED -> noble; urgency=medium (Phase 13 release) 2026-04-27 20:02:01 -07:00
michael
21bf1f5a3f docs(pkg): piuparts test recipe — Ubuntu 24.04, install/remove/purge cycle 2026-04-27 19:54:47 -07:00
michael
c1b8dd6cb0 chore(pkg): lintian sweep — fix obsolete polkit dep, drop dup systemd path, command -v in pam hooks; override deferred manpages and PPA-only warnings
Real fixes:
- authforge-daemon Depends: policykit-1 -> polkitd | policykit-1
  (policykit-1 was renamed; old name is obsolete on noble).
- debian/rules: drop manual install of authforge-daemon.service to
  /lib/systemd/system/ — dh_installsystemd auto-installs to
  /usr/lib/systemd/system/, the manual line caused a file-in-root-and-usr
  duplicate flagged by usrmerge.
- authforge-pam.{postinst,prerm}: replace [ -x /usr/sbin/pam-auth-update ]
  with command -v pam-auth-update — lintian flags hard-coded paths in
  test syntax; command -v is the portable idiom.

Overrides (with rationale in each file):
- no-manual-page on the three binaries — manpages deferred to Phase 18.
- desktop-command-not-in-package authforge in gnome-integration —
  command lives in authforge-gui (cross-package Depends).
- initial-upload-closes-no-bugs on every package — AuthForge ships
  via PPA, not the Debian archive, so there is no ITP to close.

After fixes, lintian on the five binary debs is silent (zero W/E).
2026-04-27 19:54:15 -07:00
michael
0dc108220d feat(pkg): metapackage postinst seeds /etc/authforge/policy.d/00-debconf.conf from debconf answer 2026-04-27 19:33:52 -07:00
michael
753bf54aad feat(pkg): debconf prompt for initial AuthForge policy on metapackage install 2026-04-27 19:19:12 -07:00
michael
cccca786f7 feat(pkg): authforge-daemon postrm purges /etc/authforge and /var/lib/authforge 2026-04-27 18:29:29 -07:00
michael
f9af34cbb3 feat(pkg): authforge-pam prerm calls pam-auth-update --remove on uninstall 2026-04-27 17:36:39 -07:00
michael
41ea0a1af8 feat(pkg): authforge-pam postinst calls pam-auth-update --package 2026-04-27 17:29:15 -07:00
19 changed files with 307 additions and 9 deletions

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ debian/.debhelper/
debian/files
debian/*.substvars
debian/*.debhelper.log
debian/*.debhelper
debian/authforge*/
debian/debhelper-build-stamp
pam/*.so

75
debian/PIUPARTS.md vendored Normal file
View File

@@ -0,0 +1,75 @@
# Testing the AuthForge debs with piuparts
piuparts simulates a clean Debian/Ubuntu install in a chroot, runs the
install/upgrade/remove/purge cycle, and verifies no files leak.
This is the strongest manual gate before the Phase 14 PPA build picks
up automated VM-based testing.
## Target
**Ubuntu 24.04 only** for v1. The GUI (`authforge-gui`) hard-requires
libadwaita 1.5+, which Ubuntu 22.04 does not ship. Older targets would
need a 1.5 backport PPA — not worth the matrix complexity for v1.
## Prerequisites
```bash
sudo apt install piuparts debootstrap
```
piuparts pulls a fresh `noble` (24.04) chroot on every run; first run
takes ~5 minutes to download base, subsequent runs use the cache at
`/var/cache/piuparts/`.
## Run
From the project root, after a successful `dpkg-buildpackage -us -uc -b`:
```bash
sudo piuparts \
--distribution=noble \
--mirror "http://archive.ubuntu.com/ubuntu noble main universe" \
--arch amd64 \
--warn-on-leftovers-after-purge \
../authforge_*.deb \
../authforge-*.deb
```
The `--mirror` line includes `universe` because `libpam-google-authenticator`
(Recommends of `authforge-daemon`) lives there. Without it piuparts can't
resolve the recommends and the test fails before installing AuthForge.
## What piuparts exercises
1. Install all debs together (metapackage pulls in all binary packages).
2. Verify the daemon's systemd unit enables + starts under systemd-in-chroot.
3. Verify `pam-auth-update --package` registered the AuthForge profile
(look for `authforge` in `/etc/pam.d/common-auth`).
4. Reinstall (0.1.0 over 0.1.0) — postinst runs again, must be idempotent.
5. Remove — `pam-auth-update --remove authforge` runs in prerm; the
binaries disappear; `/etc/authforge/` and `/var/lib/authforge/` survive.
6. Purge — postrm wipes `/etc/authforge/` and `/var/lib/authforge/`;
piuparts diffs the chroot against its initial snapshot and reports
leftovers.
## Pass criteria
- [ ] piuparts exit code 0.
- [ ] No `FAIL` lines in the output.
- [ ] Any `WARN: leftover` lines refer only to systemd/journald state
(logs, runtime files in `/run/`) — not files in
`/etc/authforge/`, `/var/lib/authforge/`, or `/usr/share/pam-configs/`.
- [ ] `pam-auth-update --list` (run inside the chroot mid-test, if you
pause it) shows `authforge` after install and absent after
remove — piuparts surfaces this implicitly via its `/etc/pam.d/`
diff.
## Known surface gaps
- piuparts can't exercise the GUI or the FIDO2 enrollment flow — those
need a real desktop session, deferred to Phase 14 VM smoke
(`gui/TESTING.md`, `gui/FIRSTRUN-TESTING.md`).
- The debconf prompt is non-interactive in piuparts (uses defaults).
To test the "Required for sudo" branch end-to-end, run a manual
install with `DEBCONF_PRIORITY=critical` and check that
`/etc/authforge/policy.d/00-debconf.conf` was written.

View File

@@ -0,0 +1,5 @@
# Manpages are deferred to Phase 18 (user docs); shipping 0.1.0 without
# them rather than blocking on docs is the call from the master plan.
authforge-cli: no-manual-page [usr/bin/authforgectl]
# Not destined for the Debian archive — see authforge.lintian-overrides.
authforge-cli: initial-upload-closes-no-bugs

View File

@@ -0,0 +1,5 @@
# Manpages are deferred to Phase 18 (user docs); shipping 0.1.0 without
# them rather than blocking on docs is the call from the master plan.
authforge-daemon: no-manual-page [usr/sbin/authforged]
# Not destined for the Debian archive — see authforge.lintian-overrides.
authforge-daemon: initial-upload-closes-no-bugs

24
debian/authforge-daemon.postrm vendored Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
# Cleans up state directories on purge. /etc/authforge/ holds policy
# config; /var/lib/authforge/ holds runtime state (pending flags,
# recovery hashes, TOTP secrets, userdb). Both are intentionally
# preserved on plain remove — admin state survives a remove/install
# round-trip, which matches Debian Policy §6.6 "Removing the package".
set -e
case "$1" in
purge)
rm -rf /etc/authforge
rm -rf /var/lib/authforge
;;
remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0

View File

@@ -19,7 +19,7 @@ PrivateTmp=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ReadWritePaths=/etc/authforge /var/lib/authforge /etc/pam.d /usr/share/pam-configs /etc/u2f_mappings
ReadWritePaths=/etc/authforge /var/lib/authforge /etc/pam.d /usr/share/pam-configs -/etc/u2f_mappings
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,7 @@
# The Users-panel desktop entry launches `authforge`, which is shipped
# by the authforge-gui binary package (Depends: authforge-gui). Lintian
# only inspects files inside this package and can't see the cross-
# package binary, so the warning is a false positive.
authforge-gnome-integration: desktop-command-not-in-package authforge [usr/share/applications/io.dangerousthings.AuthForge.UsersPanel.desktop]
# Not destined for the Debian archive — see authforge.lintian-overrides.
authforge-gnome-integration: initial-upload-closes-no-bugs

View File

@@ -0,0 +1,5 @@
# Manpages are deferred to Phase 18 (user docs); shipping 0.1.0 without
# them rather than blocking on docs is the call from the master plan.
authforge-gui: no-manual-page [usr/bin/authforge]
# Not destined for the Debian archive — see authforge.lintian-overrides.
authforge-gui: initial-upload-closes-no-bugs

View File

@@ -0,0 +1,2 @@
# Not destined for the Debian archive — see authforge.lintian-overrides.
authforge-pam: initial-upload-closes-no-bugs

24
debian/authforge-pam.postinst vendored Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
# Registers /usr/share/pam-configs/authforge via pam-auth-update on first
# install and reconfigure events. The profile ships Default: no, so this
# is a no-op for fresh installs — the metapackage's debconf flow flips it
# on if the admin opted in. Idempotent: safe to call repeatedly.
set -e
case "$1" in
configure)
if command -v pam-auth-update >/dev/null 2>&1; then
pam-auth-update --package
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0

24
debian/authforge-pam.prerm vendored Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
# Deregisters the authforge profile from /etc/pam.d/common-* before
# the .so disappears. Ordering matters: prerm runs before any files are
# removed, so /usr/share/pam-configs/authforge is still present here for
# pam-auth-update to inspect.
set -e
case "$1" in
remove|deconfigure)
if command -v pam-auth-update >/dev/null 2>&1; then
pam-auth-update --package --remove authforge
fi
;;
upgrade|failed-upgrade)
;;
*)
echo "prerm called with unknown argument \`$1'" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0

14
debian/authforge.config vendored Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# Asks the admin for an initial policy. Postinst (Task 6) reads the
# answer and writes /etc/authforge/policy.d/00-debconf.conf on first
# install only. Re-runs (e.g. `dpkg-reconfigure authforge`) re-prompt
# but do not overwrite an existing seed file unless the admin chose to.
set -e
# shellcheck source=/dev/null
. /usr/share/debconf/confmodule
db_input medium authforge/initial-policy || true
db_go || true
exit 0

3
debian/authforge.lintian-overrides vendored Normal file
View File

@@ -0,0 +1,3 @@
# AuthForge is shipped via the Dangerous Things PPA, not the Debian
# archive — there is no ITP bug to close on initial upload.
authforge: initial-upload-closes-no-bugs

66
debian/authforge.postinst vendored Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/sh
# Reads the debconf answer and writes /etc/authforge/policy.d/00-debconf.conf
# exactly once, on first install. Subsequent upgrades/reconfigures don't
# touch the file — admin edits via authforgectl/GUI win.
set -e
# shellcheck source=/dev/null
. /usr/share/debconf/confmodule
POLICY_DIR=/etc/authforge/policy.d
SEED_FILE=$POLICY_DIR/00-debconf.conf
case "$1" in
configure)
# Only seed on first install: $2 is empty when no previous version
# was configured. Skip if the seed file already exists (admin may
# have written one manually before apt configured the metapackage).
if [ -z "$2" ] && [ ! -e "$SEED_FILE" ]; then
db_get authforge/initial-policy || true
mkdir -p "$POLICY_DIR"
case "$RET" in
"None")
: # no seed — leave dir empty
;;
"Optional everywhere")
cat > "$SEED_FILE" <<'EOF'
# /etc/authforge/policy.d/00-debconf.conf
# Seeded by debconf on first install. Edit via `authforgectl policy set`
# or the AuthForge GUI; admin edits override on next daemon reload.
[stacks.gdm-password]
mode = "optional"
methods = ["fido2"]
[stacks.sudo]
mode = "optional"
methods = ["fido2", "totp"]
[stacks.sshd]
mode = "optional"
methods = ["fido2"]
EOF
;;
"Required for sudo")
cat > "$SEED_FILE" <<'EOF'
# /etc/authforge/policy.d/00-debconf.conf
# Seeded by debconf on first install. Edit via `authforgectl policy set`
# or the AuthForge GUI; admin edits override on next daemon reload.
[stacks.sudo]
mode = "required"
methods = ["fido2", "totp"]
EOF
;;
esac
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0

12
debian/authforge.templates vendored Normal file
View File

@@ -0,0 +1,12 @@
Template: authforge/initial-policy
Type: select
Default: None
Choices: None, Optional everywhere, Required for sudo
Description: Initial AuthForge policy:
AuthForge can write a starter policy at install time. Choose:
.
None — install only, no MFA enforcement (admins configure later).
Optional everywhere — every PAM stack offers FIDO2/TOTP but doesn't require it.
Required for sudo — sudo prompts for FIDO2 or recovery code in addition to the password.
.
You can change this any time later via the AuthForge GUI or `authforgectl policy set`.

9
debian/changelog vendored
View File

@@ -1,5 +1,8 @@
authforge (0.1.0-1) UNRELEASED; urgency=low
authforge (0.1.0-1) noble; urgency=medium
* Initial packaging skeleton (renamed from ubuntu-fido).
* Initial release: daemon, PAM module, CLI, GUI, GNOME integration.
* Phase 13 packaging: pam-auth-update register/deregister hooks,
debconf-seeded initial policy, purge cleanup of /etc/authforge
and /var/lib/authforge, lintian-clean (zero W/E).
-- Dangerous Things <ops@dangerousthings.com> Sun, 26 Apr 2026 12:00:00 +0000
-- Dangerous Things <ops@dangerousthings.com> Mon, 27 Apr 2026 12:00:00 +0000

3
debian/control vendored
View File

@@ -18,6 +18,7 @@ Vcs-Git: https://github.com/dangerousthings/authforge.git
Package: authforge
Architecture: any
Pre-Depends: debconf (>= 0.5) | debconf-2.0
Depends: authforge-daemon (= ${binary:Version}),
authforge-pam (= ${binary:Version}),
authforge-cli (= ${binary:Version}),
@@ -30,7 +31,7 @@ Description: Turnkey FIDO2/U2F/TOTP MFA for Linux desktops (metapackage)
Package: authforge-daemon
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, libpam-u2f, libfido2-1, dbus, policykit-1
Depends: ${shlibs:Depends}, ${misc:Depends}, libpam-u2f, libfido2-1, dbus, polkitd | policykit-1
Recommends: libpam-google-authenticator
Description: System daemon for authforge MFA management
Provides the privileged D-Bus service that orchestrates enrollment,

5
debian/rules vendored
View File

@@ -43,9 +43,8 @@ override_dh_auto_install:
# polkit actions
install -D -m 0644 debian/io.dangerousthings.AuthForge.policy \
debian/authforge-daemon/usr/share/polkit-1/actions/io.dangerousthings.AuthForge.policy
# systemd unit (dh_installsystemd will enable it)
install -D -m 0644 debian/authforge-daemon.service \
debian/authforge-daemon/lib/systemd/system/authforge-daemon.service
# systemd unit: dh_installsystemd auto-installs debian/authforge-daemon.service
# to /usr/lib/systemd/system/ — no manual install line needed.
# GNOME Settings Users-panel overlay (authforge-gnome-integration)
install -D -m 0644 gnome-integration/io.dangerousthings.AuthForge.UsersPanel.desktop \
debian/authforge-gnome-integration/usr/share/applications/io.dangerousthings.AuthForge.UsersPanel.desktop

View File

@@ -40,7 +40,7 @@
| 10 | First-login flow: autostart entry + fullscreen modal | 4 days | ✅ **Code complete** (2026-04-27) — VM smoke deferred |
| 11 | TOTP support (PAM module + GUI tab) — feature flag | 5 days | ✅ **Code complete** (2026-04-27) — `pam_google_authenticator` smoke deferred |
| 12 | Recovery flow (codes + emergency unlock) | 4 days | ✅ **Code complete** (2026-04-27) — backend + PAM + GUI tab all landed |
| 13 | Debian packaging finalization (postinst, debconf, purge) | 4 days | **Spec'd** — sequential tail; needs all binaries built |
| 13 | Debian packaging finalization (postinst, debconf, purge) | 4 days | **Code complete** (2026-04-27) — `piuparts` smoke deferred to Phase 14 |
| 14 | Launchpad PPA build setup | 2 days | **Spec'd** — sequential tail after 13; **VM smoke gate for all "Code complete" phases** |
| 15 | `authforge-gnome-integration` (Users panel shortcut) | 3 days | ✅ **Done** (2026-04-27) — parallel subagent (gnome lane) |
| 16 | Ansible role `dangerousthings.authforge` | 2 days | ✅ **Done** (2026-04-27) — parallel subagent (ansible lane) |
@@ -335,6 +335,34 @@ Landed via [2026-04-27-phase-11-totp.md](2026-04-27-phase-11-totp.md). Built ato
- `authforgectl totp enroll alice` → scan QR with Aegis → `sudo whoami` prompts for the 6-digit code → success.
- Verify the rendered profile loads in `pam-auth-update --package` without rejection on a clean Ubuntu VM.
### Phase 13 closeout notes (2026-04-27)
Landed via [2026-04-27-phase-13-deb-packaging.md](2026-04-27-phase-13-deb-packaging.md). 10 commits on `main` (no worktree — all changes are in `debian/` and don't touch the workspace's Rust crates).
**Done:**
- `debian/authforge.pam-config` — baseline `pam-auth-update` profile, `Default: no`, pending-flag-only. Daemon's `policy_apply::render_profile` overwrites this file at runtime once a real policy is set.
- `debian/authforge-pam.{postinst,prerm}` — registers via `pam-auth-update --package` on `configure`; deregisters via `--remove authforge` on `remove`/`deconfigure`. Idempotent. Use `command -v pam-auth-update` (not hard-coded `/usr/sbin/`) to satisfy lintian.
- `debian/authforge-daemon.postrm` — purges `/etc/authforge/` + `/var/lib/authforge/` on `purge`; preserves them on plain `remove` (Debian Policy §6.6).
- `debian/authforge.{templates,config}` — debconf prompt: `None` / `Optional everywhere` / `Required for sudo`. Default `None`. `Pre-Depends: debconf (>= 0.5) | debconf-2.0` added to the metapackage.
- `debian/authforge.postinst` — reads the debconf answer; writes `/etc/authforge/policy.d/00-debconf.conf` exactly once on first install (`[ -z "$2" ]`). Schema cross-checked against `daemon/src/storage/policy.rs` test fixture (`[stacks.<name>]` + `mode` + `methods`).
- Per-binary `lintian-overrides` (six files) — zero W/E from `lintian` on the built debs. Real fixes folded in: `policykit-1``polkitd | policykit-1` (obsolete-package error); dropped manual `/lib/systemd/system/` install line so `dh_installsystemd` owns the unit at `/usr/lib/systemd/system/` (file-in-root-and-usr error). Overrides cover deferred-manpage (Phase 18), cross-package `desktop-command-not-in-package` (gnome-integration → authforge-gui), and `initial-upload-closes-no-bugs` (PPA-only, no ITP).
- `debian/PIUPARTS.md` — manual smoke recipe for Ubuntu 24.04. Install → reinstall → remove → purge cycle documented; `piuparts` run is the Phase 14 gate.
- `debian/changelog` — bumped `(0.1.0-1) UNRELEASED; urgency=low``(0.1.0-1) noble; urgency=medium` ready for Phase 14 dput.
- `.gitignore` — added `debian/*.debhelper` so build-time hook snippets don't pollute `git status`.
**Plan deviations:**
- `dpkg-buildpackage` invoked with `-d` throughout (cargo + rustc are installed via rustup, not apt, so the apt-level Build-Depends check fails even though the build itself works). The Build-Depends in `debian/control` are correct for the Phase 14 PPA / Launchpad sbuild — only the local dev-env check needs the override.
- Lintian sweep folded three real fixes into the same commit as the overrides rather than splitting per the plan's literal "fix any real warnings" suffix — the fixes are all packaging-control changes (control file, rules file, hook scripts) and live in the same diff window. Single atomic commit reads cleaner than three.
- Added a `# shellcheck source=/dev/null` directive to `authforge.config` and `authforge.postinst` for the `/usr/share/debconf/confmodule` source line — shellcheck SC1091 is info-level and was returning exit 1 under the local config; the directive is the standard idiom for sourced runtime files.
**Test count delta:** 0. Phase 13 ships no Rust code. `cargo test --workspace` still reports 112 passed (cli 9 + common 8 + daemon 78 + gui 10 + pam 0 + zero failures). `cargo fmt --all -- --check` clean.
**Deferred until Phase 14 PPA + VM smoke:**
- `piuparts` actually run on a clean Ubuntu 24.04 chroot.
- `pam-auth-update --package` actually mutating `/etc/pam.d/common-*` on a real system.
- The "Required for sudo" debconf branch tested end-to-end (sudo prompts for FIDO2 + password).
- `dpkg-buildpackage -us -uc -b` producing five debs that install on a fresh VM (build verified locally; install path is the Phase 14 gate).
---
# Phase 0: Repository Scaffolding (FULLY DETAILED)