Skip to content

flavors via traits; Ubuntu+Rocky; k8s-CAPT image+in-memory; content-hashed caches in OCI; more powerful custom/ - #81

Open
rpardini wants to merge 14 commits into
tinkerbell:mainfrom
rpardini:pr/flavors-via-traits-UbuntuRocky-k8s-CAPT-imagein-memory-content-hashed-caches-in-OCI-more-powerful-custom
Open

rpardini wants to merge 14 commits into
tinkerbell:mainfrom
rpardini:pr/flavors-via-traits-UbuntuRocky-k8s-CAPT-imagein-memory-content-hashed-caches-in-OCI-more-powerful-custom

Conversation

@rpardini

@rpardini rpardini commented Oct 31, 2026 •

Copy link
Copy Markdown
Member
  • a large monster of a PR; sorry!
  • focus on docs/ for review
  • incl 2 rounds of fixes vs Copilot; it can always find more...

flavors via traits; Ubuntu+Rocky; k8s-CAPT image+in-memory; content-hashed caches in OCI; more powerful custom/

  • 🍃 Revert "trixie-meson64: use Armbian's oldlts instead of edge" This reverts commit b73fb91.
  • 🌿 flavors: compose flavors from traits (distro/platform/role/output)
    • replace the BaseFlavor class chain and per-flavor modules with Flavor composed of traits, plus a registry in captain/flavors.py
    • traits live in captain/traits/ (core, distro, platform, role, output); content collected along each trait's MRO
    • render one mkosi drop-in dir per trait content into disposable mkosi.generated/; mkosi runs with --directory
    • split mkosi.conf.j2, postinst and finalize per trait; custom/ becomes the last drop-in (90-custom)
    • builder container mounts mkosi.generated/ instead of six generated paths
    • keep symlinks from trait content dirs (initrd*/systemd-firstboot masks, default.target)
    • no behaviour change: mkosi summary, merged trees and script bodies identical for all existing flavors
  • 🌵 flavors: Rocky Linux 10 and Ubuntu Resolute (rpm/ubuntu distros)
    • RpmFamily distro trait: deb->rpm package-name map, systemd-networkd, update-ca-trust, SELinux off
    • Rocky distro trait: Repositories=epel, rocky-release/epel-release, kernel + kernel-modules-extra (EL10 moved nft_compat/xt_* there; CNI and kube-proxy need them)
    • Ubuntu distro trait (deb family): Repositories=universe, linux-image-generic; empty linux-firmware-minimal satisfies linux-firmware (initramfs 855M -> 206M)
    • flavors rockylinux10-full, resolute-full and resolute-rockchip64
    • builder: add dnf, rpm, distribution-gpg-keys for mkosi RPM builds
    • /init: set PATH explicitly (Rocky's bash default PATH lacks sbin)
    • mem output: per-distro DTB location and switch_root provider; export DTBs before dropping /boot; os-release ID_LIKE per distro
    • DTB location comes from the platform (kernel provider); Armbian keeps Debian layout on any distro
    • armbian-next: add armbian- suite only for releases that have one
    • tink role: install tar and gzip (tink-agent-setup needs them; not Essential on Rocky)
    • bash header exports CAPTAIN_FLAVOR_ID / CAPTAIN_DISTRO* for scripts, incl. custom/ ones; example postinst handles rpm CA store
  • 🐸 flavors: k8s-capt role and mem-k8s-capt-* flavors
    • Kubernetes/KubernetesCapt role traits: pkgs.k8s.io repo (apt+dnf), kubelet/kubeadm/kubectl/cri-tools, cloud-init
    • containerd config with CRI and SystemdCgroup, crictl config, br_netfilter/overlay, k8s sysctls, kubelet+cloud-init presets
    • capt: cloud-init Ec2 datasource for Tinkerbell's Tootles (strict_id off, ds-identify enabled, 30s max wait)
    • lsb-release on deb distros (cloud-init runs lsb_release); dropped on RPM ones (no redhat-lsb in EL10)
    • --kubernetes-version/KUBERNETES_VERSION (default 1.36), passed into the builder container with ARMBIAN_VERSION
    • flavors mem-k8s-capt-{trixie-full,resolute-full,rockylinux10-full,trixie-rockchip64}
    • mem output keeps the kernel config as /usr/lib/modules//config (kubeadm preflight)
    • banner/static-network presets move from tink role to core; empty-rendered .j2 files are skipped
    • qemu: --mem/--smp (QEMU_MEM/QEMU_SMP)
  • 🌱 flavors: img output (disk images) and img-k8s-capt-* flavors; role-aware banners
    • Img output trait: mkosi Format=disk, own repart defs (ESP + root, or root only), SectorSize=512, Bootable=no
    • img postinst: distro initrd (initramfs-tools / dracut --no-hostonly); systemd-boot + BLS entry (uefi) or extlinux.conf (DT)
    • root=PARTLABEL=root, openssh-server + sudo + growpart, fresh ssh host keys per machine
    • root autologin on consoles (tty1 and any serial port), like mem; root password stays locked
    • output trait owns artifact collection and mkosi output dir/kind; out/captainos--.img.zst
    • flavors img-k8s-capt-{trixie-full,resolute-full,rockylinux10-full,trixie-rockchip64}
    • qemu: boot img flavors from a grown scratch copy (UEFI, virtio), cloud-init NoCloud seed via SMBIOS + local HTTP, ssh port forward
    • keep kernel config as /usr/lib/modules//config for every output (kubeadm preflight)
    • builder: add systemd-repart
    • banner/motd templates name role, output and flavor id; motd hints come from the role
    • core owns /etc/issue{,.net}; /etc/os-release for every flavor from the distro's (NAME/PRETTY_NAME/VARIANT/IMAGE_ID/BUILD_ID)
    • deb flavors keeping apt divert os-release/issue/issue.net so base-files upgrades don't undo them
  • 🍀 flavors: k8s-capt for Raspberry Pi, from trixie-armbian-rpi
    • add trixie-armbian-rpi to the k8s-capt bases (img- and mem-)
    • img: new "rpi" boot scheme; vfat boot partition with RPi firmware, kernel8.img, initrd, DTBs/overlays, generic config.txt/cmdline.txt (no U-Boot)
    • rpi: firmware via ExtraTrees for img; netboot artifacts (firmware-rpi/) only for mem
    • img cmdline: rootwait; serial as primary console (/dev/console), tty1 too
    • img cmdline: cgroup_enable=memory cgroup_memory=1, as the firmware prepends cgroup_disable=memory (kubeadm preflight)
  • 🌳 ci: build and release every flavor from the registry
    • captain list-flavors: table, --json, --github-output (build + combined matrices), --release lines
    • ci.yml: prepare job emits matrices; build/publish-combined jobs use fromJSON (publish-combined needs prepare)
    • combined = both arches (incl. img flavors); ISO downloads only for flavors with an ISO
    • release.yml: pull and tag loop over the registry; per-flavor release_tag_suffix keeps existing tags stable
    • release: free runner disk space before pulling all flavors (GitHub-hosted runners only)
    • caches: save under the restore step's key; initramfs/iso cached only for selected flavors (Flavor.ci_cache)
  • 🌴 cache: content-hashed OCI build cache in ghcr.io, replacing GHA caches
    • captain/cache: pure-Python OCI client; ORAS-style artifacts (one deterministic tar layer, modes kept)
    • keys hash declared inputs only (no git sha/host/paths); doc-only changes hit
    • cached pieces: tools, kernel, mkosi output (initramfs/image), iso; traits add inputs (eg. RPi firmware tag)
    • two levels: downstream fork cache first, then tinkerbell/captain upstream
    • scope auto-detected from GHA context (fork PRs read-only) or local git remotes
    • local builds read-only unless --cache-push; --cache {on,read,off}; --cache-only
    • .captain-cache-key markers replace "file exists" idempotency; mkosi cache hit skips tools and kernel stages
    • emergency rebuilds: CACHE_EPOCH (global), per-distro cache_epoch
    • captain cache info shows scope, keys and hits
    • versions: resolve kernel/armbian tokens locally like CI's prepare job (cached 6h), so cache keys match
    • builder: --ensure-remote checks own/upstream registries (manifest HEAD, no pull); obtain_builder falls back to upstream
    • ci: registry-only handoff; drop actions/cache, workflow artifacts and the kernel warmer job; publish-combined restores with --cache-only
    • ci: cache uv, CPython and wheels via local .github/actions/setup-uv; latest action versions
    • drop ci_cache, --drop-old-caches, mkosi_image_name; mount Dockerfile into builder for kernel keys
  • 🍃 release: content-tagged images, reusable release workflow, fork version tags
    • publish under a content tag (c--<arch|combined>-) from new release keys; combined bases are the per-arch content tags
    • existing content tag: no build, no builder pull; always alias it to v0.0.0--[-arch]
    • aliases and release tag are manifest re-tags via the Python OCI client (no blob transfer, no Docker)
    • new captain release status [--github-output]; ci skips building/restoring when already published
    • move builder image management to captain/builder.py (docker.py is in mkosi keys)
    • registry: existence checks accept any manifest type (registry:2 404s on indexes otherwise)
    • release.yml: also workflow_call (version, sha, prerelease, github-release); one flavor at a time
    • forks' main pushes get v0.0.0-ci.<run_number> OCI tags; GitHub prerelease only with repo var CAPTAIN_FORK_GH_RELEASE=true
    • release tag: single-arch flavors also get -; versions also tagged -[-]
    • drop the now unused skopeo.copy
  • 🌿 cli: progress bars, clearer cache scope and hit messages, --flavor alias
    • progress: shared Rich transfer bar/spinner, terminal-only (no bars in CI/pipes)
    • bars on cache blob pull/push and tools/kernel downloads; spinners on pack/extract
    • cache: "not pushing ()" instead of "write to nowhere"; scope logged up front
    • cache: on first remote hit, say why it downloads instead of building
    • cli: --flavor alias for --flavor-id
    • rpi: drop duplicated firmware download log lines
  • 🌵 custom: per-axis layers in custom/by-//; custom/builds.txt picks CI flavors
    • axes: family, distro, arch, role, output, platform, kernel, flavor; values from the traits (custom_names(), kernel_names())
    • layers merge by name, general to specific: same-path files/same-named scripts replace, packages unioned
    • symlink a by-flavor dir to share it; warn on unmatched dirs and unknown axes
    • bash header: CAPTAIN_ARCH/ROLE/OUTPUT/PLATFORM/KERNEL for scripts to branch on
    • custom/builds.txt: flavor ids or globs CI builds (matrix, tags, releases); empty builds all; unmatched line fails
    • list-flavors table/json show a ci column when builds.txt narrows the selection
    • no-op examples: custom/by-flavor/README.md, custom/by-family/rpm/packages.txt, builds.txt listing every flavor
  • 🐸 docs: flavors/traits, build cache, releases, forks and custom builds
    • README: flavors table (distros, k8s-capt mem/img, RPi), disk images, qemu, custom/ layers and builds.txt, build cache, release notes, Documentation section
    • design-decisions/flavor-traits.md: traits by axis, drop-ins, user customization layers, outputs and boot schemes (mermaid)
    • design-decisions/k8s-capt-image-builder-replace.md: img-k8s-capt flavors vs CAPT's image-builder
    • design-decisions/oci-tooling-buildah-skopeo.md: release content tags addendum
    • build-caching.md: keys, scope, CI graph, builder image, version tokens, release content tags, cold/cached results
    • fork-custom-builds.md: fork CI, image tags, consuming images, builds.txt, a repository holding only custom/
    • gha-uv-python-deps-caching.md: uv/CPython/wheel caching in GHA
    • gotchas.md: mkosi, repart/disk images, distros, k8s/cloud-init, custom/, build tooling/CI, macOS
    • kernel-build.md: version token resolution
  • 🌱 traits: no symlinks in trait dirs; mask/set-default via postinst
    • dangling default.target -> multi-user.target broke review tools (ENOENT reading it from the repo)
    • drop default.target: core postinst already does systemctl set-default multi-user.target
    • mem: replace initrd* /dev/null symlinks with systemctl mask in a new mem postinst
    • core: drop systemd-firstboot.service mask; the unit is already removed outright
    • docs/gotchas: don't commit symlinks into trait dirs
  • 🍀 docker: keep registry credentials off the command line and DEBUG logs
    • pass REGISTRY_USERNAME/PASSWORD to the builder as bare -e NAME (value from the env)
    • mask them in the DEBUG env table

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved issues expose plaintext registry credentials in logs and can block releases when single-architecture builds produce an empty combined-build matrix.

5 open issues

Review effort: Balanced

Comment thread captain/cli/_flavors.py Resolved
Comment thread .github/workflows/release.yml Resolved
Comment thread captain/cli/_build.py Resolved
Comment thread captain/cli/_build.py Resolved
Comment thread captain/tools.py Resolved
@rpardini
rpardini force-pushed the pr/flavors-via-traits-UbuntuRocky-k8s-CAPT-imagein-memory-content-hashed-caches-in-OCI-more-powerful-custom branch from cc80a6c to 668f67c Compare November 3, 2026 20:12
@rpardini
rpardini requested a balanced review from Copilot November 3, 2026 20:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Registry passwords are logged in plaintext, permission-only file changes can leave cached images stale, and non-UEFI flavors are sent through an incompatible QEMU boot path.

4 open issues
3 resolved since last review

Review effort: Balanced

Comment thread captain/cache/_keys.py Resolved
Comment thread captain/cli/_qemu.py Resolved
- replace the BaseFlavor class chain and per-flavor modules with `Flavor` composed of traits, plus a registry in `captain/flavors.py`
- traits live in `captain/traits/` (core, distro, platform, role, output); content collected along each trait's MRO
- render one mkosi drop-in dir per trait content into disposable `mkosi.generated/`; mkosi runs with `--directory`
- split `mkosi.conf.j2`, postinst and finalize per trait; custom/ becomes the last drop-in (`90-custom`)
- builder container mounts `mkosi.generated/` instead of six generated paths
- keep symlinks from trait content dirs (initrd*/systemd-firstboot masks, default.target)
- no behaviour change: mkosi summary, merged trees and script bodies identical for all existing flavors

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- `RpmFamily` distro trait: deb->rpm package-name map, systemd-networkd, update-ca-trust, SELinux off
- `Rocky` distro trait: `Repositories=epel`, rocky-release/epel-release, `kernel` + `kernel-modules-extra` (EL10 moved nft_compat/xt_* there; CNI and kube-proxy need them)
- `Ubuntu` distro trait (deb family): `Repositories=universe`, linux-image-generic; empty linux-firmware-minimal satisfies linux-firmware (initramfs 855M -> 206M)
- flavors rockylinux10-full, resolute-full and resolute-rockchip64
- builder: add dnf, rpm, distribution-gpg-keys for mkosi RPM builds
- /init: set PATH explicitly (Rocky's bash default PATH lacks sbin)
- mem output: per-distro DTB location and switch_root provider; export DTBs before dropping /boot; os-release ID_LIKE per distro
- DTB location comes from the platform (kernel provider); Armbian keeps Debian layout on any distro
- armbian-next: add armbian-<release> suite only for releases that have one
- tink role: install tar and gzip (tink-agent-setup needs them; not Essential on Rocky)
- bash header exports CAPTAIN_FLAVOR_ID / CAPTAIN_DISTRO* for scripts, incl. custom/ ones; example postinst handles rpm CA store

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- `Kubernetes`/`KubernetesCapt` role traits: pkgs.k8s.io repo (apt+dnf), kubelet/kubeadm/kubectl/cri-tools, cloud-init
- containerd config with CRI and SystemdCgroup, crictl config, br_netfilter/overlay, k8s sysctls, kubelet+cloud-init presets
- capt: cloud-init Ec2 datasource for Tinkerbell's Tootles (strict_id off, ds-identify enabled, 30s max wait)
- lsb-release on deb distros (cloud-init runs lsb_release); dropped on RPM ones (no redhat-lsb in EL10)
- `--kubernetes-version`/KUBERNETES_VERSION (default 1.36), passed into the builder container with ARMBIAN_VERSION
- flavors mem-k8s-capt-{trixie-full,resolute-full,rockylinux10-full,trixie-rockchip64}
- mem output keeps the kernel config as /usr/lib/modules/<kver>/config (kubeadm preflight)
- banner/static-network presets move from tink role to core; empty-rendered .j2 files are skipped
- qemu: --mem/--smp (QEMU_MEM/QEMU_SMP)

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
…are banners

- `Img` output trait: mkosi Format=disk, own repart defs (ESP + root, or root only), SectorSize=512, Bootable=no
- img postinst: distro initrd (initramfs-tools / dracut --no-hostonly); systemd-boot + BLS entry (uefi) or extlinux.conf (DT)
- root=PARTLABEL=root, openssh-server + sudo + growpart, fresh ssh host keys per machine
- root autologin on consoles (tty1 and any serial port), like mem; root password stays locked
- output trait owns artifact collection and mkosi output dir/kind; out/captainos-<flavor>-<arch>.img.zst
- flavors img-k8s-capt-{trixie-full,resolute-full,rockylinux10-full,trixie-rockchip64}
- qemu: boot img flavors from a grown scratch copy (UEFI, virtio), cloud-init NoCloud seed via SMBIOS + local HTTP, ssh port forward
- keep kernel config as /usr/lib/modules/<kver>/config for every output (kubeadm preflight)
- builder: add systemd-repart
- banner/motd templates name role, output and flavor id; motd hints come from the role
- core owns /etc/issue{,.net}; /etc/os-release for every flavor from the distro's (NAME/PRETTY_NAME/VARIANT/IMAGE_ID/BUILD_ID)
- deb flavors keeping apt divert os-release/issue/issue.net so base-files upgrades don't undo them

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- add trixie-armbian-rpi to the k8s-capt bases (img- and mem-)
- img: new "rpi" boot scheme; vfat boot partition with RPi firmware, kernel8.img, initrd, DTBs/overlays, generic config.txt/cmdline.txt (no U-Boot)
- rpi: firmware via ExtraTrees for img; netboot artifacts (firmware-rpi/) only for mem
- img cmdline: rootwait; serial as primary console (/dev/console), tty1 too
- img cmdline: cgroup_enable=memory cgroup_memory=1, as the firmware prepends cgroup_disable=memory (kubeadm preflight)

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- `captain list-flavors`: table, `--json`, `--github-output` (build + combined matrices), `--release` lines
- ci.yml: prepare job emits matrices; build/publish-combined jobs use fromJSON (publish-combined needs prepare)
- combined = both arches (incl. img flavors); ISO downloads only for flavors with an ISO
- release.yml: pull and tag loop over the registry; per-flavor `release_tag_suffix` keeps existing tags stable
- release: free runner disk space before pulling all flavors (GitHub-hosted runners only)
- caches: save under the restore step's key; initramfs/iso cached only for selected flavors (Flavor.ci_cache)

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- captain/cache: pure-Python OCI client; ORAS-style artifacts (one deterministic tar layer, modes kept)
- keys hash declared inputs only (no git sha/host/paths); doc-only changes hit
- cached pieces: tools, kernel, mkosi output (initramfs/image), iso; traits add inputs (eg. RPi firmware tag)
- two levels: downstream fork cache first, then tinkerbell/captain upstream
- scope auto-detected from GHA context (fork PRs read-only) or local git remotes
- local builds read-only unless --cache-push; --cache {on,read,off}; --cache-only
- .captain-cache-key markers replace "file exists" idempotency; mkosi cache hit skips tools and kernel stages
- emergency rebuilds: CACHE_EPOCH (global), per-distro cache_epoch
- `captain cache info` shows scope, keys and hits
- versions: resolve kernel/armbian tokens locally like CI's prepare job (cached 6h), so cache keys match
- builder: `--ensure-remote` checks own/upstream registries (manifest HEAD, no pull); obtain_builder falls back to upstream
- ci: registry-only handoff; drop actions/cache, workflow artifacts and the kernel warmer job; publish-combined restores with --cache-only
- ci: cache uv, CPython and wheels via local .github/actions/setup-uv; latest action versions
- drop ci_cache, --drop-old-caches, mkosi_image_name; mount Dockerfile into builder for kernel keys

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
…on tags

- publish under a content tag (c-<flavor>-<arch|combined>-<digest>) from new release keys; combined bases are the per-arch content tags
- existing content tag: no build, no builder pull; always alias it to v0.0.0-<sha7>-<flavor>[-arch]
- aliases and `release tag` are manifest re-tags via the Python OCI client (no blob transfer, no Docker)
- new `captain release status [--github-output]`; ci skips building/restoring when already published
- move builder image management to captain/builder.py (docker.py is in mkosi keys)
- registry: existence checks accept any manifest type (registry:2 404s on indexes otherwise)
- release.yml: also workflow_call (version, sha, prerelease, github-release); one flavor at a time
- forks' main pushes get v0.0.0-ci.<run_number> OCI tags; GitHub prerelease only with repo var CAPTAIN_FORK_GH_RELEASE=true
- `release tag`: single-arch flavors also get <tag>-<arch>; versions also tagged <version>-<flavor id>[-<arch>]
- drop the now unused skopeo.copy

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- progress: shared Rich transfer bar/spinner, terminal-only (no bars in CI/pipes)
- bars on cache blob pull/push and tools/kernel downloads; spinners on pack/extract
- cache: "not pushing (<reason>)" instead of "write to nowhere"; scope logged up front
- cache: on first remote hit, say why it downloads instead of building
- cli: --flavor alias for --flavor-id
- rpi: drop duplicated firmware download log lines

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
…xt picks CI flavors

- axes: family, distro, arch, role, output, platform, kernel, flavor; values from the traits (custom_names(), kernel_names())
- layers merge by name, general to specific: same-path files/same-named scripts replace, packages unioned
- symlink a by-flavor dir to share it; warn on unmatched dirs and unknown axes
- bash header: CAPTAIN_ARCH/ROLE/OUTPUT/PLATFORM/KERNEL for scripts to branch on
- custom/builds.txt: flavor ids or globs CI builds (matrix, tags, releases); empty builds all; unmatched line fails
- list-flavors table/json show a ci column when builds.txt narrows the selection
- no-op examples: custom/by-flavor/README.md, custom/by-family/rpm/packages.txt, builds.txt listing every flavor

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- README: flavors table (distros, k8s-capt mem/img, RPi), disk images, qemu, custom/ layers and builds.txt, build cache, release notes, Documentation section
- design-decisions/flavor-traits.md: traits by axis, drop-ins, user customization layers, outputs and boot schemes (mermaid)
- design-decisions/k8s-capt-image-builder-replace.md: img-k8s-capt flavors vs CAPT's image-builder
- design-decisions/oci-tooling-buildah-skopeo.md: release content tags addendum
- build-caching.md: keys, scope, CI graph, builder image, version tokens, release content tags, cold/cached results
- fork-custom-builds.md: fork CI, image tags, consuming images, builds.txt, a repository holding only custom/
- gha-uv-python-deps-caching.md: uv/CPython/wheel caching in GHA
- gotchas.md: mkosi, repart/disk images, distros, k8s/cloud-init, custom/, build tooling/CI, macOS
- kernel-build.md: version token resolution

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- dangling `default.target -> multi-user.target` broke review tools (ENOENT reading it from the repo)
- drop `default.target`: core postinst already does `systemctl set-default multi-user.target`
- mem: replace `initrd*` `/dev/null` symlinks with `systemctl mask` in a new mem postinst
- core: drop `systemd-firstboot.service` mask; the unit is already removed outright
- docs/gotchas: don't commit symlinks into trait dirs

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
- pass REGISTRY_USERNAME/PASSWORD to the builder as bare -e NAME (value from the env)
- mask them in the DEBUG env table

Assisted-by: Claude Opus 5.5
Signed-off-by: Ricardo Pardini <ricardo@pardini.net>
@rpardini
rpardini force-pushed the pr/flavors-via-traits-UbuntuRocky-k8s-CAPT-imagein-memory-content-hashed-caches-in-OCI-more-powerful-custom branch from 668f67c to a5dd68a Compare November 3, 2026 21:06
@rpardini
rpardini marked this pull request as ready for review November 3, 2026 22:09
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