SmartConfig

M5 plan: the package

Status: built and signed off; M5 is done (tag m5 on 8f91158, 2026-10-09). The plan itself is not approved: it was drafted on 2026-10-08 for your read, and your call (2026-10-07) was that M5 starts after the M3/M4 follow-ups whether or not you have read it. Where a choice is yours, the plan went on with the one marked (recommended) and said so in the worklog, so it can still be turned round. Section 9 lists where the build differs from the plan.

0. Summary

M5 turns the hand install of the README into one .deb, smartconfig, that installs, upgrades, removes and purges cleanly on Ubuntu 24.04, and proves that on a clean VM in the QEMU lab (both firmware modes) and on this VM, replacing the hand install.

What goes in it (today’s hand install, moved to packaged paths):

File Today (by hand) In the package
sc /usr/local/sbin/sc /usr/sbin/sc
scd.service, sc-boot-seen.service, sc-boot-ok.service /etc/systemd/system/ /usr/lib/systemd/system/
the rescue and emergency drop-in /etc/systemd/system/{rescue,emergency}.service.d/50-smartconfig.conf /usr/lib/systemd/system/{rescue,emergency}.service.d/50-smartconfig.conf
42_smartconfig /etc/grub.d/42_smartconfig the same (a conffile)
the store /var/lib/smartconfig (scd makes it) the same; kept on remove, asked about on purge (Q3)
docs the repo /usr/share/doc/smartconfig/ (README, copyright, changelog)

/usr/sbin, not /usr/local/sbin: a package never writes /usr/local (Debian policy 9.1.2), and the rescue boot mounts only /, where /usr is (merged-usr on 24.04); /usr/local on a disk of its own was a README caveat that goes away.

1. Goals and non-goals

Goals:

  1. make deb builds dist/smartconfig_<version>_amd64.deb from a clean tree, reproducibly (the same bytes twice: SOURCE_DATE_EPOCH from the commit, sorted members, root:root), with the static sc the lab tests.
  2. Install: files in place, daemon-reload, the three units enabled (deb-systemd-helper, as dh_installsystemd would), scd started, update-grub run, the grub.cfg checked; the first verdict comes at the next boot, as today.
  3. Upgrade (a newer .deb over an older one): scd restarted once, after the new sc is in place (scd is Type=notify: an old sc under a new unit never says ready); the store and the boots file untouched.
  4. Remove: units stopped and disabled, 42_smartconfig gone from grub.cfg (update-grub), the menu flag cleared, the store kept. Purge: as Q3 says.
  5. From the hand install (this VM, and anyone who followed the README): the package takes over; hand-installed copies in /etc/systemd/system and /usr/local/sbin/sc that match a released build are moved aside, others are left and named (Q4).
  6. The lab installs the .deb on its clean image instead of install.sh (a new --deb mode of lab/e2e.py, both firmware modes), runs the M4 scenario unchanged, then upgrade, remove and purge checks.
  7. CI builds the .deb and checks its contents and control file.

Non-goals: an apt repository or PPA (M5 ships the file), signing, other architectures than amd64 (arm64 later: sc builds for it), other distributions than Ubuntu 24.04, a Debian source package.

2. How it is built (Q1)

(recommended) dpkg-deb --root-owner-group --build on a staged tree, from a make deb target and a small script (scripts/build-deb.sh): dpkg-deb is on every Ubuntu machine and CI runner, so nothing new is added. The maintainer scripts are plain sh, written out (no debhelper).

The milestone named nfpm: one YAML file instead of the staging script, but a new tool (a Go module tree, pinned and fetched with go run github.com/goreleaser/nfpm/v2/cmd/nfpm@vX). The project’s rule is no new dependency without asking: that is Q1.

Version: 0.5.0 at the tag m5; between tags 0.4.99+git<count since m4>.<UTC time>.<sha7> so a later build always sorts higher (C3). sc version prints the same.

3. The maintainer scripts

4. Paths that change

The units’ ExecStart=, ConditionFileIsExecutable=, RequiresMountsFor= and the drop-in’s ExecStartPre= name /usr/sbin/sc; the README’s hand install goes (it points to the package); lab/guest/install.sh stays for the hand-install mode of the lab until the .deb mode has passed, then goes; accept-m4.sh reads the path from the unit. The unit tests’ expectations change with them.

5. Order of implementation

One step at a time, each with its tests, the full checks, a commit, a worklog line, push and CI green.

  1. sc version and the version string (from the build: -ldflags -X).
  2. The packaged paths: units, drop-in, 42_smartconfig’s comments, the unit tests (/usr/sbin/sc).
  3. scripts/build-deb.sh and make deb: the staged tree, the control file, md5sums, conffiles, copyright, changelog; reproducible.
  4. The maintainer scripts, with a Go test that runs them against a fake root and stub tools (as TestLabGuestFacts does for facts.sh).
  5. CI: make deb, dpkg-deb --info/--contents checked against a list, the .deb as an artifact.
  6. The lab’s --deb mode: install with apt install ./x.deb, the M4 scenario, then upgrade (a second build with a higher version), remove, purge (checks D.x).
  7. The hand-install migration (Q4), in the lab too (a guest with the hand install first).
  8. README and docs; a chunk review (two reviewers).
  9. Sign-off: make lab-e2e LAB_E2E_ARGS=--deb both modes; on this VM: install the .deb over the hand install, the post-install checks, a reboot and its verdict; then remove and install again.

6. Acceptance

7. Risks

8. Questions for you

  1. Build tool: dpkg-deb, no new dependency (recommended), or nfpm, as the milestone line says (a new pinned tool)?
  2. Install path: /usr/sbin/sc (recommended: policy, and / is all the rescue boot mounts) or keep /usr/local/sbin/sc?
  3. Purge: keep /var/lib/smartconfig (recommended: the history is the point of the tool; the purge says where it is and how to delete it), or delete it?
  4. The hand install: the package moves aside the copies it recognises (recommended), or leaves all of them and only warns?
  5. Package name smartconfig (recommended) or sc?
  6. The license. The repo has no LICENSE file, and a package’s copyright file names one. Until you choose, it says “No license has been chosen yet: all rights reserved”. Which license (for example MIT, Apache-2.0 or GPL-3.0)?
  7. The command’s name (from the review, A11). Ubuntu’s universe has a package sc, a spreadsheet, whose /usr/bin/sc the package’s /usr/sbin/sc hides for anyone with both installed (/usr/sbin comes first in Ubuntu’s PATH), and Debian policy 10.1 asks for other names. Keep sc and say so in the README (recommended for now: the project, its docs and the rescue report all say sc; nothing is published to an archive), add Conflicts: sc, or rename the command before a wider release?

9. Changes while it is built

The questions run on their recommended answers until you say otherwise; the plan above is as drafted, and these are where the build differs.

# Date Change Why Commit
C1 2026-10-08 postinst restarts scd on every configure, a first install too (section 3 said start); it runs no grub-script-check (goal 2, section 3): grub-mkconfig checks its output before it replaces grub.cfg. A hand install’s scd may still run its old sc, and start leaves a running unit as it is. e710ce2
C2 2026-10-08 The hand install is recognised by SmartConfig’s marks in each file (the repo’s URL in a unit, sc status --console in a drop-in, the header line of 42_smartconfig, the store’s Go package path in sc), not by matching a released build (goal 5). Every hand install since M2 carries them, and a build of any commit is SmartConfig’s. e710ce2
C3 2026-10-08 From the review: the takeover runs on a first install only (dpkg passes no version); preinst moves only 42_smartconfig (its conffile prompt), postinst’s first configure the rest, and postrm abort-install puts 42_smartconfig back. The version is 0.N.99+git<count since mN>.<time>.<sha7> (section 2 said <UTC date>.<sha7>), and no mN tag is an error. reviews/2026-10-08-m5.md: a remove and install again lost the rescue entry; a failed install left the hand install moved aside; a commit could sort below its parent. 5e9f1e3, 8284709
C4 2026-10-08 CI prints the package’s control and contents (step 5 said “checked against a list”); TestBuildDeb holds the contents to the list, with a stand-in sc. One list, in a test that runs everywhere. 922e5cc
C5 2026-10-08 The lab installs with dpkg -i (step 6 said apt install): the guest has the dependencies, and both run the same maintainer scripts; apt install is the README’s and this VM’s sign-off’s. lab/guest/install.sh stays, for both modes (section 4 said it goes). The lab also checks the takeover of the m4 tag’s hand install (D.4) and a remove and install again (D.5). The files mode is still the M4 scenario’s own run. 7c4e466, 4df3912, 99d91ba
C6 2026-10-08 With DPKG_ROOT set (dpkg’s --force-script-chrootless), the scripts keep to that root for their own files, but daemon-reload, the restart and update-grub act on the running system when their guards find run/systemd/system or boot/grub/grub.cfg under it. No tool takes a root for them, and the tests need DPKG_ROOT for the files; such installs are rare and need the force flag. 84f9959 (the review’s A6)
C7 2026-10-08 From the sign-off on the dev VM: postinst says when scd was not restarted (its MainPID unchanged, or none while enabled), and prerm when scd still runs after the stop, with the command; /usr/sbin/policy-rc.d is honoured, as every package does. This VM’s image has a policy-rc.d that forbids everything, and the hand install’s scd ran on its moved-aside sc, silently. 8bd34c3
C8 2026-10-08 From the sign-off on the dev VM: sc status does not call a deleted unit or drop-in of /etc/systemd/system an error when systemd reads one of the same name from a later unit directory (deleted; /usr/lib's in use); a mask is no stand-in. After the takeover it exited 2 on the hand install’s files and offered sc restore, which would have hidden the package’s units again. d81e2d5