Skip to content

feat(image): prune gnupg, cracklib, lvm2 and other command-line tools from /usr - #487

Open
jeefy wants to merge 2 commits into
projectbluefin:mainfrom
jeefy:feat/prune-usr-cli-tools
Open

jeefy wants to merge 2 commits into
projectbluefin:mainfrom
jeefy:feat/prune-usr-cli-tools

Conversation

@jeefy

@jeefy jeefy commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

First of two "how minimal can the runtime image be" PRs. This one extends files/prune/usr.list (the #419 mechanism) with tools no unit, udev rule, script or documented operator workflow uses. −9.1 MB usr.raw (−4.6%), proven by a full export and check-image against v26.10.1182: 190 paths removed, 0 added, no stale KNOWN_BROKEN waiver.

A finding worth knowing: the installer's /usr is a CopyBlocks= of the same usr.raw, so there is no "installer-only" tier — pruning must be safe for diskless, disk and stick alike. systemd-sysinstall is C and needs none of this.

Pruned

  • gnupg extras (17 bins, 7 libexec, share/gnupg): gpgsm, dirmngr*, scdaemon, gpg-wks-*, gpgtar, gpgsplit, kbxutil, gpgconf, gpg-connect-agent, … plus libgpg-error/libgcrypt dev tools and their Lisp bindings. Kept gpg, gpg-agent, keyboxd, gpgv: systemd-pull execs gpg (then gpg2) to verify SHA256SUMS.gpg on every sysupdate — check-image cannot see that (nothing in a unit names it). Verified by running gpg with systemd-pull's exact arguments and gpgv as bluefin-diskless-update-check does, inside a bwrap root built from the pruned /usr: good signature accepted, tampered manifest refused.
  • cracklib tools + share/cracklib: the image never shipped a compiled pw_dict, so pam_pwquality's behaviour is unchanged; libcrack stays (libpwquality links it).
  • lvm2: lvm + 48 links, dmeventd, fsadm, blkdeactivate, liblvm2cmd, libdevmapper-event*, the device-mapper plugin dir, 11-dm-lvm.rules, 69-dm-lvm.rules. Kept libdevmapper, dmsetup (+dmstats links) and 10-dm/13-dm-disk/95-dm-notify.rules — dm-verity /usr and cryptsetup depend on dmsetup udevflags/udevcomplete. 11-dm-lvm.rules exits unless DM_UUID starts with LVM-. The initrd is untouched.
  • openssl CLI; util-linux: cal hexdump look mesg more rev ul; sqlite3 CLI (the initrd already prunes it); build-time generators (compile_et mk_cmds asn1* pcre2test bashbug); Python scripts with no Python (psl-make-dafsa routel xfs_protofile); share/zsh, share/fish (neither shell ships); usr/include, lib/*/pkgconfig leftovers.

Not done, documented

docs/skills/ddi-installer-build.md records why disabling systemd features (homed, userdb, portabled, oomd, localed, timedated, backlight, rfkill, hibernate, xdg-autostart) was not attempted: it needs a patch on FSDK's systemd element, which takes systemd and every dependent off FSDK's artifact cache for every build, for a few MB. Not until measured.

Sizes (v26.10.1182 → this)

before after
usr.raw 196,075,520 186,998,784
OS DDI 480,256,512 471,179,776
disk UKI 81,622,960 81,627,648 (initrd unchanged)

Verification

just validate; just export-image + just check-image vs v26.10.1182 (190/0); gpg/gpgv signature checks in a pruned root; veritysetup, cryptsetup, dmsetup, systemd-sysupdate, passwd start from the pruned tree; pytest 1325 passed; docs-checks. Not run: a QEMU A/B update (just dogfood-install … next) — that is the end-to-end proof of the gpg path; please label full-build so CI's boot-test runs it before merge.

@github-actions
github-actions Bot requested review from alatiera and hanthor October 17, 2026 02:57
@jeefy jeefy added the full-build Run the full image build and boot test on this pull request label Oct 17, 2026
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
To complete the pull request process, please assign ahmedadan after the PR has been reviewed.
You can assign the PR to them by writing /assign @ahmedadan in a comment when ready.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HUMAN DECISION NEEDED — @jeefy merge only after the full-build boot-test (A/B sysupdate) is green; it is the only check that exercises the gpg verification path this prune leaves in place.

Reviewed at abbd646. The removals are mechanically safe: files/prune/prune.sh:17-19 fails the build on any pattern that matches nothing, and image-check (.github/workflows/build.yml:603-644) catches any unit/udev/library reference to a pruned path. No script under files/ or any page under docs/ invokes a pruned tool (only gpgv in files/os/update-check/usr/libexec/bluefin-diskless-update-check:93 and files/initrd-ignition/usr/libexec/bluefin-ignition-credentials:90, both kept).

correctness

  • The one load-bearing claim neither tool can see is that systemd-pull's gpg exec still works with 17 gnupg binaries and 7 libexec helpers gone (files/prune/usr.list:7-36). The PR body's bwrap test covers it, but the end-to-end proof is the A/B update in boot-test (.github/workflows/build.yml:364-401), which is running now under the full-build label. Gate the merge on it.
  • Test adequacy (low): the PR establishes that gpg-agent and keyboxd are load-bearing (files/prune/usr.list:7-9, docs/skills/ddi-installer-build.md:250-252), but tests/unit/test_check_image.py:185 still protects only usr/bin/gpg, usr/bin/gpgv, usr/lib/locale, usr/lib/systemd. Add usr/bin/gpg-agent and usr/libexec/keyboxd to the protected tuple so a later prune cannot silently remove them.

style

  • usr/bin/lv*, usr/bin/vg*, usr/bin/pv* (files/prune/usr.list:50-52) are the only prefix globs in the lists; every other block, including the 17 ICU tools in files/prune/common.list:8-27, names each binary. A future unrelated pv/lv*/vg* binary would be pruned silently, and image-check cannot flag interactive tools. List the LVM commands explicitly for consistency.

No findings from: security, intent-alignment, docs-currency.

Confidence: 3/5 (needs attention) — 1 medium finding; 1 perspective requires a human decision

— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

@jeefy
jeefy force-pushed the feat/prune-usr-cli-tools branch from abbd646 to d6f61d4 Compare October 17, 2026 04:02

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change shrinks the server's read-only system image by deleting command-line tools nothing on a running node uses (GnuPG extras, LVM, cracklib, the OpenSSL CLI and similar). I read the diff plus files/prune/prune.sh, .github/scripts/check-image.py, the FSDK lvm2/cracklib/gnupg element definitions at the pinned ref, and scanned the v26.10.1182 usr.raw to check the body's claims. The pruning itself looks safe; the one thing worth fixing is how the lists are organised.

style

  • files/prune/initrd.list:6-19 is now a strict subset of files/prune/usr.list (all 11 entries: sqlite3, gpgsm, gpg-wks-*, gpgtar, gpg-card, gpg-mail-tube, gpgsplit, gpgparsemail, libexec/gpg-wks-client, libexec/scdaemon). files/prune/common.list:1 and docs/skills/ddi-installer-build.md:230-232 define common.list as the list applied to both trees, so these belong there; keeping the same entries in two per-tree lists is the drift common.list exists to prevent. (If initrd.list ends up empty, tests/unit/test_check_image.py:174-181 asserts each list is non-empty, so either keep one initrd-only entry or adjust the parametrize.)
  • files/prune/usr.list:50-52 (usr/bin/lv*, usr/bin/vg*, usr/bin/pv*) are the only open-ended globs in the list; prune.sh:16-23 only errors when a pattern matches nothing, so any future FSDK binary starting with pv/lv/vg is deleted silently. Everything else in the list names binaries explicitly — consider the same here, or a comment that the globs are intentional.

correctness (info, not blocking)

  • Verified against v26.10.1182 usr.raw: no pw_dict.{pwd,pwi,hwm} exists (body's cracklib claim holds), and no lvm2-monitor/dm-event/lvm2-lvmpolld units ship (the only name hits are text inside the lvmdump script), so dropping usr/bin/lvm/dmeventd leaves no dangling Exec*=. The end-to-end systemd-pull → gpg proof the body defers to CI is covered by boot-test (.github/workflows/build.yml:542-552); the full-build label is set, so that job should be green before merge.

No findings from: security, intent-alignment, docs-currency.

Confidence: 5/5 (safe)

— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

jeefy and others added 2 commits October 17, 2026 00:17
… /usr

/usr is shared by the diskless, disk and installer boots, so every entry
is something none of them runs. Groups added to files/prune/usr.list:

- GnuPG beyond verification: gpgsm, gpgtar, gpg-card, wks, dirmngr,
  gpgconf, gpg-connect-agent, scdaemon and the agent helpers, plus the
  libgpg-error/libgcrypt developer tools and Lisp bindings. gpg, gpgv,
  gpg-agent and keyboxd stay: systemd-pull (behind systemd-sysupdate)
  execs gpg, and bluefin-diskless-update-check runs gpgv. Checked by
  running gpg exactly as systemd-pull does, and gpgv, against the
  release SHA256SUMS(.gpg) in the pruned tree: good signature accepted,
  tampered manifest refused.
- cracklib tools and its uncompiled word list; no pw_dict is built into
  the image. libcrack stays for libpwquality.
- LVM2: lvm and its lv*/vg*/pv* links, dmeventd, fsadm, blkdeactivate,
  liblvm2cmd, libdevmapper-event and the dmeventd plugins, and
  11-dm-lvm.rules (acts only on LVM- devices) and 69-dm-lvm.rules (runs
  lvm pvscan). libdevmapper, dmsetup and 10-dm, 13-dm-disk and
  95-dm-notify.rules stay for dm-verity /usr and cryptsetup; the initrd
  is untouched.
- openssl CLI (libssl/libcrypto stay).
- cal, hexdump, look, mesg, more, rev, ul: nothing on a node runs them;
  less is the pager.
- sqlite3 CLI (libsqlite3 stays), build-time generators (compile_et,
  mk_cmds, asn1*, pcre2test, bashbug), Python scripts with no Python in
  the image (psl-make-dafsa, routel, xfs_protofile), libdevmapper's
  header and .pc, and zsh/fish completions with no zsh or fish.

just check-image against v26.10.1182 passes: 190 paths removed, 0 added,
no new or stale KNOWN_BROKEN waivers.

usr.raw:  196075520 -> 186998784 bytes (-8.7 MiB, -4.6%)
OS DDI:   480256512 -> 471179776 bytes
disk UKI:  81622960 ->  81627648 bytes (initrd unchanged)

Also note in ddi-installer-build.md that the check cannot see programs a
binary execs itself, and why systemd features are not disabled.

Assisted-by: Claude Fable 5.1 via GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…protect gpg-agent

Review follow-ups: the eleven entries initrd.list and usr.list both carried
move to common.list, which exists for exactly that; initrd.list is left
empty with a note, and the list test no longer requires each list to be
non-empty. The lv*/vg*/pv* globs are spelled out as the 48 lvm2 names FSDK
ships, so a future binary starting with those letters cannot be swept up
silently. The prune-list test now also protects gpg-agent and keyboxd,
which systemd-pull's gpg needs.

Assisted-by: Claude Fable 5.1 via GitHub Copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jeefy
jeefy force-pushed the feat/prune-usr-cli-tools branch from b2248ad to 0e26a8b Compare October 17, 2026 05:17

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HUMAN DECISION NEEDED — @jeefy asks for the full-build label so CI's boot-test runs an A/B systemd-sysupdate before merge; that is the only end-to-end proof of the pruned tree's gpg exec path, which check-image cannot see (.github/scripts/check-image.py:10-12 only follows Exec*= and udev RUN/PROGRAM/IMPORT). A maintainer must decide whether to gate merge on that run.

correctness

  • (open question, low) files/prune/usr.list:33 deletes usr/share/cracklib wholesale. elements/bluefin-server/os-filesystem.bst:20 composes with integrate: True, and FSDK's components/cracklib.bst integration command runs create-cracklib-dict -o /usr/share/cracklib/pw_dict … into that very directory, so a compiled pw_dict.{pwd,pwi,hwm} can exist there before prune.sh runs (elements/oci/bluefin-server-usr.bst:90). The PR body says the image never shipped one; please confirm pw_dict.* was not among the 190 removed paths in the check-image diff — if it was, pam_pwquality's default dictcheck would start failing every password change with "error loading dictionary".

style

  • files/prune/usr.list:118 — # SQLite CLI (gpg, keyboxd and liblastlog2 keep libsqlite3). is now an orphan comment: the usr/bin/sqlite3 entry moved to files/prune/common.list:43. Delete it (or move the note there).

No findings from: security, intent-alignment, docs-currency.

Confidence: 3/5 (needs attention) — 1 medium finding; 1 perspective requires a human decision

— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-build Run the full image build and boot test on this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant