Skip to content

packaging: maintainer script templating and control metadata - #107

Open
Christopher Obbard (obbardc) wants to merge 9 commits into
mainfrom
wip/obbardc/packaging-hygiene
Open

Christopher Obbard (obbardc) wants to merge 9 commits into
mainfrom
wip/obbardc/packaging-hygiene

Conversation

@obbardc

Copy link
Copy Markdown
Contributor

Nine packaging changes with no CI dependencies, kept together because they all
concern how the binary packages are assembled and described.

The substantial one generates every maintainer script from a template, so
preinst/postinst/postrm stop drifting from each other, and runs the kernel
hooks through linux-run-hooks rather than open-coding them. Alongside that:
module debug files are linked into .build-id so debuginfod can find them, the
image package depends on an initramfs generator instead of merely recommending
one, the metapackages are pinned to the build they were made from, and
Standards-Version and the Vcs-* fields are brought up to date.

No CI or matrix changes. Independent of any other PRs; can be merged immediately.

The worked example of how KVER is composed added -qcom-next-20260826 to a
7.0.0-rc2 base and arrived at 7.2.0-qcom-next-20260826, so the base version
on the left and the release on the right disagreed: the LOCALVERSION suffix
is appended to the base version and nothing in 'prepare' rewrites it, which
is the one thing the example exists to show.

Say 7.2.0-rc7 on both sides. The arithmetic is the point here, not which
particular release is current, and -rc7 is the version the surrounding
examples already use.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
Only the postinst was a @kver@ template; the preinst and postrm were copied
in verbatim. That left the postrm reconstructing the kernel release at run
time, by stripping "linux-image-" and a trailing "-qcom" off
DPKG_MAINTSCRIPT_PACKAGE. The suffix is not part of the package name any more
-- the package is linux-image-<kernelrelease>, and the release carries its own
+qcom-next-<date>-g<sha> -- so the strip is a no-op at best, and at worst
chops a real part of the version off and runs the hooks for a kernel that
does not exist.

Rename all three to .in and substitute @kver@ into each, so every script
names its own kernel release the way the postinst already did. The scripts
now share one shape: header, set -e, KVER, #DEBHELPER#, body.

override_dh_installdeb generates them in one loop over the four maintainer
script names, taking whichever templates exist, so adding a prerm becomes a
matter of adding debian/linux-image.prerm.in. debian/clean and .gitignore
already cover all four generated names.

Which hook directories those scripts run is unchanged here, and wrong on
forky; the next commit fixes that.

