Phase 0 deliverable per PLAN.md. Answers: which first-party administrative
capabilities depend on Python? (Not: which packages contain Python.)
Provenance: surveyed 2026-08-07 on Ubuntu 24.04.4 LTS (noble), headless server with some desktop packages installed. Every Ubuntu-column claim below traces to a command run or file read on this machine (see per-record Evidence lines and Appendix B). Scope is installed packages only; archive-only tools are out of scope. Cross-distro and KDE columns are knowledge-based (no network, no KDE on this box) and marked accordingly.
Five enumeration sweeps, unioned (raw outputs summarized in Appendix B):
- A. Shebang scan:
find /usr/bin /usr/sbin /usr/libexec -maxdepth 2 -type f -exec awk 'FNR==1 && /^#!.*python/ {print FILENAME} {nextfile}' {} +Limitation:-maxdepth 2, PATH bin dirs only; sweep E catches out-of-PATH scripts for candidate packages, but this is an inventory of observed paths, not a complete census. - B. Package mapping: sweep A through
xargs dpkg -S | cut -d: -f1 | sort | uniq -c. - C. Reverse-deps:
apt-cache rdepends --installedforpython3-apt,python3-dbus,python3-gi. Used as a candidate generator only; rdepends does not distinguish direct from transitive and is not treated as proof of administrative relevance. - D. Daemons:
ExecStartshebang resolution across/usr/lib/systemd/systemand/etc/systemd/system;Exec=resolution in/usr/share/dbus-1/system-services/. Misses generated units, drop-ins, and wrapper indirection; observed paths only. - E. Out-of-PATH scripts:
dpkg -L <pkg>+ executable shebang check per candidate package.
Per-record deep dives: head -1 (shebang), grep '^import\|^from' (direct imports),
dpkg -s (Depends), /usr/share/polkit-1/actions/ (privilege), systemctl unit and
timer state ("installed and active" vs merely installed), apt-cache policy (component).
Include a tool when all three hold:
- Shipped by Ubuntu/Debian — record archive component, source package, and maintainer
separately;
mainis not treated as synonymous with first-party, and Ubuntu-originateduniversepackages remain eligible. - Implemented in Python — the capability's entry point is a Python script or a Python package provides the logic. "Python implementation" is recorded separately from "Python dependency" (a non-Python wrapper over a Python backend counts; a Python wrapper over a C library is noted as such).
- Administers the system — mutates packages, repositories, configuration, hardware, services, or users; or reports system state to an administrator.
Exclusions (recorded in Appendix A): Python-library CLI stubs, dev tooling, third-party
applications, desktop end-user applications. bpfcc-tools (105 scripts) collapses to one
diagnostics row.
Each full record uses exactly these fields:
Program · Ubuntu package (version, component, source, maintainer) · Installed/active · Category · Implementation (language + entry point) · Daemon vs command · Python dependencies (direct) · Native APIs underneath · IPC/API used · Privilege model · Boot-critical? · Performance-sensitive? · Cross-distro equivalents (knowledge-based) · KDE prior art (knowledge-based) · Candidate Runix APIs · Replacement difficulty · Replacement value · Technically bounded? · Evidence
17 full records, 8 table-only rows. Headline findings:
- The Python footprint is concentrated in the apt/update ecosystem. 10 of 17 full records sit on python3-apt. Service management (systemctl: C), users/groups (adduser: Perl; accounts-daemon: C), udev, storage, and session management have zero Python. Runix Phase 2's apt+systemd slice covers the Python-heavy half and the native half in one stroke.
- The privilege pattern Runix needs already ships in 4 root Python D-Bus backends:
aptd (13 polkit actions), software-properties-dbus, ls-dbus-backend, and
usb-creator-helper — all D-Bus-activated as root, all gating each method through
polkit
CheckAuthorization. These are simultaneously prior art and replacement targets. - Canonical already migrates hot paths to C and leaves policy in Python: netplan
(C libnetplan1 + C boot generator, Python CLI), ubuntu-drivers-common (C gpu-manager
on the display-manager boot path, Python CLI), ubuntu-pro-client (C apt hook on the
apt upgradehot path, 32,700 lines of Python everywhere else). Runix replaces the policy layer; the C cores stay. - aptdaemon is largely vestigial on 24.04. Only language-selector-gnome and
update-notifier still use it; PackageKit 1.2.8 runs in parallel with its own C apt
backend (
libpk_backend_apt.so), which is what GNOME Software and Discover use. - Boot-critical Python is rare. cloud-init only — and it resolved to DataSourceNone and is disabled on this box. netplan's boot half is already C.
- Two persistent root Python daemons observed: unattended-upgrade-shutdown (resident
logind-inhibitor waiter, running now) and networkd-dispatcher (condition-skipped here;
systemd-analyze securityrates it 9.6 UNSAFE).
Full records (17):
| Program | Package | Category | Impl | Form | Privilege | Boot-crit | Perf-sens | Difficulty | Value |
|---|---|---|---|---|---|---|---|---|---|
| add-apt-repository, s-p-dbus | software-properties-common 0.99.49.4 | repositories | Python | cmd + D-Bus backend | polkit + root backend | no | no | medium | high |
| aptd, aptdcon | aptdaemon 1.1.1+bzr982 | package mgmt | Python | D-Bus daemon | polkit (13 actions) | no | no | high | low (vestigial) |
| command-not-found | command-not-found 23.04.0 | package mgmt | Python | shell hook | root via apt hook | no | mild | low | low-medium |
| ubuntu-security-status, hwe-support-status | update-manager-core 1:24.04.12 | updates | Python | cmd, read-only | unprivileged | no | no | low-medium | medium |
| unattended-upgrade (+shutdown) | unattended-upgrades 2.9.1 | updates | Python | timer cmd + resident inhibitor | root, no polkit | shutdown-path | no | medium-high | moderate |
| update-manager | update-manager 1:24.04.12 | updates, desktop | Python/GTK | GUI cmd | delegates to aptd+polkit | no | mild | high | moderate |
| apt-check, package-data-downloader | update-notifier-common 3.192.68.2 | updates | Python | apt-hook cmds | root | login-path | yes (apt hot path) | low-moderate | high |
| do-release-upgrade | ubuntu-release-upgrader-core 1:24.04.28 | updates | Python | cmd, long-running | root (pkexec/sudo) | no; bricks on failure | no | high | low-moderate |
| apport suite | apport 2.28.2 | crash reporting | Python (+C whoopsie) | kernel-invoked oneshot | root, unconfined | no | crash-time | high | low-moderate |
| ubuntu-drivers (+C gpu-manager) | ubuntu-drivers-common 1:0.9.7.6 | drivers | Python + C | cmd + C boot oneshot | euid check, root | C half yes | mild | medium-high | medium-high (query) |
| pro (+C apt hook) | ubuntu-pro-client 37.2 | security, updates | Python + C | cmd + timers | root; AppArmor-confined units | no | apt hook (already C) | high / blocked | low-moderate |
| cloud-init | cloud-init 26.1 | cloud provisioning | Python | 4 boot oneshots | root at boot | yes | boot latency | very high | low |
| netplan CLI | netplan.io 1.1.2 | networking | Python CLI over C core | cmd (+C generator, C dbus) | root, structural | C half only | mild | low-moderate | very high |
| ufw | ufw 0.36.2-6 | security, networking | Python | cmd + boot oneshot | root only | security-critical at boot | no | medium | medium-high |
| networkd-dispatcher | networkd-dispatcher 2.2.4 | networking | Python | persistent daemon | root, unhardened | no | no | low-moderate (native) | moderate |
| check-language-support, ls-dbus-backend | language-selector-common 0.225 | desktop admin | Python | cmd + D-Bus backend | polkit + root backend | no | no | low-moderate | moderate |
| usb-creator-helper | usb-creator-common 0.3.17 | desktop admin, hardware | Python | D-Bus backend | polkit + root, UDisks2 | no | write throughput | moderate | high |
Table-only (8) — observed in sweeps, excluded from full records for scope reasons:
| Program | Package | Category | Note |
|---|---|---|---|
| 105 BCC tracing scripts | bpfcc-tools 0.29.1 | diagnostics | native successors exist: bpftrace (C++), libbpf-tools (C) |
| mountstats, nfsiostat, nfsdclnts | nfs-common, nfs-kernel-server | diagnostics | read-only /proc parsers |
| ssh-import-id | ssh-import-id 5.11 | cloud provisioning | fetches SSH keys from Launchpad/GitHub; bounded |
| ec2metadata | cloud-guest-utils | cloud provisioning | IMDS query CLI |
| switcherooctl | switcheroo-control | hardware | Python client to a C D-Bus daemon |
| powerprofilesctl | power-profiles-daemon | hardware, power | Python client to a C D-Bus daemon |
| prime-select | nvidia-prime | drivers | GPU switch tool |
| hp-pkservice (+hplip suite) | hplip 3.23.12 | hardware, printing | HP upstream; root D-Bus service com.hp.hplip |
- Program:
add-apt-repository(aliasapt-add-repository),software-properties-gtk,software-properties-dbus - Ubuntu package: software-properties-common 0.99.49.4 (noble-updates/main, source: software-properties, maint: Michael Vogt); software-properties-gtk same version
- Installed/active: installed; D-Bus backend activates on demand (not resident)
- Category: repositories
- Implementation: Python 3. Entry points
/usr/bin/add-apt-repository(#!/usr/bin/python3),/usr/lib/software-properties/software-properties-dbus(#!/usr/bin/env python3) - Daemon vs command: CLI command + on-demand root D-Bus backend
- Python dependencies (direct):
python3-software-properties(softwareproperties.*),apt_pkg(python3-apt),python3-dbus,python3-gi(GLib main loop) - Native APIs underneath: libapt-pkg (via python3-apt); D-Bus; polkit. Notably hard-Depends on packagekit + gir1.2-packagekitglib (GUI driver install path)
- IPC/API used: system bus name
com.ubuntu.SoftwareProperties(/usr/share/dbus-1/system-services/com.ubuntu.SoftwareProperties.service) - Privilege model: unprivileged frontend → root Python D-Bus backend gated by polkit
actions in
com.ubuntu.softwareproperties.policy(e.g....applychanges"To change software repository settings, you need to authenticate"). CLI run as root writes directly. - Boot-critical?: no
- Performance-sensitive?: no
- Cross-distro (knowledge-based): Debian ships the same package; Fedora:
dnf config-manager(dnf5 is C++, dnf4 was Python); NixOS: declarative config, no imperative repo tool; Arch: manualpacman.conf - KDE prior art (knowledge-based): Kubuntu's
software-properties-qtis the same Python backend with a Qt frontend — KDE has no native reimplementation; Discover only toggles repos via PackageKit's RepoEnable - Candidate Runix APIs:
apt_repos()(deb822 + one-line parse),apt_repo_add()(incl.ppa:resolution),apt_repo_remove(), keyring management under/etc/apt/keyrings - Replacement difficulty: medium — sources.list/deb822 writing is bounded;
ppa:shortcut resolution needs the Launchpad API + GPG key fetch; GUI parity needs the polkit/D-Bus split - Replacement value: high — daily-use admin surface, and the D-Bus backend is the cleanest model of the privilege pattern Runix must handle
- Technically bounded?: mostly; Launchpad/key-fetch is the unbounded edge
- Evidence:
head -1, import grep,dpkg -s software-properties-common,com.ubuntu.softwareproperties.policy, sweep D D-Bus hit (2026-08-07)
- Program: aptd — D-Bus system daemon exposing transactional APT operations (install/remove/upgrade/update-cache/install-file/repo+config edits) to unprivileged desktop clients;
aptdconis its CLI client. - Ubuntu package: aptdaemon 1.1.1+bzr982-0ubuntu44 (noble/main, section admin, source aptdaemon, Ubuntu Developers); library split out as python3-aptdaemon (same version)
- Installed/active: Installed, not running.
busctl --system listshowsorg.debian.aptas(activatable)with no current owner; no aptd process. No systemd unit — activation is purely D-Bus. - Category: package management
- Implementation: Python 3. Entry points
/usr/sbin/aptdand/usr/bin/aptdcon, both#!/usr/bin/python3, both thin stubs; all logic in/usr/lib/python3/dist-packages/aptdaemon/(core.py, client.py, worker/aptworker.py, policykit1.py, debconf.py, lock.py). - Daemon vs command: Both.
aptdis a D-Bus-activated root daemon with idle-exit;aptdcona short-lived client. Third-party GUIs useaptdaemon.client.AptClientas a library. - Python dependencies (direct):
apt,apt_pkg,aptsources(python3-apt);dbus+dbus.mainloop.glib(python3-dbus);gi.repository.GObject/GLib+PackageKitGlib(network-state detection only);defer(python3-defer). Depends adds policykit-1, gir1.2-packagekitglib-1.0, iso-codes. - Native APIs underneath: libapt-pkg via python3-apt C++ bindings (full resolver, cache, dpkg invocation), GLib main loop, dpkg lock semantics (
lock.py), debconf passthrough over a pty, euid/egid drop for downloads. - IPC/API used: D-Bus system bus, name
org.debian.apt, object/org/debian/apt, per-transaction objects/org/debian/apt/transaction/<uuid>with progress/finished signals. Activation fileUser=root. Calls out toorg.freedesktop.PolicyKit1. - Privilege model: root daemon; per-operation polkit
CheckAuthorizationagainst 13 fine-grained actions inorg.debian.apt.policy(install-or-remove-packages,upgrade-packages,update-cache,change-repository,change-config,high-trust-repo, ...), mostlyauth_admin/auth_admin_keep. The granular action split is the design's main advance over "sudo apt". - Boot-critical?: no — purely on-demand
- Performance-sensitive?: no — transactions dominated by download and dpkg; Python cost only at daemon startup and progress-signal churn
- Cross-distro (knowledge-based): Fedora/RHEL: PackageKit (
packagekitd, C, dnf backend) + dnf5daemon-server; Debian: same aptdaemon package upstream; Arch: PackageKit alpm backend, rarely used; NixOS: no privileged package D-Bus service (nix-daemon plays a different role) - KDE prior art (knowledge-based): Discover uses PackageKit-Qt, not aptdaemon; the retired Muon/QApt
qaptworker(C++/Qt + polkit) was the closest KDE analogue. KDE's answer to "aptd in Python" is already "PackageKit-Qt in C++". - Candidate Runix APIs: transactional package service (
search/resolve/install/remove/upgrade, Transaction object with simulate/commit/cancel + typed progress events), repo/config management, fine-grained authorization check, debconf-style prompt channel - Replacement difficulty: high — thin Python veneer over libapt-pkg's resolver, dpkg-lock semantics, debconf-over-pty, and a stable public D-Bus API external GUIs link against
- Replacement value: low — largely vestigial on 24.04:
apt-cache rdepends --installedshows only language-selector-gnome and update-notifier'sbackend_helper.pyas consumers, while PackageKit 1.2.8 is installed with its own native apt backend (libpk_backend_apt.so) that does not proxy through aptd. The reusable ideas (polkit-gated transaction daemon, idle-exit lifecycle: 60s check interval, 10min timeout, per-transaction 300s/30s timers) matter more than the code. - Technically bounded?: yes for the daemon shell (D-Bus surface, polkit gate, queue, idle-exit); no for the payload — correctness is defined by libapt-pkg, so a replacement must keep calling it
- Evidence:
head/cat+ import grep on both entry points and core.py/console.py/aptworker.py/policykit1.py;dpkg -s aptdaemon python3-aptdaemon;org.debian.apt.policyaction ids;org.debian.apt.service;apt-cache rdepends --installed aptdaemon python3-aptdaemon;busctl --system list --activatable;ls /usr/lib/x86_64-linux-gnu/packagekit-backend/; core.py idle-exit at lines 2115-2140;grep -rn aptdaemonin LanguageSelector/ and update-notifier/ (2026-08-07)
- Program:
command-not-found(shell hook),cnf-update-db - Ubuntu package: command-not-found 23.04.0 (noble/main, source: command-not-found, maint: Michael Vogt)
- Installed/active: installed; hook fires on shell command-miss; DB refreshed by apt
Post-Invoke hook (
/var/lib/command-not-found/commands.db, 3.9 MB SQLite, current) - Category: package management (discovery)
- Implementation: Python 3. Entry points
/usr/lib/command-not-found,/usr/lib/cnf-update-db(both#!/usr/bin/python3) - Daemon vs command: command (shell-hook invoked); no daemon
- Python dependencies (direct):
python3-commandnotfound(CommandNotFound.*),apt_pkg(python3-apt) in cnf-update-db - Native APIs underneath: SQLite database; apt Commands-* index files fetched via
Acquire::IndexTargets(/etc/apt/apt.conf.d/50command-not-found) - IPC/API used: none (stdin/stdout with shell integration)
- Privilege model: unprivileged lookup; DB rebuild runs as root only because apt's
Post-Invoke runs as root — writes
/var/lib/command-not-found/ - Boot-critical?: no; failure is harmless
- Performance-sensitive?: mildly — runs on every missed command; startup latency is user-visible (relevant to R's startup-cost question)
- Cross-distro (knowledge-based): Fedora:
PackageKit-command-not-found(C); Arch:pkgfile(C); NixOS:command-not-foundover channel-generatedprograms.sqlite; Debian: same package - KDE prior art (knowledge-based): n/a — shell-level capability, no desktop analogue
- Candidate Runix APIs:
apt_command_search("cmd")over the Commands metadata / SQLite index - Replacement difficulty: low — bounded formats (SQLite schema + deb822 Commands files), two native precedents to crib from
- Replacement value: low-medium — good bounded proof of concept, but per codex review it exercises almost none of the privileged-administration architecture
- Technically bounded?: yes
- Evidence:
head -1, import grep,dpkg -s command-not-found,ls /var/lib/command-not-found/,50command-not-foundapt hook (2026-08-07)
- Program:
ubuntu-security-status,hwe-support-status - Ubuntu package: update-manager-core 1:24.04.12 (noble-updates/main, source: update-manager, maint: Ubuntu Developers)
- Installed/active: installed; run on demand
- Category: updates / security reporting
- Implementation: Python 3. Entry points
/usr/bin/ubuntu-security-status,/usr/bin/hwe-support-status(both#!/usr/bin/python3) - Daemon vs command: command, read-only
- Python dependencies (direct):
apt(python3-apt high-level),distro_info,UpdateManager.Core.utils(python3-update-manager); package Depends adds ubuntu-pro-client + ubuntu-release-upgrader-core - Native APIs underneath: libapt-pkg cache and package origins (archive/pocket classification: main vs universe vs ESM)
- IPC/API used: none; reads apt state, prints report (has
--format jsonvia argparse/json import) - Privilege model: fully unprivileged
- Boot-critical?: no
- Performance-sensitive?: no
- Cross-distro (knowledge-based): Fedora:
dnf updateinfo(built into dnf); Debian:debsecan(also Python); Arch:arch-audit(Rust); NixOS:vulnix(third-party) - KDE prior art (knowledge-based): Discover surfaces per-update security
classification via PackageKit
update-details(PK_INFO_ENUM_SECURITY) — the data model, not a reimplementation - Candidate Runix APIs:
apt_security_status()returning a data frame (package, origin, pocket, ESM coverage, support horizon) — pure Phase 1 introspection - Replacement difficulty: low-medium — read-only over apt cache + origin metadata; ESM/Pro coverage needs the Pro contract data the tool currently gets via ubuntu-pro-client
- Replacement value: medium — admins read this output; perfect introspection-first fit
- Technically bounded?: yes
- Evidence:
head -1, import grep,dpkg -s update-manager-core, sweep E hits (2026-08-07)
- Program:
/usr/bin/unattended-upgrade(2522-line Python script); helper/usr/share/unattended-upgrades/unattended-upgrade-shutdown(414 lines); alias/usr/bin/unattended-upgrades - Ubuntu package: unattended-upgrades 2.9.1+nmu4ubuntu1 (noble/main, source unattended-upgrades, orig. maint Michael Vogt)
- Installed/active: installed;
unattended-upgrades.serviceenabled + active (PID 2091 is the shutdown helper resident in--wait-for-signalmode); apt-daily.timer and apt-daily-upgrade.timer enabled + active; logs in/var/log/unattended-upgrades/ - Category: updates
- Implementation: pure Python 3; heavy lifting delegated to python3-apt over libapt-pkg
- Daemon vs command: both. The upgrade run is a one-shot fired from the timer chain (
apt-daily-upgrade.timer→apt.systemd.daily install(shell) →unattended-upgradeat line 495). Separately,unattended-upgrades.serviceis a long-lived Python process holding a logind delay-inhibitor to finish upgrades before shutdown. - Python dependencies (direct):
apt,apt_pkg,apt_inst,distro_info, optionalgi.repository.Gio.NetworkMonitor; shutdown helper addsdbus+dbus.mainloop.glib+ GLib. Depends: debconf, python3-apt, python3-dbus, python3-distro-info, ucf, lsb-release, xz-utils - Native APIs underneath: libapt-pkg (
apt_pkg.config,apt.Cache,apt_pkg.pkgsystem_lock_inner()), dpkg subprocess withDPkg::Options, GIO metered-network check, fcntl locking, syslog - IPC/API used: D-Bus to
org.freedesktop.login1—Manager.Inhibit(delay lock), readsInhibitDelayMaxUSec, subscribes toPrepareForShutdown; ships a logind drop-in raisingInhibitDelayMaxSec=30. Hooks in/etc/kernel/postinst.d/, systemd-sleep, MOTD fragment92-unattended-upgrades. - Privilege model: root, unconditionally. No polkit, no sandboxing directives on the units (
KillMode=process,TimeoutStopSec=1800only). Full package-installation authority. - Boot-critical?: no for boot; shutdown-path-relevant — a misbehaving replacement can stall shutdown up to 30 min
- Performance-sensitive?: no — 06:00 with 60m random delay; resident helper ~8 MB RSS, 38 ms CPU over 10 days
- Cross-distro (knowledge-based): Fedora
dnf-automaticis likewise Python; NixOSsystem.autoUpgrade(declarative); openSUSE zypper-automatic/YaST. The pattern is universal, the Python is incidental to the apt/dnf bindings. - KDE prior art (knowledge-based): Discover prefers PackageKit offline updates (staged, applied at reboot) — architecturally the opposite of live in-place apply; the C++/D-Bus precedent for "keep the box patched" exists
- Candidate Runix APIs: package transaction with origin/allowlist filtering; system-lock acquisition; logind inhibitor lease +
PrepareForShutdownsubscription; network state (online/metered); AC-power state; reboot-required signalling - Replacement difficulty: medium-high — the core loop is bounded, but the tail is long: dry-run/chroot modes,
MinimalStepsunlock/relock dance, dpkg journal recovery, mail reporting, needrestart, exact apt-config parity - Replacement value: moderate — high visibility and a genuine win removing Python from the shutdown-inhibitor path, but the real work already lives in libapt-pkg; a rewrite mostly re-expresses policy
- Technically bounded?: partly — the happy path yes; correctness lives in the failure modes (interrupted dpkg, shutdown races, ESM origin matching), exactly where damage occurs
- Phase 4 fit: excluded per PLAN.md — unattended root mutation of the security posture with no operator present, plus the shutdown-stall hazard
- Evidence:
dpkg -s; shebangs + import greps on both scripts;systemctl cat unattended-upgrades.service apt-daily{,-upgrade}.timer;grep -n unattended /usr/lib/apt/apt.systemd.daily(lines 479-502);/etc/apt/apt.conf.d/{20auto-upgrades,50unattended-upgrades}; logind drop-in (2026-08-07)
- Program:
/usr/bin/update-manager— "Software Updater", the GTK updater dialog - Ubuntu package: update-manager 1:24.04.12 (noble-updates/main, section gnome, source update-manager) + update-manager-core; library python3-update-manager
- Installed/active: installed; a session instance was running at survey time (
--no-update --no-focus-on-map); no systemd unit - Category: updates, desktop administration
- Implementation: Python 3 + GTK3/libhandy; library in
/usr/lib/python3/dist-packages/UpdateManager/ - Daemon vs command: foreground GUI command; privileged work delegated to the aptdaemon system daemon
- Python dependencies (direct):
gi(Gtk, Gio, GLib, Handy, Snapd),dbus,apt/apt_pkg,distro_info,uaclient.api(Ubuntu Pro),DistUpgrade.DistUpgradeCache,aptdaemon.client+aptdaemon.gtk3widgets,defer,yaml, optionallaunchpadlib - Native APIs underneath: libapt-pkg (cache/depcache/policy); snapd REST socket via Snapd GIR; NetworkManager D-Bus for metered detection; aptd's libapt underneath the transaction
- IPC/API used: session D-Bus (single-instance), system D-Bus to
org.debian.apt, polkit via aptd, snapd UNIX socket, HTTPS to changelogs.ubuntu.com and meta-release - Privilege model: runs unprivileged; escalation fully delegated. Verified in
UpdateManager/backend/__init__.py:418-465:get_backend()picksInstallBackendAptdaemonwhen/usr/sbin/aptdexists, falls back to synaptic — synaptic is not installed, so aptdaemon is the only working backend on this box. PackageKit is installed but unused by update-manager. - Boot-critical?: no
- Performance-sensitive?: mildly — full cache open + network fetches make startup multi-second
- Cross-distro (knowledge-based): GNOME Software (C, PackageKit/flatpak plugins) on Fedora; the generic layer others standardize on is PackageKit's D-Bus API + offline updates
- KDE prior art (knowledge-based): Discover's update page over PackageKit-Qt; on Kubuntu it drives apt through PackageKit's aptcc backend, not aptdaemon.
plasma-pk-updatesdid the notifier job before folding into Discover. - Candidate Runix APIs:
packages.list_upgradable(),packages.upgrade()with progress/cancel streaming,packages.changelog(),system.reboot_required(),subscription.status(),network.is_metered() - Replacement difficulty: high — the Python is a shell, but parity needs apt resolution, phased-update filtering, ESM display, HWE status, release-upgrade handoff, snap refresh, a GUI, and a privileged transaction service
- Replacement value: moderate — user-facing, runs only when opened; value concentrates in the transaction/authorization layer it borrows from aptdaemon
- Technically bounded?: partly — the UI layer yes; the transitive surface (aptdaemon, DistUpgrade, uaclient, snapd, meta-release) pulls in most of the update stack
- Evidence: shebang + import greps;
dpkg -s update-manager(Depends:python3-aptdaemon.gtk3widgets ... | synaptic, pkexec, polkitd);backend/__init__.py:418-465;dpkg -lsynaptic absent, aptdaemon present; live process list (2026-08-07)
- Program:
/usr/lib/update-notifier/apt-check— computes upgradable/security/ESM counts and feeds the MOTD "N updates" block;/usr/lib/update-notifier/package-data-downloaderfetches deferred package data. (The tray daemon/usr/bin/update-notifieris C — not part of this record.) - Ubuntu package: update-notifier-common 3.192.68.2 (noble-updates/main, section gnome, source update-notifier, maint Michael Vogt)
- Installed/active: installed;
update-notifier-download.timerenabled+active (24h),motd-news.timerenabled+active; live output/var/lib/update-notifier/updates-availablecurrent ("14 updates ... 59 additional security updates with ESM Apps") - Category: updates
- Implementation: Python 3, driven by shell glue (
update-motd-updates-available,/etc/update-motd.d/90-updates-available) - Daemon vs command: one-shot commands — apt-hook-invoked (apt-check) and timer-invoked (package-data-downloader). No Python daemon.
- Python dependencies (direct): apt-check:
apt,apt_pkg,distro_info, lazyUpdateManager.Core.UpdateListfor phased-update exclusion; downloader:debian.deb822,debconf,apt_pkg. Depends: python3-apt, python3-dbus, python3-debian, python3-distro-info, update-manager-core - Native APIs underneath: libapt-pkg (origin/archive matching incl.
UbuntuESM/UbuntuESMApps), debconf protocol, dpkg status, the Pro ESM cache at/var/lib/ubuntu-advantage/apt-esm/ - IPC/API used: file-based. apt hook
/etc/apt/apt.conf.d/99update-notifier(DPkg::Post-Invoke+APT::Update::Post-Invoke-Success) → staleness check →apt-check --human-readable→ atomicmvinto the stamp file → MOTDcats it at login - Privilege model: root (apt hooks + system timer); apt-check itself is read-only and runs unprivileged for reporting. Ships pkexec actions (
com.ubuntu.update-notifier.policy) for the GUI side only. - Boot-critical?: no, but on the login path (MOTD reads the precomputed file — cheap; the cost is paid during apt runs)
- Performance-sensitive?: yes — apt-check opens the full apt cache and walks every package on every
apt updateand every dpkg transaction; a known contributor to Ubuntu apt slowness - Cross-distro (knowledge-based): Ubuntu-specific. Debian has no MOTD update counter; Fedora uses dnf-makecache + insights banners; Arch
checkupdates. The ESM upsell portion is unique to Ubuntu. - KDE prior art (knowledge-based): Discover polls PackageKit (
GetUpdates+UpdatesChangedsignals) — the count comes from a daemon's cached state over D-Bus rather than a per-apt-run subprocess writing a text file; strictly cheaper for the same information - Candidate Runix APIs:
packages.update_counts()(total/security/phased-held, cached),packages.cache_stale(), MOTD fragment registration with staleness handling,packages.hooks.on_transaction_complete() - Replacement difficulty: low-to-moderate for apt-check — a single self-contained script whose logic is "walk the depcache, classify by origin, pluralize"; the downloader is harder in proportion to its debconf surface
- Replacement value: high — root, on the apt hot path, runs on every transaction, output contract is one text file; a native implementation removes an interpreter start + full cache walk from every apt operation
- Technically bounded?: yes for apt-check (inputs and the one-file output are fully enumerable); less so for package-data-downloader (executes per-package hook scripts)
- Evidence: shebangs + import greps (lazy import at apt-check:452);
dpkg -s update-notifier-common;update-motd-updates-available:62;99update-notifierhooks; live stamp file contents; timer states (2026-08-07)
- Program:
do-release-upgrade— release-to-release upgrade driver;check-new-release(argv[0]-dispatched symlink),do-partial-upgrade, MOTD hook - Ubuntu package: ubuntu-release-upgrader-core 1:24.04.28 (noble-updates/main, source ubuntu-release-upgrader); logic in python3-distupgrade; GTK frontend also installed
- Installed/active: installed and wired (
Prompt=lts, MOTD stamp current, meta-release cache fresh); a real upgrade ran on this box 2026-05-05 (/var/log/dist-upgrade/). No dedicated timer — piggybacks on MOTD machinery. - Category: updates
- Implementation: Python 3;
/usr/lib/python3/dist-packages/DistUpgrade/— 29 modules, ~14,200 lines, with Text/GTK3/KDE/NonInteractive view classes. Zero compiled code. - Daemon vs command: one-shot interactive command, long-running (minutes to hours)
- Python dependencies (direct):
DistUpgrade.*,UpdateManager.Core.MetaRelease,apt,apt_pkg,aptsources,dbus,gi.repository.{Gio,GLib},distro_info. Depends: python3-distupgrade → python3-apt, python3-dbus, python3-update-manager, gpgv, procps - Native APIs underneath: libapt-pkg (
apt_pkg.Acquirefor downloads,TagFilefor meta-release parsing, config tuning incl.--force-overwrite), aptsources rewriting, dpkg/apt as the engine, logind sleep inhibitor via GIO fd-passing,apt-key/gpgv verification, shell-outs to screen/sudo/pkexec/sshd/snap/systemctl - IPC/API used: HTTPS to
changelogs.ubuntu.com/meta-release-lts(If-Modified-Since cached); the meta-release index yieldsUpgradeTool/UpgradeToolSignatureURLs — the tool downloads a signed tarball andos.execvs the extracteddist-upgrade.py, so the code that actually performs the upgrade is fetched at runtime, not the code on disk. D-Bus: logindInhibit("shutdown:sleep"), session ScreenSaver inhibit. - Privilege model: root required; GUI path re-execs via pkexec (
com.ubuntu.release-upgrader.policy), CLI viasudo -E. Session-survival machinery: re-exec inside GNU screen when needed, spare sshd on port 1022 for SSH upgrades. Only screen is supported, and screen is not installed here (tmux only) — that safety net silently no-ops on this machine. - Boot-critical?: no — but the highest-blast-radius tool in this survey: rewrites all apt sources then dist-upgrades the running system; mid-run failure routinely leaves an unbootable mixed-release box. The screen/sshd/inhibitor/btrfs-snapshot machinery exists precisely because of that.
- Performance-sensitive?: no — dominated by download and dpkg. Only
check-new-release -qon the MOTD path is latency-relevant, hence backgrounded + 24h stamp. - Cross-distro (knowledge-based): Fedora
dnf system-upgradeis also Python (staged offline, safer shape); openSUSEzypper dupis C++ on libzypp; Arch rolling; Debian's documented path is manual sources editing +apt full-upgrade. Ubuntu is the outlier in bespoke Python policy (quirks, deb→snap migration, demotion lists). - KDE prior art (knowledge-based): Kubuntu ships
DistUpgradeViewKDE.pyin this very package (PyQt5) — a frontend veneer. PackageKit exposesUpgradeSystem()/GetDistroUpgrades()but delegates the actual hop to the distro. No reimplementation prior art. - Candidate Runix APIs: meta-release fetch/parse; signed-artifact verification; sources.list suite rewriting; session-detachment primitive (screen/tmux/systemd-run scope) for long privileged operations; logind inhibition with fd lifetime; snapshot-before-upgrade
- Replacement difficulty: high —
DistUpgradeQuirks.pyalone is 2,143 lines of per-release institutional knowledge with no spec, and the on-disk code is mostly a bootstrapper for the runtime-downloaded tarball. Correctness bar: bugs brick machines. - Replacement value: low-to-moderate — runs once per machine every 6-24 months; the genuine wins (session detachment that actually works — see the dead screen fallback here — snapshots, resumable transactions) are better delivered as Runix primitives the existing tool could adopt
- Technically bounded?: no — the quirks corpus grows every release and effective behavior is not determined by anything on this disk. Bounded extractable sub-pieces: meta-release parsing, signature verification, sources rewriting, session detachment, inhibition.
- Evidence:
dpkg -son all three packages; shebangs/imports;wc -l DistUpgrade/*.py(14,218);DistUpgradeFetcherCore.py:95-145(verify+extract),:211-256(sudo re-exec);DistUpgradeMain.py:164-220(screen re-exec);DistUpgradeController.py:320-359(port-1022 sshd),:1777-1841(btrfs snapshot);MetaRelease.py:349-364;/etc/update-manager/{meta-release,release-upgrades};/var/log/dist-upgrade/dates;which screen tmux(2026-08-07)
- Program:
ubuntu-drivers(list/devices/install/autoinstall),nvidia-detector,quirks-handler, and the Cgpu-manager - Ubuntu package: ubuntu-drivers-common 1:0.9.7.6ubuntu3.7 (noble-updates/main, source same, Ubuntu Developers)
- Installed/active: installed;
gpu-manager.serviceenabled, ran at boot (oneshot, 65 ms), now dead. CLI on-demand. - Category: drivers
- Implementation: mixed C + Python — the key finding. Python:
/usr/bin/ubuntu-drivers(553 lines, click-based) +UbuntuDrivers/detect.py(1,704) + NvidiaDetector (~600) ≈ 3,000 lines. C:/usr/bin/gpu-manager, 80 KB stripped ELF linking libpci, libdrm, libkmod, libudev. Shell helperu-d-c-print-pci-ids. - Daemon vs command: Python side pure CLI; C gpu-manager is a boot oneshot ordered
Before=display-manager.service, plus udev rules stamping/run/u-d-c-*flag files on DRM/nvidia module events - Python dependencies (direct):
apt_pkg,click,xkit(xorg.conf editing); subprocess tomodinfo,udevadm hwdb,apt-get - Native APIs underneath: sysfs walk of
/sys/devices/**/modalias; apt package-recordModaliases:headers parsed into a bus→alias→package map; kmod; libpci/libdrm/libkmod/libudev in the C half - IPC/API used: no D-Bus, no polkit — subprocess + apt cache +
/runflag files. The Python API (import UbuntuDrivers.detect) is a first-class documented interface. - Privilege model: explicit euid check (
ubuntu-drivers:422"must be run as root"), then rootapt-get install. Query subcommands unprivileged. gpu-manager root at boot. - Boot-critical?: partially — the C half gates display-manager; the Python half is not on any boot path
- Performance-sensitive?: mildly — full sysfs walk + all-package Modaliases scan makes
listtake seconds (memoized); the boot-path piece is C for exactly this reason - Cross-distro (knowledge-based): largely Ubuntu-unique (apt Modaliases headers, DKMS/OEM metapackage conventions). Debian's
isenkramborrows the modalias idea; Fedora relies on RPM Fusion with no auto-candidacy tool. - KDE prior art (knowledge-based): weak — this package
Provides:the deadjockey-kde; modern KDE has no driver-installation module (kinfocenter only reports) - Candidate Runix APIs: modalias enumeration as a data frame (device, sysfs path, bus, IDs, bound module); driver candidacy as a joinable data frame (device × package × free/nonfree × recommended × installed); apt Modaliases index reader; install action with dry-run plan
- Replacement difficulty: medium-high for parity, low-medium for the useful part — detection/candidacy is well-factored Python over apt_pkg + sysfs; the NVIDIA version-ordering policy and the C boot component are the hard/untouchable parts
- Replacement value: medium-high for querying (today's only interfaces: unstructured CLI text or undocumented Python API); low for installing (thin apt-get shell-out)
- Technically bounded?: yes for the query layer; no for the whole package (C gpu-manager + open-ended NVIDIA quirks)
- Evidence:
dpkg -L | grep bin;file+ldd /usr/bin/gpu-manager;gpu-manager.service; udev rules71-u-d-c-gpu-detection.rules; shebang/imports;grep -n modalias detect.py(62 hits);grep -n geteuid /usr/bin/ubuntu-drivers;wc -l(2026-08-07)
- Program:
/usr/sbin/netplan→/usr/share/netplan/netplan.script(23-line Python shim:from netplan_cli import Netplan; Netplan().main()). Subcommands: apply, generate, get, set, try, status, info, ip, migrate, sriov-rebind. - Ubuntu package: netplan.io 1.1.2-8ubuntu1~24.04.2 (noble-updates/main, priority important, source netplan.io). Lockstep siblings from the same source: libnetplan1 (C), netplan-generator (C), python3-netplan (cffi bindings).
- Installed/active: installed and in use —
/etc/netplan/01-network-manager-all.yamlsetsrenderer: NetworkManager; renderer output live under/run/NetworkManager/. NetworkManager active; systemd-networkd inactive. - Category: networking
- Implementation: Python CLI over a C core — the migration pattern Runix wants, spelled out end to end. YAML grammar, netdef model, and backend emission live in
libnetplan.so.1(354 KB, 97 exportednetplan_*symbols). Python is ~5,700 lines of argument parsing, subprocess orchestration, and rich formatting, plus ~890 lines of cffi shim. The CLI never parses netplan YAML itself. - Daemon vs command: command; two C companions —
/usr/libexec/netplan/generate(systemd system-generator, runs in early boot) andnetplan-dbus(D-Bus-activatedio.netplan.Netplan, root) - Python dependencies (direct): own cffi bindings (
_netplan_cffi.abi3.so),yaml(auxiliary only),rich(status output); stdlib otherwise. No click, no requests, no dbus-python — shells tobusctlwhen it needs D-Bus. - Native APIs underneath: libnetplan1 → libyaml, glib/gio, libuuid; generator and netplan-dbus link libsystemd (sd-bus). Kernel effects via
ip/networkctl/nmclisubprocesses (11/5/6 call sites). - IPC/API used: D-Bus
io.netplan.Netplan(system bus, root-owned,AssumedAppArmorLabel=unconfined);netplan trycoordinates rollback via a stamp file - Privilege model: root, enforced structurally (writes to /etc/netplan, /run/systemd/network, /run/NetworkManager) — no euid gate, no polkit
- Boot-critical?: yes — but the boot-critical part is C, not Python. systemd runs the C generator in early boot; Python is never on the boot path. Canonical packaged them separately precisely so early boot needs no interpreter.
- Performance-sensitive?: mildly — generator is C for the boot path; the CLI is interactive
- Cross-distro (knowledge-based): Canonical project; exists in Debian but non-default; Fedora/RHEL/Arch configure networkd/NM directly. Ubuntu-shaped blast radius — an advantage for a targeted migration.
- KDE prior art (knowledge-based): KDE bypasses netplan entirely — plasma-nm → NetworkManagerQt → NM D-Bus, one layer below. Demonstrates the native-binding instinct but offers no prior art for the netplan layer itself.
- Candidate Runix APIs:
netplan_get()/set()/apply()/try()/generate()/status()over a netdef object model. An R binding to libnetplan1 would be a native-first Runix package — linking the same C ABI the Python cffi shim consumes (strong evidence the surface is FFI-friendly). Note: no pkg-config file ships; declare-lnetplandirectly (libnetplan-dev is in the archive, uninstalled here). - Replacement difficulty: low-to-moderate — the lowest-risk high-value target in this survey. None of the hard work is in Python. The fiddly parts:
netplan status(725 lines) andnetplan try(termios/signals rollback). - Replacement value: very high — the key exemplar of "swap the Python CLI, keep the C core". The seam is already cut and load-bearing in the shipped 4-package design; Runix replaces exactly one of the four. Every other candidate should be measured against how closely it approximates this shape.
- Technically bounded?: yes, unusually cleanly — one C ABI (97 symbols, versioned), one D-Bus interface, one YAML schema owned by the C side, 8 external commands. The full behavioral contract is enumerable from installed files.
- Evidence:
head -1+file /usr/sbin/netplan;dpkg -s netplan.io python3-netplan libnetplan1(lockstep versions);file+ldd /usr/libexec/netplan/generate;ls -la /usr/lib/systemd/system-generators/netplan;nm -D libnetplan.so.1 | grep ' T '(97 symbols); D-Bus service + policy files;/etc/netplan/*.yaml; renderer state under /run (2026-08-07)
- Program:
/usr/bin/networkd-dispatcher— runs hook scripts on systemd-networkd link-state changes; the networkd analogue of NetworkManager-dispatcher - Ubuntu package: networkd-dispatcher 2.2.4-1 (noble/main, priority important, section utils, orig. maint Julian Andres Klode)
- Installed/active: installed; service enabled but inactive for two verified reasons:
ConditionPathExistsGlobunmet (all 12 hook dirs empty) and this box runs NetworkManager, not systemd-networkd - Category: networking
- Implementation: pure Python 3, single 547-line script
- Daemon vs command: persistent daemon —
Type=notify, GLib main loop, hand-rolled sd_notify overNOTIFY_SOCKET(AF_UNIX datagram) rather than libsystemd - Python dependencies (direct):
dbus+dbus.mainloop.glib,gi.repository.GLib; stdlib otherwise. Depends: dbus, python3-dbus, python3-gi. - Native APIs underneath: systemd-networkd D-Bus — subscribes to
PropertiesChangedonorg.freedesktop.network1.Linkobjects, readsOperationalState/AdministrativeState. Caveat: also shells out tonetworkctl list/statusand regex-parses the text — exactly the CLI-scraping pattern PLAN.md tells Runix to avoid. - IPC/API used: D-Bus signal subscription (consumer only); sd_notify datagram;
subprocess.Popenper hook with synthesized environment (IFACE, STATE, ESSID, JSON blob) - Privilege model: root, unhardened — no
User=, full 41-capability bounding set, zero sandbox directives;systemd-analyze securityscores it 9.6 UNSAFE - Boot-critical?: no — machine boots and networks fine with it condition-skipped (as now)
- Performance-sensitive?: no — rare events; cost is idle RSS of a root Python+dbus+GObject process for the machine's uptime
- Cross-distro (knowledge-based): NetworkManager-dispatcher is C, inside NM itself — the same problem already has a mature native solution in the NM world; networkd-dispatcher exists only because networkd upstream declines to ship one. All distros package this identical Python script; no native reimplementation exists anywhere.
- KDE prior art (knowledge-based): no dispatcher equivalent; the relevant KDE lesson is the binding layer (NetworkManagerQt-style typed D-Bus proxies), not a dispatcher
- Candidate Runix APIs: D-Bus signal-subscription primitives; a typed
org.freedesktop.network1binding (Link objects as structured properties, replacing networkctl scraping); sd_notify; an ordered hook-directory executor with environment injection - Replacement difficulty: low-to-moderate — small logic, stable documented D-Bus API. Per PLAN.md the replacement should be native (C/Rust), not R — persistent privileged event-driven daemon is precisely the shape PLAN.md assigns to native code, with R as the client.
- Replacement value: moderate — canonical target shape (removes a persistent root Python interpreter and the CLI-scraping), but inert on NM desktops; value concentrates on networkd servers/cloud images
- Technically bounded?: yes — single file, one D-Bus interface, one output contract (exec hooks with fixed env). Fuzzy edge: optional ESSID via
iw/iwconfig, replaceable with nl80211 or droppable. - Evidence:
dpkg -s; shebang/imports (bus.add_signal_receiverat 285-288, network1.Link filter at 395, sd_notify at 450); full unit text (twoConditionPathExistsGlob=|, zero hardening);systemctl show -p ConditionResult -p User -p CapabilityBoundingSet;systemd-analyze security9.6; hook dirs all empty; NM active / networkd inactive (2026-08-07)
- Program:
/usr/sbin/ufw— "Uncomplicated Firewall" CLI; 159-line driver over/usr/lib/python3/dist-packages/ufw/(~216 KB: frontend, parser, backend_iptables, applications) - Ubuntu package: ufw 0.36.2-6 (noble/main, section admin, priority standard, source ufw, maint Jamie Strandboge)
- Installed/active: installed;
ufw.serviceenabled and "active (exited)" — but the firewall is off:/etc/ufw/ufw.confhasENABLED=no, andufw-init start quietexits 0 without loading chains - Category: security, networking
- Implementation: Python 3 end to end, stdlib only — no compiled extension, no third-party modules, no dbus, no python-nftables
- Daemon vs command: command + boot oneshot (
Type=oneshot,RemainAfterExit=yes,Before=network-pre.target,DefaultDependencies=no); the kernel does all filtering, ufw only loads rules - Python dependencies (direct): stdlib only. Depends: iptables, ucf, debconf.
- Native APIs underneath: none directly — shells to iptables binaries, which on this box are Debian alternatives pointing at iptables-nft: the real kernel path is nf_tables via the compat layer (
lsmodconfirms nft_compat). ufw itself contains zero references to nft; it is iptables-syntax-only. - IPC/API used: subprocess only — bulk loads via
cat rules | iptables-restore -n(backend_iptables.py:587-595); enable/disable delegate to the shellufw-init. fcntl lock on/run/ufw.lock. No D-Bus, no polkit, no socket. - Privilege model: root-only, setuid-free; rule files 0640 root:root (verified: permission denied as user). Authority is caller's uid 0 + CAP_NET_ADMIN in the iptables children.
- Boot-critical?: not for reaching a usable system — but security-critical at boot: the oneshot exists to install rules before interfaces come up; if it fails with
ENABLED=yesthe box boots fully open. Low availability risk, high exposure risk. - Performance-sensitive?: no — kernel filters at line rate; Python runs only at rule-edit time and boot, via one batched restore transaction
- Cross-distro (knowledge-based): firewalld (Fedora/RHEL/SUSE default) is also Python — a D-Bus daemon over python-nftables/libnftables. Arch/Gentoo: raw
nftor restore scripts. Policy layer is Python across both dominant families; mechanism is C everywhere. - KDE prior art (knowledge-based): plasma-firewall KCM has pluggable ufw and firewalld backends; the ufw backend invokes the CLI through a KAuth/polkit helper and screen-scrapes the text output. That KAuth-wrapping-a-CLI seam is exactly what a typed Runix API would replace. Not installed here.
- Candidate Runix APIs: typed rule objects (direction/action/proto/port/CIDR/iface/log) replacing the string grammar; non-root-readable status via a privileged read service; transactional apply speaking nftables netlink directly with atomic rollback; app profiles; change events so GUIs need not re-parse
- Replacement difficulty: medium — clean two-layer split and no daemon/state machine, but the rule grammar is large and accreted (limit, route, app profiles, IPv6 mirroring, the ufw-before/user/after chain topology), and on-disk
/etc/ufw/*.rules+ CLI compatibility must hold or fail2ban/docker-ufw guides/plasma-firewall break - Replacement value: medium-high — native nftables transaction instead of
cat | iptables-nft-restore, typed API for GUI clients, drops the compat translation layer, removes Python from theBefore=network-pre.targetpath. Tempered by: stable, rarely touched, and a wrong rule is a security incident. - Technically bounded?: yes — one CLI (documented), six rule files, one config, one shell shim, one systemd oneshot, one external interface. Scope knowable up front.
- Evidence: shebang + imports;
dpkg -s ufw;grep -rn nftin ufw/ (zero hits);grep -n restore backend_iptables.py(587-595);update-alternatives --query iptables→ iptables-nft;lsmod | grep nf_tables; unit text;/etc/ufw/ufw.confENABLED=no; 0640 perms verified as user (2026-08-07)
- Program:
pro(/usr/bin/ubuntu-advantage,uasymlinks) — Ubuntu Pro entitlement client: attach/detach, ESM/livepatch/FIPS enablement, CVE/USN fixes, security-status, apt/MOTD messaging - Ubuntu package: ubuntu-pro-client 37.2ubuntu~24.04.1 (noble-updates/main, priority important, source ubuntu-advantage-tools)
- Installed/active: installed, machine not attached (no machine-token;
ua-timer.timercondition-gated off; apt-news/esm-cache static, apt-triggered) - Category: security, updates
- Implementation: Python 3 —
/usr/lib/python3/dist-packages/uaclient/, 168 files, ~32,700 lines. Plus one C++ binary:/usr/lib/ubuntu-advantage/apt-esm-json-hook(51 KB ELF linking libapt-pkg, libjson-c) speaking apt's JSON hook protocol on theapt upgradehot path. (The olderapt-esm-hookno longer exists in 37.2.) - Daemon vs command: on-demand CLI + oneshot timers (
ua-timer6h with 1h jitter); no resident daemon. apt integration:Pre-Invokefires apt-news/esm-cache viasystemctl start --no-block; the Upgrade hook runs the C++ binary synchronously. - Python dependencies (direct):
yaml,apt/apt_pkg,pycurl(proxy edge case); stdlib urllib/json/argparse. Depends: python3-yaml, python3-apt, distro-info + the C-side libapt-pkg/libjson-c. - Native APIs underneath: libapt-pkg (both via python3-apt and directly in the C++ hook), apt sources/preferences/auth files, ESM apt cache under
/var/lib/ubuntu-advantage/apt-esm/, systemctl subprocesses, cloud metadata services, MOTD, AppArmor profiles for the apt-triggered units - IPC/API used: HTTPS to
contracts.canonical.com(attach/refresh/metering; server can push polling intervals);motd.ubuntu.com/aptnews.json; apt JSON hook protocol (fd handshake); a stable versioned machine-readable API:pro api <endpoint>with ~30 versioned JSON endpoints (u.pro.status.is_attached.v1,u.pro.security.cves.v1, ...). No D-Bus service. - Privilege model: root for mutations (
NonRootUserErrorgate); no polkit, no privilege separation —sudo pro. Read-only commands work unprivileged. apt-news/esm-cache run as root but AppArmor-confined with capability bounding and address-family restrictions. - Boot-critical?: no — all units condition-gated, no-op unattached; failure degrades ESM/messaging only
- Performance-sensitive?: one hot path — the apt Upgrade hook, which is already C++ for exactly that reason (interpreter start + cache load per apt run would be visible)
- Cross-distro (knowledge-based): RHEL
subscription-manageris likewise Python (C rhsm library + D-Bus service); SUSE's suseconnect-ng is Go. Vendor entitlement clients tied to proprietary backends; not portable either way. - KDE prior art (knowledge-based): n/a — no KDE subscription client; Discover surfaces ESM state only indirectly
- Candidate Runix APIs: the read-only reporting surface via
pro apiJSON (security status, CVE/USN listing, ESM counts, reboot-required); the versioned JSON-envelope pattern itself is a good model for Runix's machine-readable interface - Replacement difficulty: high / effectively blocked for the core — the contract protocol is undocumented, Canonical-proprietary, server-versioned; reimplementing is a compatibility and legal risk. Realistic strategy: wrap, not replace — consume
pro apiendpoints, reimplement only presentation/inventory. - Replacement value: low-to-moderate, concentrated in read-only reporting; the entitlement machinery is pure liability to fork
- Technically bounded?: no for the package (unbounded external protocol); yes for the scoped subset (consuming
pro apiv1) - Evidence: entry-point shebang; uaclient file/line counts;
defaults.py:53contract URL;uaclient.conf;file+ldd apt-esm-json-hook;20apt-esm-hook.conf; unit files + timer intervals; root gate atcli_util.py:88;pro status,pro api u.pro.version.v1(2026-08-07)
- Program: apport — core-dump handler
/usr/share/apport/apport, CLI/GTK frontends, uploaderwhoopsie-upload-all, and ~12 hook scripts (package_hook, kernel_oops, recoverable_problem, unkillable_shutdown, ...) - Ubuntu package: apport 2.28.2-0ubuntu0.1 (noble-updates/main, source apport); companions python3-apport, python3-problem-report, apport-core-dump-handler, apport-gtk. The uploader daemon whoopsie is C (separate source).
- Installed/active: installed;
apport.serviceenabled+active (a oneshot latch — it installs the core_pattern hook and stays "active (exited)"); whoopsie.path enabled (inotify on /var/crash); autoreport off; /var/crash empty - Category: crash reporting
- Implementation: Python 3 throughout (only
apport-bugandis-enabledare shell); the ~44 KB handler plus python3-apport pulling python3-apt, launchpadlib, requests, yaml - Daemon vs command: kernel-invoked one-shot — the kernel forks a fresh Python interpreter per crash; frontends are interactive commands; whoopsie (C) is the only long-running piece
- Python dependencies (direct): stdlib-heavy in the handler (fcntl, grp, pwd, socket, struct) +
apport.report,problem_report; the report/crashdb layer adds apt/launchpadlib/requests/yaml - Native APIs underneath:
core_patternpipe — verified live:|/usr/share/apport/apport -p%p -s%s ... %E, withsuid_dumpable=2andcore_pipe_limit=10set bystart_apport(); direct pipe, not via systemd-coredump (that alternate route exists asapport-coredump-hook@.service, dormant here). Reads/proc/meminfo,/proc/<pid>/{stat,exe,cwd,environ}. - IPC/API used: kernel pipe on stdin (the core itself); AF_UNIX
/run/apport.socketwithSO_PASSCRED/SCM_CREDENTIALS+ fd passing for container crash forwarding; filesystem queue in /var/crash (.crash/.upload/.uploadedmarkers, whoopsie path-activated); HTTPS to daisy.ubuntu.com (whoopsie/libcurl) and Launchpad (launchpadlib) - Privilege model: root via core_pattern, unconfined on the active path — no AppArmor profile for apport itself, no seccomp; explicit
setresuid/setresgiddrops to the crashing user's ids at three sites; handles setuid targets deliberately; /var/crash isdrwxrwsrwt root:whoopsie. The sandboxed systemd-coredump variant unit (NoNewPrivileges, ProtectSystem=strict, PrivateNetwork, MemoryDenyWriteExecute, SystemCallFilter) exists but does not apply to the direct-pipe route in use here. - Boot-critical?: no — masking it costs only crash reports
- Performance-sensitive?: yes, at crash time under memory pressure — full CPython start + heavy imports exactly when the system is least healthy. The code is built around this:
core_size_limit = usable_ram() * 3/4abort, zstd streaming, core_pipe_limit=10 anti-fork-bomb, Nice=9/OOMScoreAdjust=500 on the sandboxed variant. - Cross-distro (knowledge-based): systemd-coredump (C) is the mainstream answer (journal storage, coredumpctl, no upload layer); Fedora's ABRT (C daemon + Python plugins) is the closest structural analogue, itself being displaced by systemd-coredump
- KDE prior art (knowledge-based): DrKonqi (C++/Qt) — migrated to consuming systemd-coredump (drkonqi-coredump-processor units), drives gdb, files to bugs.kde.org. apport's cron cleanup even special-cases
.drkonqimarkers; the two coexist on Kubuntu. - Candidate Runix APIs: core_pattern/core_pipe_limit/suid_dumpable sysctl management; bounded streaming pipe reads; /proc introspection; credential-passing AF_UNIX sockets; setresuid privilege drop; spool-directory management; package attribution of a binary (dpkg lookup)
- Replacement difficulty: high — setuid handling, container forwarding, credential verification, privilege-drop ordering, and crash-storm limiting are each subtle and each a security boundary; plus hundreds of package hooks and a report format whoopsie/Error Tracker consume
- Replacement value: low-to-moderate — the honest fix is adopting systemd-coredump for capture (where the ecosystem already went), not rewriting apport
- Technically bounded?: no — crashdb/Launchpad integration, the hook ecosystem, and the upload protocol are open-ended. The narrow slice (accept core, bound size, spool) is cleanly scoped.
- Phase 4 fit: excluded — root pipeline parsing untrusted crash data is an explicit PLAN.md security-boundary exclusion, and apport's CVE history (privilege escalation via core handling, /var/crash races, container forwarding) is exactly in that area
- Evidence: live
cat /proc/sys/kernel/core_patternandcore_pipe_limit;dpkg -sacross the suite; shebangs + imports;systemctl caton all 5 units + socket;find -perm /6000(none);ls -ld /var/crash; AppArmor absence checked in /etc/apparmor.d and loaded profiles; handler internals —drop_privileges()L209, setresuid L451/480,start_apport()L693-709,usable_ram()L357, size limit L1049, SCM_CREDENTIALS note L651;fileutils.pyL34/59/202;/etc/default/apport;/etc/cron.daily/apport(2026-08-07)
- Program:
cloud-init— first-boot instance provisioning (datasource discovery, network render, users/SSH keys,#cloud-configmodules) - Ubuntu package: cloud-init 26.1-0ubuntu1~24.04.1 (noble-updates/main, source cloud-init)
- Installed/active: installed; all four boot units enabled but the tool is disabled on this box —
/etc/cloud/cloud-init.disabled("Disabled by Ubuntu live installer after first boot"), instance dir symlinks toiid-datasource-none(bare metal, no datasource ever matched) - Category: cloud provisioning
- Implementation: Python 3 (~30 datasource modules, ~15 network renderers,
cc_*config modules). Notable non-Python piece:ds-identifyis POSIX shell — the fast datasource-detection pass avoids starting Python at all when no datasource exists. - Daemon vs command: four boot-stage oneshots (
cloud-init-local→cloud-init→cloud-config→cloud-final), ordered Before sysinit/sshd/network-online targets - Python dependencies (direct): yaml, jinja2, requests, jsonschema, jsonpatch, configobj, oauthlib (MAAS), serial (SmartOS), debconf; Recommends python3-apt, software-properties-common
- Native APIs underneath: netplan rendering then
netplan generate/apply; systemd unit ordering as the execution engine; IMDS HTTP to 169.254.169.254 across ~10 providers; growpart/gdisk; dhcpcd; udev; SSH host-key generation - IPC/API used: subprocess shell-out dominant (
systemctl,hostnamectl,udevadm,netplan,apt); HTTP to metadata endpoints; on-disk state under /var/lib/cloud as cross-stage IPC - Privilege model: root at boot, unconfined, executing user-supplied
runcmd/bootcmdfrom the datasource — a root-level remote-code-execution path by design; a major security boundary - Boot-critical?: YES — two stages block sysinit.target; on a cloud instance failure means no network, no SSH keys, no recovery
- Performance-sensitive?: yes — directly on boot latency; upstream mitigates with the shell ds-identify fast path
- Cross-distro (knowledge-based): cloud-init is itself the cross-distro standard (BSD renderers in this very tree). The non-Python alternatives already exist: Ignition (Go) and Afterburn (Rust) on Fedora CoreOS.
- KDE prior art (knowledge-based): n/a
- Candidate Runix APIs: none directly — PLAN.md explicitly excludes cloud-init from Phase 4 (fails not-boot-critical, bounded, and Runix-API-backed criteria). Its constituent surfaces (network config, unit control, user/key management) are worth exposing as Runix building blocks for other tools.
- Replacement difficulty: very high — ~30 provider protocols, ~15 renderers across 5 OS families, a de facto public config format where bug-compatibility is the bar
- Replacement value: low relative to cost — boot-latency win already captured by ds-identify; on this machine the local benefit is zero
- Technically bounded?: no — scope defined by an open-ended set of third-party providers
- Evidence: entry-point shebang (setuptools shim, cloud-init==26.1);
dpkg -s;systemctl caton all four units (Before/After chains);file ds-identify(POSIX shell);/var/lib/cloud/instancesymlink;cloud-init status→ disabled;/etc/cloud/cloud-init.disabled; datasource_list in90_dpkg.cfg; IMDS grep across sources/ (2026-08-07)
- Program:
check-language-support(CLI) +ls-dbus-backend(root D-Bus backend); GTK frontend in language-selector-gnome - Ubuntu package: language-selector-common 0.225 (noble/main, section admin, source language-selector)
- Installed/active: installed; backend not resident — D-Bus-activated on demand (
User=root) - Category: desktop administration
- Implementation: Python 3 throughout; library
language_support_pkgs.py+LanguageSelector/package - Daemon vs command: both — one-shot CLI + short-lived root D-Bus-activated service on a GLib loop
- Python dependencies (direct):
dbus+ mainloop + service,gi.repository.GObject,apt(apt.Cache()). Depends: python3-apt, python3-dbus, iso-codes, accountsservice. - Native APIs underneath: libapt (language-pack resolution), GLib loop, iso-codes data, accountsservice for per-user language
- IPC/API used: system bus
com.ubuntu.LanguageSelector, methods incl.SetSystemDefaultLanguageEnv(writes/etc/default/locale); polkit actioncom.ubuntu.languageselector.setsystemdefaultlanguage(auth_admin/auth_admin_keep) - Privilege model: root backend; each privileged method calls
_authWithPolicyKit()→CheckAuthorization(system-bus-name subject, AllowUserInteraction) before touching /etc. CLI is unprivileged read-only. - Boot-critical?: no (the
/etc/default/localeit writes is boot-read; the tool never runs at boot) - Performance-sensitive?: no — dominated by one-off
apt.Cache()construction - Cross-distro (knowledge-based): Ubuntu-specific language-pack model; Fedora langpacks dnf plugin; openSUSE YaST; Arch none
- KDE prior art (knowledge-based): Plasma's Region & Language KCM (C++/Qt/KConfig) covers the locale-write half; on Kubuntu it shells to this same language-pack machinery for the apt half
- Candidate Runix APIs: locale get/set with atomic
/etc/default/localewrite + validation; language-support completeness query over libapt; polkit check helper; D-Bus service host with sender authorization - Replacement difficulty: low-to-moderate — the backend is ~100 lines of D-Bus + polkit + config write; the moderate part is the apt dependency-traversal parity in
language_support_pkgs.py - Replacement value: moderate — removes a root Python D-Bus service; apt-cache load is the visible slowness in the language UI; Ubuntu-only and rarely invoked
- Technically bounded?: yes — two D-Bus methods, one polkit action, one config file, one well-defined query
- Evidence: shebangs + import greps on both entry points;
language_support_pkgs.py(apt.Cache());dpkg -s; D-Bus activation + policy files; polkit policy;_authWithPolicyKitat ls-dbus-backend:27-55 (2026-08-07)
- Program:
usb-creator-helper(root D-Bus backend) +usb-creator-gtk("Startup Disk Creator") - Ubuntu package: usb-creator-common 0.3.17 (noble/main, section admin, source usb-creator); -gtk also installed
- Installed/active: installed; helper D-Bus-activated on demand (
User=root), exits via its ownShutdownmethod - Category: desktop administration, hardware (block devices)
- Implementation: Python 3;
usbcreator/support package - Daemon vs command: D-Bus-activated root service on a GLib loop, started per operation
- Python dependencies (direct):
dbus,gi.repository.{GObject,GLib,UDisks}. Depends: python3-dbus, gir1.2-udisks-2.0, udisks2, xorriso, python3-debian. - Native APIs underneath: UDisks2 — confirmed and load-bearing:
UDisks.Client.new_sync(), object-manager enumeration of/org/freedesktop/UDisks2/block_devices/,MountPointscached properties,call_unmount_sync()before writing,check_system_internal()refusing internal disks. The actual bytes go through an in-process Python_builtin_dd— UDisks2 owns discovery/safety/unmount, not the copy. - IPC/API used: system bus
com.ubuntu.USBCreator— methodsImage(source, target, allow_system_internal),KVMTest,Shutdown; signalProgress(u). Outbound to UDisks2 and PolicyKit1. Polkit actionscom.ubuntu.usbcreator.imageand.kvm— bothallow_any=no,allow_active=auth_admin_keep(stricter than language-selector). - Privilege model: root via D-Bus activation; every method starts with
check_polkit(sender, conn, action)resolving the caller and callingCheckAuthorization(code annotated "Taken from Jockey 0.5.3"). Safety layered on top: internal-disk refusal + unmount-all before write. - Boot-critical?: no
- Performance-sensitive?: yes, moderately — streams a 1-6 GB ISO through a Python copy loop while emitting Progress on the same main loop; USB is usually the bottleneck but the loop is Python
- Cross-distro (knowledge-based): problem is universal, tools are per-distro — Fedora Media Writer (C++/Qt), GNOME Disks restore (C, UDisks2), balenaEtcher (Electron). The UDisks2 dependency is fully cross-distro; only the D-Bus/polkit surface is Ubuntu's.
- KDE prior art (knowledge-based): strong — KDE ISO Image Writer (C++/KAuth helper) is the direct analogue, and Solid wraps the same UDisks2 interfaces for removable-device discovery. A proven design to copy: Solid/UDisks2 discovery + polkit authorization + native write loop.
- Candidate Runix APIs: UDisks2-backed block enumeration + removable-vs-internal classification + unmount-all + safe write handle; ISO inspection; streaming progress events; polkit action check
- Replacement difficulty: moderate — the pieces are well-trodden; the cost is correctness under adversarial conditions (enumeration races, the internal-disk check whose failure destroys a system disk, sync/flush before reporting done)
- Replacement value: high — removes a root Python service writing raw bytes to block devices, replaces a Python copy loop on a multi-GB path with native I/O, and lands on the UDisks2 abstraction Runix needs anyway for storage features
- Technically bounded?: yes — three methods, two polkit actions, one native dependency, one data flow
- Evidence: shebang; import grep (line 18
UDisks); UDisks call sites at 38-84 and 155/174-180;Image()body 168-183 (check_polkit → check_system_internal → unmount_all → _builtin_dd);check_polkitat 196-228;dpkg -s usb-creator-common; D-Bus service (User=root) + polkit policy files; UDisks-2.0.typelib present (2026-08-07)
Verified on this machine (2026-08-07):
- Service management:
systemctlis a C ELF binary; systemd's entire toolchain is C. No Python anywhere in the service-management path. - Users/groups:
adduseris Perl (#!/usr/bin/perl);accounts-daemonis a C ELF. useradd/usermod are C (shadow-utils). - Hardware core: udev/udevadm are C; the Python presence in hardware is limited to client CLIs for C daemons (switcherooctl, powerprofilesctl — table-only rows).
- Installer: not installed on this machine (no subiquity/ubiquity), out of scope per the installed-only rubric. Known from general knowledge (labeled as such): subiquity, the Ubuntu server installer, is Python — and an explicit PLAN.md Phase 4 exclusion anyway.
- Diagnostics: the 105 bpfcc-tools scripts are Python-over-BCC; native successors (bpftrace, libbpf-tools) already exist upstream — replacement is an upstream adoption question, not a Runix rewrite target.
Knowledge-based (no network access during survey); Ubuntu column verified locally.
| Capability | Ubuntu (Python) | Debian | Fedora/RHEL | NixOS | Arch | Native impl exists? |
|---|---|---|---|---|---|---|
| Repo administration | add-apt-repository | same package | dnf5 config-manager (C++) | declarative config | manual pacman.conf | yes (dnf5) |
| Privileged package D-Bus service | aptdaemon (vestigial) | same / PackageKit | PackageKit (C) | n/a | PackageKit (rare) | yes (PackageKit) |
| command-not-found | command-not-found | same | PackageKit-cnf (C) | programs.sqlite hook | pkgfile (C) | yes |
| Security/update status | ubuntu-security-status | debsecan (Py) | dnf updateinfo | vulnix | arch-audit (Rust) | partial |
| Automatic updates | unattended-upgrades | same | dnf-automatic (Py) | system.autoUpgrade | none standard | no |
| GUI updates | update-manager | GNOME Software | GNOME Software (C) via PackageKit | n/a | Discover (C++) | yes |
| Release upgrade | do-release-upgrade | manual apt | dnf system-upgrade (Py) | nixos-rebuild | rolling | partial (zypper dup C++) |
| Crash handling | apport | same available | systemd-coredump (C) / ABRT | systemd-coredump | systemd-coredump | yes |
| Driver management | ubuntu-drivers | isenkram | none (third-party repos) | declarative | none | no |
| Entitlement client | pro | n/a | subscription-manager (Py) | n/a | n/a | no |
| Instance provisioning | cloud-init | same | cloud-init / Ignition (Go) | declarative | cloud-init | yes (Ignition/Afterburn) |
| Network config layer | netplan CLI | available, non-default | networkd/NM direct | declarative | networkd/NM direct | the C core ships in Ubuntu's own archive |
| Firewall frontend | ufw | same | firewalld (Py) | declarative nft | raw nft (C) | partial (nft) |
| networkd dispatcher | networkd-dispatcher | same | same (Py); NM-dispatcher (C) | same | same | yes, for NM only |
| USB image writer | usb-creator | n/a | Fedora Media Writer (C++) | n/a | n/a | yes |
| Language packs | language-selector | n/a | dnf langpacks (Py) | declarative | n/a | no |
Explicitly provisional. This scores today's evidence; the actual pick happens at the Phase 4 gate, after Phase 1-3 APIs exist. Scoring: PLAN.md's five criteria, 0-2 each.
| Candidate | Clearly admin | Mostly Python | Not boot-critical | Bounded | Meaningful | Total |
|---|---|---|---|---|---|---|
| netplan CLI | 2 | 2 | 2 (Python never on boot path) | 2 | 2 | 10 |
| ubuntu-security-status | 2 | 2 | 2 | 2 | 1 | 9 |
| add-apt-repository | 2 | 2 | 2 | 1 (PPA/key fetch edge) | 2 | 9 |
| networkd-dispatcher | 2 | 2 | 2 | 2 | 1 (inert on NM boxes) | 9 |
| command-not-found | 1 | 2 | 2 | 2 | 1 | 8 |
| usb-creator | 1 | 2 | 2 | 2 | 1 | 8 |
| ubuntu-drivers (query layer) | 2 | 2 | 2 | 1 | 1 | 8 |
Excluded classes (fail PLAN.md's "do not begin with" list regardless of score):
unattended-upgrades (unattended security mutation + shutdown-inhibitor hazard), apport
(root parsing of untrusted crash data; CVE history), cloud-init (boot-critical, explicit
exclusion), do-release-upgrade (bricks machines; runtime-downloaded code), ubuntu-pro-client
(proprietary contract protocol — wrap pro api, don't replace), update-manager (GUI over
a vestigial backend), aptdaemon (vestigial; let it die).
Recommendation (provisional, two-track):
- Introspection track — ubuntu-security-status. Read-only over libapt origins, fully unprivileged, output admins actually read, and it doubles as the Phase 1 forcing function for the apt query API. Lowest risk, immediate value.
- Substitution track — netplan CLI. The strongest architectural fit found in this
survey: Canonical already split the C core (libnetplan1 + boot generator) from the
Python CLI as separate packages, so Runix replaces exactly one package and inherits
the parser, semantics, and boot safety untouched. An R binding to libnetplan1's 97-symbol
C ABI is native-first Runix by construction. Caveat to resolve at the gate:
rctl-side packaging must not drag r-base-core intopriority: importantterritory uninvited — ship as a coexisting alternative binary, as PLAN.md's Phase 4 already requires.
Codex-review caveats, confirmed by the records: software-properties is broader than it looks (PackageKit dependency, GTK frontend, D-Bus backend — scope any replacement to the CLI + deb822 path first), and command-not-found, while the most bounded target, exercises almost none of the privileged-administration architecture, so it proves little. networkd-dispatcher scores well but, per its record, is a native-daemon shape: it would prove the native-helper half of Runix with R as client only, not an R showcase.
Every sweep-A/B package not covered above, with exclusion class (full sweep output preserved in the survey scratchpad; counts in Appendix B):
- Python-library CLI stubs (the executable exists because a library ships a console script, not because Ubuntu administers anything with it): python3-serial, python3-numpy, python3-jsonpatch, python3-websocket, python3-unidiff, python3-speechd, python3-pystache, python3-pysmi, python3-pygments, python3-netaddr, python3-markdown-it, python3-mako, python3-keyring, python3-jsonschema, python3-json-pointer, python3-chardet, python3-babel, isympy-common
- Interpreter infrastructure: python3.12, python3-minimal (py3compile, pydoc stubs)
- Dev tooling: devscripts (8 scripts), gyp, patchutils, clang-18
- Third-party applications (not first-party administration): glances (universe;
its
/etc/systemd/system/glances.serviceis a local admin addition), duplicity, proton-vpn-gtk-app, piper/ratbagd (gaming-mouse config) - Desktop end-user applications: gnome-shell (2 hits), gnome-browser-connector, gnome-tweaks, gnome-terminal, gnome-menus, orca (screen reader), mesa-vulkan-drivers (shader tool script)
- Miscellaneous support scripts: rsync (rrsync restricted-shell helper), iproute2
(one Python script —
routel; trivia: even iproute2 ships a Python rewrite of one tool)
Borderline calls, documented: hplip kept as table-only (main component, root D-Bus
service com.hp.hplip, but HP-upstream printing rather than Ubuntu first-party admin);
ssh-import-id and ec2metadata table-only (bounded single-purpose provisioning helpers);
gnome-browser-connector excluded (browser integration, not administration).
- Sweep A: 190 Python-shebang executables in
/usr/bin+/usr/sbin(0 at/usr/libexecdepth ≤ 2) - Sweep B: those 190 map to 62 packages; 105/190 belong to
bpfcc-toolsalone - Sweep C: 18 installed packages depend on
python3-apt, nearly all first-party admin - Sweep D: 19 systemd service files resolve to Python
ExecStart(incl. apport, cloud-init, unattended-upgrades, networkd-dispatcher, ubuntu-pro timers, update-notifier-download); 5 system D-Bus activation services exec Python (aptd, SoftwareProperties, LanguageSelector, USBCreator, hplip) + 1 session (gnome-browser-connector) - Sweep E: 232 executable Python scripts across the 23 candidate packages (incl.
out-of-PATH:
/usr/lib/command-not-found,/usr/lib/update-notifier/apt-check,/usr/share/unattended-upgrades/unattended-upgrade-shutdown) - Unit/timer state at survey time: unattended-upgrades.service enabled+active, apt-daily{,-upgrade}.timer enabled+active, update-notifier-download.timer enabled+active, motd-news.timer enabled+active, apport.service enabled+active, ua-timer.timer enabled (inactive), networkd-dispatcher.service enabled (inactive), cloud-init.service enabled (inactive post-boot)