Skip to content

AI-assisted fork master: build reproducibility and build-step fixes (removes some upstream CI -- see description) - #7

Open
assisted-by-ai wants to merge 2272 commits into
Kicksecure:masterfrom
org-ai-assisted:master
Open

AI-assisted fork master: build reproducibility and build-step fixes (removes some upstream CI -- see description)#7
assisted-by-ai wants to merge 2272 commits into
Kicksecure:masterfrom
org-ai-assisted:master

Conversation

@assisted-by-ai

Copy link
Copy Markdown

This is the accumulated work on the org-ai-assisted fork's master, offered
upstream. It is AI-assisted work, reviewed by a human before this PR was
opened. Please read the removals below before the diff.

Read this first: what this PR REMOVES from upstream

Four removals are upstream-affecting and were made for the fork's needs, not
yours. They are called out here so they are not discovered inside a 100-file
diff. I am happy to restore any or all of them on request:

  1. .github/workflows/local-test-build.yml -- the build: job is deleted.
    That is the automated builder (ansible, ANSIBLE_VAULT_PASSWORD, artifact
    handling, 240 minute timeout), gated
    if: github.repository == 'Whonix/derivative-maker'. The fork cannot run it
    (no secret, and the gate never matches), so it was dropped; upstream can.
    The rest of the file is retained and rescoped to the dry-run gate.
  2. ci/build deleted (229 lines).
  3. ci/github-build deleted (207 lines).
  4. ci/dry-run.d/400_reproducible-buildinfo deleted (62 lines).

The fork's lanes drive help-steps/dm-build-official directly instead of going
through ci/build, which is why those entry points went away here. If upstream
still uses them, they should be restored before merging.

What it adds

228 commits, 100 files, +5126 -2619. By area:

  • help-steps/ (35 files) -- variables, pre, parse-cmd, unmount-tree
    and the mount/unmount helpers.
  • build-steps.d/ (19 files) -- notably 1100_sanity-tests,
    1200_prepare-build-machine, 2100_create-debian-packages,
    3500_install-packages, 3600_convert-raw-to-iso, and a new
    4350_reimage-raw-reproducible.
  • docker/ (8 files) -- the run/setup scripts, including pinning apt to the
    build's frozen snapshot so a container rebuild does not silently change the
    package set.
  • ci/ (15 files) and .github/workflows/ (7 files) -- see the caveat below.

A large share of the work targets build reproducibility: normalising the EFI
FAT ESP, update-alternatives symlink mtimes, debconf owner spellings, and ucf
temp paths, plus a reproducible re-image step.

Caveat: the fork's CI lanes are included

The local-*.yml workflows are the fork's own lanes. Five of the six are gated
if: vars.CI_ENABLED_ORG_AI_ASSISTED == 'true', so in a repository that never
sets that variable they resolve false and consume no runner. local-test-build.yml
is not gated that way, but it is pre-existing upstream and this PR only rescopes
it (and fixes its tag filter: '*' does not match a slash, so a slash-delimited
tag silently skipped the gate; it is '**' now).

They are included because this PR is the fork's master as-is. Say the word and I
will strip them.

Submodules

No submodule gitlink is touched. Verified: merging this branch changes zero
mode-160000 entries, and the merge is clean in a clone with no submodules
checked out, which is the case that otherwise conflicts.

Testing status, honestly

  • Verified: the reproducibility work reached bit-for-bit identical output
    locally versus CI on the fork's lanes.
  • Verified: the merge itself is clean against current upstream master
    (11269ee1), with no gitlink changes and no conflicts.
  • NOT verified: this exact tree has not been built or boot-tested inside
    upstream's own CI, which has secrets and runners the fork does not.
  • The commits are individually scoped and messaged; the history is not squashed,
    so it can be reviewed or taken piecemeal.

Happy to split this into smaller PRs by area if that is easier to review.

adrelanos pushed a commit that referenced this pull request Sep 8, 2026
- advance submodule gitlinks to upstream's current pointers so Kicksecure sees
  zero gitlink delta when pulling our master (fixes PR #7 gitlink changes)
- absorbs Kicksecure's own arraybolt3/trixie merge and the
  kicksecure-debug -> kicksecure-ci-tiny-do-not-use flavor rename
- keep our newer frozen pin 20260821T022305Z; our SMBIOS-reader design retained
claude and others added 29 commits September 9, 2026 06:26
The build lane has ended three times out of four with the build step never
concluding, no 'ERROR detected in script' marker anywhere, and the log stopping
mid-trace at 3500_install-packages after ~38-40 minutes. That shape means the
process was KILLED, not that the build failed -- but nothing recorded why, so
each occurrence cost a full re-run to learn nothing new.

Collects the evidence while the runner still exists:
  - docker inspect OOMKilled / ExitCode on every container -- the decisive field
  - kernel OOM records, from both dmesg and the journal
  - memory (including swap, which hosted runners may not have), disk, load

Runs on failure only and cannot fail the job: an evidence collector that itself
fails replaces one unexplained failure with two. Every command is guarded and
the script ends in an explicit success. Verified end to end: prints its sections
and exits 0 on a healthy host.

Deliberately diagnosis, not a fix. The obvious guess is memory pressure -- the
failures began with the 24-gitlink bump, which pulled in newer submodule code
and made 3500 heavier -- but guessing at a remedy without the OOMKilled flag
would be another 40-minute round trip per attempt.

Co-Authored-By: Claude <noreply@anthropic.com>
Literal text and newlines in the format string; the data belongs in the
argument. Surfaced by the gate now enforcing R-030.

Co-Authored-By: Claude <noreply@anthropic.com>
Pinning the build's umask does not reach files the CHECKOUT created, because the
checkout happened first. git records only 100644 / 100755 and applies the umask
on top: 0644 on a umask-022 host, 0660 on a umask-007 one. Any build step that
then copies a source file with a plain 'cp' carries that mode into the image.

Measured on commit 41e408b: /etc/debootstrap/packages came out 0644 in CI and
0640 locally, and it was one of only two differences left between the CI image
and a local build of the same commit. grml-debootstrap:2273 copies it with a
plain cp, so the mode is inherited straight from the checkout -- the same defect
class already fixed for the five derivative-maker chroot placements, but in a
submodule and therefore untouched by that fix.

Normalising the tree to the mode git actually records fixes the whole class at
once, wherever the copy happens, instead of chasing each cp site.

Verified on a fixture with a parent and a submodule, all four files chmod'ed to
the umask-007 form beforehand: regular files become 0644 and executables 0755 in
both trees.

Co-Authored-By: Claude <noreply@anthropic.com>
The failure-handler approach added earlier cannot work for this failure mode,
and the run that proved it is unambiguous: build (b) died and EVERY step from
the build onward -- the 'if: failure()' diagnostic and github's own post-steps
included -- is recorded as never having run. The runner itself dies, so nothing
downstream of the build can capture anything.

The only evidence that survives is whatever already reached the job log. So the
sampler runs INSIDE the build step, backgrounded, and its readings interleave
into that step's own output right up to the moment of death: MemAvailable,
SwapFree, MemTotal and the largest RSS consumer by name, once every 30s.

One line per interval, so the sampler cannot itself be what fills the log. The
build step's trap kills it when the build returns.

ci/diagnose-build-kill is kept but its comment now states its real scope: it
helps when the build fails and the RUNNER SURVIVES -- a container OOM-killed
inside an otherwise healthy job, where docker's OOMKilled flag is decisive. That
case is real and validated against a genuine OOM kill; it is simply not this one.

Co-Authored-By: Claude <noreply@anthropic.com>
Proven by synthetic probe rather than reasoned about. Two kill shapes were run
against a throwaway workflow in ~1 minute each instead of ~40:

  process-oom  a memory-limited child killed -- step FAILS, runner survives:
               the step-level collector runs, and so does a separate job.
  runner-oom   the job does not fail cleanly: the step-level collector is
               SKIPPED, while a separate job still runs and reports
               'victim: cancelled | 1=success,2=cancelled,3=skipped,4=success'.

The second shape is what the build lane hits: the build step and everything
after it, including github's own post-steps, are recorded as never having run.
No step in that job can report anything, which is why the earlier 'if: failure()'
collector produced nothing.

The postmortem job runs on a FRESH runner, so it survives. It cannot see the
dead runner's memory or dmesg -- the in-step MEMSAMPLE lines cover that, being
written into the log while the build is still alive -- but it makes the shape of
the failure explicit instead of leaving a silent mystery.

Co-Authored-By: Claude <noreply@anthropic.com>
Workaround, deliberately loud rather than silent: the target now warns and sets
dist_build_windows_installer=false, so a build that asked for it cannot mistake
the result for a Windows installer having been produced.

The target downloads the VirtualBox Windows installer and verifies its
Authenticode signature with osslsigncode, which fetches every CRL distribution
point over the network at verify time. That makes an otherwise offline
'--freshness frozen' build depend on a live fetch to crl3.digicert.com, and it
failed a CI build outright:

  PEM routines:get_name:no start line
  Use the "-CRLfile" option to add one or more CRLs in PEM format.
  Signature verification: failed

while the identical installer verified fine in the sandbox minutes earlier.

A real fix already landed in developer-meta-files 9ac5d857 (fetch the CRLs with
curl, convert DER to PEM, retry with -CRLfile, revocation checking preserved).
This disables the target anyway so the lane stops depending on it while CI
reliability is the priority. Rationale, evidence and re-enable steps recorded in
~/TODO.md.

Co-Authored-By: Claude <noreply@anthropic.com>
- Both call sites commented out: the per-flavor sanity-tests loop and the
  prepare-build-machine call that actually triggers the VirtualBox Windows
  installer download.
- WHY: osslsigncode verifies the installer's Authenticode signature by fetching
  every CRL distribution point over the network at verify time, so an otherwise
  offline --freshness frozen build depends on a live fetch to crl3.digicert.com.
  It failed CI outright.
- Disabled at the caller, not neutralised in parse-cmd: --target windows keeps
  meaning what it says for anyone who passes it deliberately.
- Details and the re-enable steps: ~/TODO.md, "osslsigncode / Windows installer
  target".

Co-Authored-By: Claude <noreply@anthropic.com>
Call dm-debconf-normalize-owners on the rootfs config.dat before packing
(4350 raw/qcow2, 3600 ISO), so debconf question ownership is byte-identical
regardless of the build environment's configure sequence. Last local-vs-CI
difference. Root cause + fix detail in the developer-meta-files commit.

Co-Authored-By: Claude <noreply@anthropic.com>
…cibility

- Call dm-normalize-fat-partition on the vfat ESP (found on the same kpartx
  mapping mke2fs just wrote) so the FAT is byte-identical across build hosts --
  the 3.44 MB local-vs-CI difference. mke2fs -d already handles the ext4 root.
- Pin SYMLINK mtimes in the reimage tree to SOURCE_DATE_EPOCH (touch -h): the
  build's file-mtime pinning uses touch WITHOUT --no-dereference, so it follows
  update-alternatives links (e.g. /usr/sbin/policy-rc.d) and pins the target,
  leaving the link's own wall-clock mtime -- the last local-vs-CI byte in the
  root partition.
- Add dosfstools + mtools to the build deps (mkfs.fat + mcopy/mmd); they are
  only Recommends of grml-debootstrap.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds check-sufficient-disk: a full ISO+qcow2 build writes a ~1.3 GB sparse raw,
a live-build chroot, the qcow2 and working copies, and silently runs out deep in
a later step (observed: 3600 live-build bootstrap_cache restore, ~40 min in,
'No space left on device'). Check free space in the build output folder up front
and fail with a clear message + how much is needed. Floor 20 GiB, override via
dist_build_min_free_gib.

Co-Authored-By: Claude <noreply@anthropic.com>
A reproducibility check needs only the tiny '.sha512sums' to compare hashes, but
today those sidecars are bundled inside the ~1.3 GB 'repro-image' artifact, so a
verifier downloads the whole image for one hash line (learned the hard way).

- local-build.yml: new 'repro-checksums-<copy>' artifact (~100 KB) with every
  '.sha512sums' (+ '.asc'/'.sig'), image + torrent signatures, and the signing
  PUBLIC keys. repro-image stays as-is for the compare job.
- 5200_prepare-release: export the auto-generated signing PUBLIC keys next to the
  build output (signify '.pub' verifies '.sig'; OpenPGP cert verifies '.asc').
  Public keys only; best-effort so it can never fail an otherwise-good build.

Bundle assembly verified synthetically against a real local build's output:
22 files, ~110 KB, both pubkeys exported cleanly (sq cert export + keyname.pub).

Co-Authored-By: Claude <noreply@anthropic.com>
… too)

Image filenames are '<Product>-<flavor>-<version>...', so 'Kicksecure-*' globs
silently drop Whonix-Gateway/-Workstation images. Match the distributable
images and sidecars by extension (.qcow2.libvirt.xz / .iso / .ova and .sha512sums
/ .asc / .sig / .torrent / .pub) so both products are covered. Verified against a
real build's output: repro-image catches images+sidecars (no .raw), repro-checksums
catches only the small sidecars+pubkeys, neither catches the build-cache dirs.

Co-Authored-By: Claude <noreply@anthropic.com>
Same generalization as local-build.yml: 'Kicksecure-*.qcow2*/.iso/.ova' globs
miss Whonix images; match by extension so both products work. The variables
'Kicksecure-*' VMNAME defaults are per-flavor config (kicksecure-cli etc.),
correct as-is; Whonix flavors set their own.

Co-Authored-By: Claude <noreply@anthropic.com>
Boot-test image discovery hardcoded 'Kicksecure-*.{iso,ova,qcow2.libvirt.xz}',
which misses Whonix images. Match by extension only. (Also fixed a pre-existing
R-030 printf in the touched file.)

Co-Authored-By: Claude <noreply@anthropic.com>
The disk path (4350) already pins symlink mtimes to SOURCE_DATE_EPOCH; the ISO
squashfs chroot had the same update-alternatives symlink wall-clock-mtime leak
(the build's mtime-pinning follows links, pinning targets not the links). Add
the same 'find -type l -exec touch -h @sde' before mksquashfs. The ISO EFI FAT
(efi.img) is already reproducible via live-build (SOURCE_DATE_EPOCH volume-id +
pinned mtimes + zeroed fresh image), so no FAT normalization is needed on the
ISO path -- confirmed by upstream live-build code review.

Co-Authored-By: Claude <noreply@anthropic.com>
- dist_build_sign_and_tag now defaults false (variables): signing rewrites
  submodule HEADs + re-commits gitlinks, which local/dev/AI builds do not
  want. An explicit value (--sign-and-tag, env, dispatch site) still wins.
- dist_build_ignore_unsigned now defaults to the INVERSE of sign_and_tag, so
  a non-signing build skips signature verification in lockstep instead of
  failing git_sanity_test on its own unsigned tree. This is the behaviour
  git_sanity_test already documented; the code now matches.
- Redistributable dispatch opts in explicitly (a redistributable image
  requires signatures; git_sanity_test rejects unsigned+redistributable):
  local-build.yml, local-boot-test.yml, ci/reproducible-build-twice,
  ci/dry-run.d/200_sign-and-tag. CI build behaviour is unchanged -- it signs
  exactly as before, now by explicit opt-in rather than the default.
- parse-cmd help text updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dry-run runs BOTH 200_sign-and-tag and the build (300) via run-as-user,
whose 'sudo --preserve-env=PATH' strips every var but PATH. With
dist_build_sign_and_tag defaulting off, the earlier fix set it only for step
200, so the build's git_sanity_test saw sign_and_tag=false ->
ignore_unsigned=true, colliding with the forced dist_build_redistributable=true
(mutually exclusive) and failing the lane.

- 300_run-derivative-maker: add dist_build_sign_and_tag=true to the env prefix
  that already carries CI/arch past run-as-user, so git_sanity_test sees it.
- 200_sign-and-tag: keep the env-prefix opt-in so sign-and-tag actually signs
  (300 asserts the buildinfo carries recorded provenance, not 'unrecorded').
- ci/dry-run: drop the export attempt (run-as-user strips it) and document that
  the opt-in must ride each run-as-user hand-off's env prefix.

The full build lanes (local-build.yml, local-boot-test.yml,
ci/reproducible-build-twice) run dm-build-official directly with no run-as-user
layer, so their existing export reaches git_sanity_test -- confirmed by the
full build passing its signature verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vbox-ova-reproducible-normalize is present at the pinned helper-scripts gitlink
(63608f3d), so the .ova export no longer aborts dm-prepare-release. Offer
'virtualbox' as a target choice; REPRO_MULTI_TARGET_LIST already yields
'virtualbox' for a non-iso target, the upload globs already collect *.ova, and
the compare job already maps virtualbox -> *.ova. So a virtualbox dispatch
builds two runners' .ova and diffoscopes them byte-for-byte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-in only

The reader turns a firmware-supplied string (SMBIOS Type 1 Serial Number, after a
'dm-cmdline=' sentinel) into trailing kernel command line, so whoever can write
that field gets 'init=/bin/sh'. The kernel command line is the part of the boot
chain Secure Boot does not authenticate, and vm-config-dist is a dependency of
dist-general-cli, so the reader shipped on bare-metal installs too. It exists only
for the QEMU boot-test harness.

Realistic writers without OS root: Supermicro SUM EditDmiInfo/ChangeDmiInfo over
IPMI with no OS on the box, HPE via RBSU through iLO KVM, and any OEM or
refurbisher DMI editor -- set once in the supply chain it survives disk wipe and
reinstall. Stock Redfish is narrower: SerialNumber is readonly, only AssetTag is
writable.

- help-steps/parse-cmd, help-steps/variables: --smbios-reader [true|false],
  default false, exported to the build steps.
- 3500_install-packages: write /etc/default/grub.d/50_dm-smbios-reader.cfg into
  the chroot before grub-mkconfig; remove it when disabled, so it cannot survive
  from an earlier build in the same slot.
- 3600_convert-raw-to-iso: gate both halves of the ISO path -- the
  ${dm_smbios_extra} placeholder on --bootappend-live, and inlining the reader
  into config.cfg.
- live-build-data/grub-config: the reader lives in smbios-reader.cfg so it can be
  omitted; config.cfg no longer carries it.
- local-boot-test.yml: pass --smbios-reader true. Without it every boot leg would
  time out with a 0-byte serial log rather than fail loudly.
- Bump the vm-config-dist gitlink for the matching disk-path guard.

Not a Secure Boot bypass: Secure Boot never covered the kernel command line. This
is a hardening gap -- every attacker who can reach it can already do worse -- but
Kicksecure appears to be the only distro reading SMBIOS into the cmdline in
grub.cfg, and under SEV-SNP/TDX it would be a real boundary crossing, which is why
systemd stopped trusting SMBIOS there.

Verified without a rebuild: placeholder escaping is byte-identical to the previous
inline form and empty when disabled; the opt-in was exercised through a built
image's own update-grub in a chroot (absent 0 reader lines / 0 placeholders,
enabled 5 / 11); the QEMU gate was exercised by booting a built image five ways,
canary-checked. The build-option plumbing itself is checked statically only and
needs one real build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
actionlint rejects 'if: false' as a constant condition. The step is already inert,
so commenting it out is behaviour-identical and matches how the other disabled
vbox pieces are expressed. Re-enable instructions kept.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ISO copy of the reader, kept identical to the disk copy in vm-config-dist, plus
the matching gitlink bump (00dd919..52d2087, reachable on org-ai-assisted).

Type 1 offset 7 holds a string INDEX, and 0 means "no string" (DSP0134 6.1.3), so
an unguarded --get-string 7 makes GRUB print 'error: failed to retrieve the
structure field.' on every ordinary boot of an image built with the reader -- the
QEMU gate passes on any QEMU guest, and the serial is set only when a tester
injects. Probe the index byte first; read the string only when it is non-zero.

Found by coderabbit on org-ai-assisted/vm-config-dist#8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The header described resolvability only, while the job also fails a gitlink
that deviates from the merge base with Kicksecure/derivative-maker.

- Both invariants stated, with why 1 does not imply 2: a pointer merely BEHIND
  the submodule's upstream tip is resolvable, yet an upstream merge still
  conflicts on the submodule for anyone without it checked out.
- 'submodules: false' and the default depth 1 are now explained as deliberate:
  the check must reach its verdict WITHOUT submodule objects, since that is the
  case that conflicts, and it deepens from origin itself when it needs history.
- GITHUB_TOKEN is documented as required, not a rate-limit nicety: a sweep that
  verified nothing exits 2 rather than reporting a green pass.
- Step names say what the steps now do.

No behaviour change; the tool needed no workflow change to start enforcing
invariant 2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Configuration files are not auto-generated. The drop-in was printf-ed inline, so
its content lived only inside this build step -- unreadable and undiffable where
it belongs, and dependent on this script's quoting. It now ships as
vm-config-dist usr/share/vm-config-dist/50_dm-smbios-reader.cfg and is copied
with the same 'install --mode' used for 20_dist-base-files.cfg above, which is
also what keeps the installed mode independent of the builder's umask.

The unconditional safe-rm stays outside the branch on purpose: a disabled build
must clear a file left by an earlier build in the same slot.

Also bumps the vm-config-dist gitlink to carry the new file (52d2087..0a7b353,
reachable on org-ai-assisted).

Verified through the image's own update-grub in a chroot, no rebuild:

  drop-in absent      0 reader lines, 0 cmdline placeholders
  static file copied  5 reader lines, 11 placeholders
  value not "true"    0 reader lines, 0 placeholders

The first run of that check reported "absent" as enabled -- the image is reused
across runs and an earlier run had deliberately left it opted in under the
previous filename, which the rename orphaned. The check now clears every
50_dm-*.cfg before the absent case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Match dm-reproducible-compare-artifacts' dedicated 'artifacts differ' code (4,
was 1). These consumers forward the comparator's exit verbatim, so only the
documented contract changes: 0 reproducible, 4 differ, 2 setup, 3 build failed
(build-twice), 1 internal error.
…ss.bsh

Follow the developer-meta-files rename of reprepro-freshness.bsh (the library
also manages package-build-hash manifests). Source-path + comment update only.
Follow package-build-freshness dropping its reprepro path: dm_package_needs_rebuild
now takes (source_dir, apt_codename) and decides from the source-content hash, so
the call no longer passes the repository name and the comment describes content
detection, not a published-version check.
Document the tested behaviour of dm-reproducible-compare-artifacts on real
multi-gigabyte iso/ova images: the sha256 verdict is authoritative, but the
best-effort diffoscope EXPLANATION can OOM (measured: a 1.4 GB differing .iso ->
diffoscope exit 2 'Out of memory'), and the tool degrades gracefully to the
sha256 verdict. Note that extending the qcow2 mounted route (attach read-only,
diffoscope the mount points) to iso/virtualbox would give them a memory-safe
explanation -- a future improvement, not required for a correct verdict.
- libpam-tmpdir (security-misc) points TMPDIR at a path absent in the
  chroot; debootstrap inheriting it aborts opaquely (Debian #823651)
- must clear at the invocation: sudo env_reset + PAM re-set it after the
  pbuilder configfile is sourced, mirroring the adjacent LD_PRELOAD unset
- add R-010 export LC_ALL=C to the strict block (inert here; variables
  re-sets C.UTF-8 on source)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mount-cleanup refuses a non-root EUID; these two callers invoked it bare
(all 7 others use ${SUDO_TO_ROOT}), so it died 'MUST be run as root'
during 3500 teardown once the build reached it. Also add the missing
'export LC_ALL=C' both files' strict blocks lacked (R-010).
- matches xtrace.bsh output_cmd becoming an array (printf %s\n over echo)
- back-compatible with a scalar output_cmd during the rollout window
- Default true: 4x3/BIOS is the reproducible-build default (primary use
  case is BIOS VMs); grub-mkconfig in the chroot must not read build-host
  firmware. On env_vars_keep_list so it crosses into the chroot.
The underscore path usr/libexec/helper_scripts/strings.bsh does not exist
(the dir is helper-scripts); help-steps/variables is sourced by every build
step, so every build aborted. Matches line 185's shellcheck directive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- dist_build_image_upload_supported now STICKS true across multiple
  --target: dm-upload-images gates the whole upload on it, so token
  order must not let a later unsupported target flip it back
- multiple-flavor guard fires even when one --flavor is source: source_run
  no longer exempts it, so '--flavor source --flavor X' is refused
- note --target/--type compatibility as an accepted, unvalidated limitation
- vm_names_to_be_exported=none (set for --flavor source) no longer derives
  a bogus '.../none-<ver>.<arch>.raw' path; the vars stay unset, matching
  the kicksecure single-build case
- move set_default_variable + the derivation into source-able
  variables-lib.bsh so a unit test can source and call them directly
- kicksecure --type host has dist_build_type_long=kicksecure-host, not a
  substring of Kicksecure-CLI, so the skip does NOT fire and the vars are
  populated; only workstation/unified builds consume them, so it is unread
- substring match is not path-containment; dist_build_version with '../'
  escapes it. Documented as a known, out-of-scope limitation (builder-set
  override, self-inflicted threat model)
- new grml-debootstrap bails when SOURCE_DATE_EPOCH is set (frozen builds
  always set it) unless --defaultinterfaces is passed
Prints declare -p and exits before any build step; consumed by dist-ai
dm-help-steps-tests to baseline variables ahead of a variables refactor.
Default unset -> no build impact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tracks the helper-scripts has.sh->has.bsh rename.
- Converts a raw disk image into a highly boot-compatible hybrid ISO (legacy
  BIOS + UEFI + UEFI Secure Boot via shim->signed grub + loopback.cfg + USB/DVD
  hybrid) using only packages.debian.org tools; dracut-based. Reimplements what
  live-build's ISO packaging does, without live-build. Prep for porting
  3600_convert-raw-to-iso off live-build.
- Static GRUB config templates in dm-raw-to-iso.d/ (no auto-generated config).
- All live-build architectures: amd64, i386, arm64, armhf.
- dracut --add dmsquash-live (hardened images suppress auto-inclusion); neutralize
  the input's /etc/fstab + /etc/crypttab so a disk rootfs boots live, not into
  emergency mode.
- Verified: builds an authentic Kicksecure-CLI raw into an ISO that boots to a
  login prompt under bios and efi-secureboot. Tests + docs live in dist-ai.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Feature-preserving: the ISO keeps the custom GRUB theme, the three live
  boot roles (USER/SYSMAINT/UNRESTRICTED) and the Utilities + keyboard-layout
  submenus, now supplied to the generic tool via new --grub-overlay/--live-overlay.
- Dead debian-installer scaffolding dropped (already disabled, never reached the
  ISO; install-to-disk is Calamares in the live session).
- The live-build submodule gitlink is intentionally NOT removed (human-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ai-review (grok/claude) on the port:
- The ISO squashfs missed initializer-dist's chroot-scripts-post.d (approx apt
  sources removal, random-seed wipe, autologin, setup wizard, MOK leak check):
  4300 skips them for dist_build_iso and runs after 3600, and the old live-build
  path ran them itself. 3600's prepare_raw now runs them, mirroring 4300.
- The ISO grub/shim/memtest build deps are host-arch-specific; installing them
  unconditionally in 30_dependencies.conf broke arm64 hosts and non-ISO builds.
  They already live per-arch in 1200 under dist_build_iso; only arch-independent
  librsvg2-bin stays in 30. Added memtest86+ (amd64) and grub-efi-arm64-signed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ai-review (claude) on the fix commit:
- prepare_raw armed the unchroot_unmount cleanup trap but tore the chroot/mount
  down itself before returning; a later failure (overlay build, tool run) would
  re-run unchroot-raw/unmount-raw against an already-clean state and mask the real
  error. Reset the trap to exception_handler_general after teardown.
- memtest is an optional diagnostic (its GRUB entry self-hides when absent), so a
  missing memtest86+ binary now warns and skips instead of failing the whole ISO
  (matters for a cross-arch / minimal build host); a native amd64 build still
  installs and stages it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 'cp' without -p creates dest as source-mode & ~umask, so the ISO's
  boot/grub/kb_layouts modes depended on the builder's umask; chmod them
  to fixed 0644/0755 for a byte-identical rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- the bootstrap apt-get-noninteractive wrapper sources helper-scripts
  libraries (strings.bsh -> wc-test.sh -> check_runtime.bsh); staging
  only the wrapper aborted the first in-image apt-get with "strings.bsh:
  No such file". Stage the transitive closure alongside it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 4300 no longer skips dist_build_iso: the ISO runs the same
  chroot-scripts-post.d as raw/qcow2/vbox (all five are live-safe).
- rename 3600 -> 4310_convert-raw-to-iso so it runs after 4300; drop the
  embedded post-scripts + duplicate set-grub-keymap (3500 builds them).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- the boot-test job checks out submodules:false, but dm-boot-test sources
  helper-scripts' package_installed_check.sh; every leg aborted before
  qemu. Init that one submodule before the fork-branch switch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- live ISO booted UNHARDENED: the live cmdline carried none of security-misc's
  kernel hardening (mitigations, slab_nomerge, rd.shell=0, rd.emergency=halt);
  the old live-build path scraped it from grub-mkconfig, so restore that in 4310
- cross-arch real ISO builds need target-arch loaders the host lacks; fail early
  and clearly (native-only) instead of deep in dm-raw-to-iso
- exotic ISO --arch (e.g. ppc64el) passed CLI validation then aborted in the
  converter after a ~40 min build; gate it in parse-cmd to the converter's set

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Debian Trixie ships no signed ARM32 GRUB and no ARMHF shim, so dm-raw-to-iso
  hard-errors on the missing gcdarm/shimarm for armhf's only EFI platform; every
  real armhf ISO build fails. Gate ISO --arch to {amd64,arm64} (the archs with
  signed GRUB+shim), same spirit as the ppc64el rejection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- live-build built the ISO bootloader inside the target chroot, so a cross-arch
  ISO worked; this port sourced the signed EFI loaders + grub modules from the
  HOST, so cross-build failed on the host's wrong-arch loaders
- dm-raw-to-iso now takes the signed loaders, grub modules and unicode.pf2 from
  the mounted TARGET rootfs (grml installs grub-efi-<arch>-signed + shim-signed
  per target arch), so a cross-arch build gets the correct-arch loaders
- revert the native-only gate in 4310 (it was the wrong fix for the host/target
  mismatch); cross-build now works, native unchanged

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 4310: extract the rootfs kernel hardening by token content (strip
  linux/kernel-path, then root= and a standalone ro) instead of a positional
  field cut, which silently drops a hardening param -- or leaks root= and breaks
  the live boot -- if grub's token layout shifts
- 1200: reject a non-username $USER (the sudoers word ALL, or a metacharacter)
  before writing the passwordless-sudo rule; USER=ALL would otherwise grant every
  account root and still pass visudo --check
- dm-raw-to-iso: mksquashfs -one-file-system + proc/*,sys/* excludes, so a
  silently-failed API-bind unmount cannot pack the build host's /proc,/sys into
  the public live ISO

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- EFI-signing set (efivar, sbsigntool, keyutils, shim-signed-common, fwupd,
  fwupd-signed, efibootmgr) is installed by 3500's availability-checked
  efi_weak_recommended list; a hard ISO entry instead breaks a build on an
  arch that lacks the package.
- base / boot packages (ca-certificates, less, mokutil, dracut,
  grub-live-dracut, sudo, tirdad, legacy-dist, kicksecure-packages-dependencies-pre,
  mate-polkit) already come from the flavor metapackage graph or 3500 itself.
- the tool ships no GRUB config: --grub-overlay is required and must carry
  config.cfg/grub.cfg/loopback.cfg/esp-redirect.cfg; @APPEND_LIVE@ and @timeout@
  are substituted in staged cfgs and --serial-console injects the serial terminal.
- iso-build-data/grub-config is now the single ISO GRUB config source (loopback.cfg
  and esp-redirect.cfg moved in from the deleted dm-raw-to-iso.d/); 4310 stages them.
- boot-verified in the sandbox: BIOS + EFI + EFI-secureboot reach a login prompt.
- variables: the SUDO_TO_ROOT re-entrance guard flag was exported, so a child
  process (build steps, recursive derivative-maker/genmkfile) inherited the flag
  but not SUDO_TO_ROOT, rebuilt the bare default, and skipped the --preserve-env
  append -- dropping DIST_APTGETOPT_SERIALIZED (and the other passed vars) from
  sudo. Keep the guard same-shell only: do not export it.
- mmdebstrap: define a local exit_with_error; the wrapper cannot source pre, so a
  failed sanity check aborted with 'error: command not found' instead of the real
  cause. Also add the missing 'export LC_ALL=C'.
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