Verified that the three generated scripts are shellcheck-clean with no @kver@
left in them, and that the postrm now names the same release the postinst
does.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
Kernel hooks used to live only in /etc/kernel/{postinst,postrm}.d, so that is
the one directory the maintainer scripts run. Since linux-base 4.11 a package
that ships its own hook installs it in /usr/share/kernel/*.d instead, leaving
/etc for the administrator, and systemd-boot's zz-systemd-boot made that move
in forky: on trixie it is a conffile in /etc/kernel/postinst.d, on forky
nothing is installed there at all.

The result is that installing this package on forky writes the kernel to /boot
and stops. No loader entry and no loader.conf are generated, so the new kernel
is invisible to sd-boot and the board comes back on the old one. The workaround
people have been using is to symlink the hooks into /etc before installing the
kernel package, which is the administrator's directory and not ours to
populate.

Dispatch through linux-run-hooks(1) instead, which is what Debian's own
linux-image packages call: it runs both directories in the documented order
and sets DEB_MAINT_PARAMS. The postrm gains the same treatment, so removing a
kernel drops its loader entry rather than leaving sd-boot offering a kernel
that is gone.

The command ships in linux-base, and nothing said it had to be there --
linux-base arrived unversioned, if at all, through the initramfs-tools or
dracut we merely recommend. Declare it the way Debian's linux-image packages
do, with Pre-Depends: linux-base (>= 4.12~). Pre- is the right strength for
the same reason it is right for them: the command runs from our postinst, so
it has to be configured before we are.

There is deliberately no run-parts fallback for the case where the command is
missing. Running /etc/kernel/*.d alone is not a degraded path, it is precisely
the silent unbootable install above, so a fallback could only reintroduce the
bug this commit exists to fix while hiding the missing dependency. The
postinst calls the command unguarded, as Debian's does.

The postrm is the exception and tests for it first. Pre-Depends holds while
the package is installed, not while it is being removed: dpkg may remove
linux-base in the same run, and on purge it can already be gone. Debian's
postrm guards it for the same reason and warns rather than failing -- a kernel
has to stay removable, and a skipped hook run there costs a stale bootloader
entry, not a system that will not boot. The warning says as much, on stderr.

The field is a plain line in control.in rather than something 'prepare'
decides per suite, because there is no suite to make an exception for: the
matrix builds trixie, forky and resolute, carrying 4.12.1, 4.17 and
4.15ubuntu5. Only noble in build-kernel.sh's list of accepted --distro values
predates 4.12, and nothing builds it. A build aimed there would produce a
package noble refuses to install, which is the better failure.

Note that linux-run-hooks arrived in linux-base 4.12, not the 4.11 that
release notes for the /usr/share move suggest: 4.12's changelog adds the
command, and Debian's kernel pre-depends on that same 4.12.

Verified on forky that linux-run-hooks runs both directories; over the
generated scripts, that postinst and postrm both dispatch with the version and
image path when the command is present, that the postrm takes the warning
branch and still exits 0 when it is absent, and that all three scripts are
shellcheck-clean; that 'prepare' emits the Pre-Depends field whatever DISTRO
it is given, the stanza parsing through Dpkg::Control::Info; and that
dpkg --compare-versions agrees 4.12.1 and 4.15ubuntu5 satisfy the clause while
4.11 and 4.5ubuntu9 do not.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The -dbg package ships each module's debug file only at the path that
mirrors where the module installs, /usr/lib/debug + /lib/modules/<ver>/
kernel/<path>. scripts/package/builddeb, which this package otherwise
follows, writes that path and also a /usr/lib/debug/.build-id/xx/yyy.debug
symlink to it. We were missing the second one.

It matters because the mirrored path only answers a lookup for a module
addressed as /lib/modules/...: /lib is a symlink to /usr/lib, so a tool
that reaches the module by its canonical name looks for
/usr/lib/debug/usr/lib/modules/..., which nothing in the package provides.
The build-id path does not depend on how the module was addressed at all,
and it is the one gdb and debuginfod consult first.

Extract the build ID with readelf -n in the same loop that extracts the
symbols, and link the two paths together relatively, so the link is
correct in the staging tree and stays correct once installed. A module
built without a build ID keeps its mirrored debug file and gets a warning
rather than a .build-id/.debug entry named after an empty ID.

bundle-dkms-modules.sh gets the same treatment for the modules it puts in
extra/, reading the ID from the module before Stage 3 strips it. It calls
the host readelf, as its DWARF assertion already does -- readelf parses ELF
directly and is not tied to a target. debian/rules resolves a cross readelf
alongside its cross objcopy instead, which is what builddeb does.

Verified by replaying the loop over a small ELF tree: the symlink lands at
.build-id/<first two hex>/<rest>.debug, resolves to the mirrored debug file
through a relative path that survives installation, and a module with
--build-id=none takes the warning branch and still ships its symbols. Not
verified with a debugger actually resolving a module by build ID.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The image carries a built copy of every module named in DKMS_MODULES,
installed in /lib/modules/<kver>/extra/ by bundle-dkms-modules.sh, but
nothing in the package says so. Anything that depends on kgsl-dkms to get
the kgsl module has to be told separately that this kernel already has it.

Emit a Provides entry per bundled module, generated in 'prepare' from the
same DKMS_LIST that already drives Build-Depends and the dkms-modules
manifest, so the three cannot drift. With DKMS_MODULES=kgsl,camx the image
package now provides kgsl-dkms and camx-dkms alongside linux-image and
linux-image-arm64; with the list empty the field is exactly what control.in
spells, as with Build-Depends.

The entries are unversioned. A dkms source's PACKAGE_VERSION is the module
version, not the version of the Debian package that shipped it, so a
version clause here would claim something we cannot check -- and an
unversioned Provides deliberately does not satisfy a versioned dependency,
which is the right answer when the caller wants a specific dkms release.

This says "the module is here", not "the dkms source is here": the kernel
provides a module built for one kernel release, and cannot build one for
any other. Installing the real -dkms package alongside is still possible
after this commit, and still builds a second copy into updates/dkms/ that
shadows ours. Refusing that outright -- a Breaks against the -dkms packages
we provide -- is a separate decision, and not one this commit makes.

Verified by running prepare over a stub kernel tree: DKMS_MODULES=kgsl,camx
produces "Provides: linux-image, linux-image-arm64, kgsl-dkms, camx-dkms"
and an empty list leaves the line untouched, both parsing clean through
Dpkg::Control::Info.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The image package recommended initramfs-tools | dracut |
linux-initramfs-tool. Debian's own linux-image packages depend on that same
set, and they are right to: the postinst runs the kernel hooks, but the hook
that builds the initramfs belongs to the tool, so with none of them
installed the install succeeds, runs no such hook, and leaves a kernel with
no initrd. On these boards that is not a degraded install, it is one that
does not boot.

A Recommends is enough only until someone builds an image with
--no-install-recommends, which image recipes routinely do. Promote it to
Depends and drop the now-empty Recommends field.

The alternatives keep the order they had, initramfs-tools first. Debian's
forky kernel lists dracut first, but which generator a board should prefer
is an image decision and not one to change in passing here.

Verified that prepare still generates a control file Dpkg::Control::Info
parses, with the alternatives in Depends and no Recommends field left.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
Both metapackages depended on their versioned package by name alone:
linux-image-qcom-next on linux-image-<kver>, linux-headers-qcom-next on
linux-headers-<kver>. The name carries the kernel release, so that much is
pinned, but not the Debian version -- an earlier build of the same release,
a different revision or an earlier respin, satisfies the dependency just as
well as the one the metapackage was built beside.

Add (= ${binary:Version}), which is what Debian's linux-image-amd64 does
with its image package and what our own -dbg stanza already does. All the
binaries take the same version from dh_gencontrol -v, so the clause resolves
to the build the metapackage came from.

Verified over the generated control: both metapackages now carry the clause
and the -dbg package is unchanged, all parsing through Dpkg::Control::Info.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
Standards-Version said 4.6.2, which is several policy releases behind;
lintian reports it as out-of-date. Say 4.7.4, the current release
(4.7.4.1 is an editorial revision of it, and the three-component form is
the convention).

This is a claim of compliance, so it is worth saying what backs it: nothing
in the packaging changed for it, and the upgrading checklist between the two
was not readable from this machine. The fields policy governs here are the
ordinary ones -- a kernel image, its headers, their metapackages -- and
lintian over a built package is what will confirm or deny it.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
The source stanza named a Homepage but not where the packaging itself is
maintained, so a built package gave no way back to the tree that produced
it. The changelog records the kernel repository, ref and commit; this is the
other half, the repository this debian/ directory lives in.

Vcs-Git carries the .git clone URL and Vcs-Browser the plain one, both over
HTTPS -- the same repository the checkout's origin points at, spelled the
way a stranger can clone it without an account.

Signed-off-by: Christopher Obbard <chris.obbard@oss.qualcomm.com>
@bjordiscollaku

Copy link
Copy Markdown
Contributor

The linux-base 4.12 pre-depends and the linux-run-hooks calls are the same as what Debian does, and they fix zz-systemd-boot getting skipped now that it lives under /usr/share/kernel/postinst.d on forky and resolute. The metapackage pin and the .build-id links look good too.

A few things I noticed. Standards-Version 4.7.4 includes the 4.7.1 rule about not installing into /lib, but the modules still go to /lib/modules. I opened #119 to move them to /usr/lib/modules once #114 is in, since that reworks the bundler anyway, so I'm fine keeping the bump here as long as that's tracked. Related and minor, 4.7.3 says Priority: optional shouldn't be set on the source since it's the default, so that one and the one on the dbg package can just go.

Debian's image scripts also run hooks in preinst and prerm, with prerm calling linux-check-removal before the prerm hooks. Here preinst doesn't do anything and there's no prerm template, so prerm.d never runs, including the dkms hook in /etc/kernel/prerm.d. Worth matching that?

What's the Provides for the dkms packages meant to cover? I'm guessing it's to stop apt pulling the dkms package and a toolchain onto targets that already have the module. #114 drops it again though, and the per-module packages there also carry the modprobe.d bits the bundled module doesn't have here, so I'm wondering if it needs to be in this one at all.

Last small one, build-kernel.sh still lists noble, which can't install these anymore since its linux-base doesn't have linux-run-hooks.

@obbardc

Copy link
Copy Markdown
Contributor Author

I split the immediatly required changes which fix forky into #121 so we can come back to this PR after.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants