Accepted shared choices on main. Current evidence and unfinished work belong in STATUS.md; build instructions belong in DEVELOPMENT.md. Detailed investigations and superseded implementations remain in Git history.
Keep this file for accepted shared choices and STATUS for current work, checks,
gaps, and next steps. Long-lived variants store their own records in
docs/<branch>/DECISIONS.md and docs/<branch>/STATUS.md, linking to shared
material. Keep the shared documents aligned with main to reduce merge conflicts.
README is the project overview and user guide; DEVELOPMENT holds the former
README's build/customization instructions. Repository routing is in AGENTS.md.
Clarified by user direction on 2026-10-20: README contains marketing and user help only. Agents may suggest README changes but must obtain explicit approval for the README edit before applying it; general implementation/documentation requests do not grant that approval. Development decisions belong here and customization details in DEVELOPMENT.
Prepare only the custom Brewfile,
including explicitly selected Aurora apps. Include Bazaar before first boot to
avoid its background-install/icon delay. Do not automatically install Aurora's
system, CLI, DX, or font bundles. Keep its inherited Homebrew bootstrap.
This supersedes the earlier default-Flatpak merge and temporary smoke selection.
The full custom list is enabled; ettercap remains removed and ZAP is AMD64-only.
Custom-only payloads use profile=controlthings; retained smoke payloads keep
their original profile and filename. Per-user uv provisioning remains deferred.
Install app stores only in manual disk preparation, not normal OS containers.
The latter ship the manifest and explicit ujust install-controlthings-tools
helper. This replaces the earlier app archives/automatic-seeding approach,
avoiding their cost on every OS update. Existing installs receive list additions
only through the explicit helper; it installs missing apps without removing
unlisted tools or upgrading existing Brew formulae.
Read the manifest/helper from the resolved OS digest, not workflow HEAD.
Populate Brew as a non-root owner and deploy Flatpaks, including extra-data, in a
disposable native stage. Copy only stores and metadata into disk-image /var;
bootc imports these initially and preserves them on upgrades. Disk preparation
alone receives SYS_ADMIN and NET_ADMIN for nested Flatpak/Bubblewrap setup.
Check that sandbox before downloads because Flatpak trigger failures can be warnings.
Prune only this invocation's labelled dangling builder images to recover space;
retain payload ancestors and unrelated storage. Full-size capacity needs validation.
The helper uses the Brew owner and installs the fixed system Flatpak list through
native Flatpak authorization (updated 2026-10-19). The previous pkexec wrapper
forced an unnecessary administrator prompt, bypassing Aurora’s normal policy.
Installing a shell tool does not itself configure it. The removed Gqrx
preinstall declaration can require one explicit reinstall on older development
installs after preinstall reconciliation.
Prepare once per architecture and retain the result in the separate GHCR
controlthingsplatform-disk-payload package under run-RUNID-ATTEMPT-ARCH.
Assembly/retries use its digest, preserving installed versions across both formats.
This supersedes independent preparation per format and the ban on publishing a
prepared derivative; cache images are installer inputs, not OS update channels.
Bind reuse to source OS digest, channel, recipe, platform, and config identity. Verify publication and unchanged base-layer prefixes; reject mismatches without silently preparing again. Only preparation receives package-write access. Registry retention is manual. GHCR avoids a second large Actions image archive; first-run speedup is not promised. Dependency preflight is metadata-only.
Manual disk builds offer independent QCOW2 (default on), ISO, and VMDK
checkboxes as of 2026-10-19, superseding the single format/both selector.
ISO/QCOW2 use native AMD64/ARM64 runners; VMDK targets AMD64 VMware Workstation
and, for latest-channel bundles, VirtualBox. A matching source architecture must
exist; container CI publishes AMD64 only. Use the pinned image-builder CLI and a separate pinned Fedora 44 Anaconda
installer; legacy local Justfile disk recipes remain unmigrated and unbundled.
QCOW2 and VMDK retain Btrfs with a 64 GiB root minimum. VMDK shares the retained
payload and provenance handling. Empty selections and ARM64 VMDK fail before
preparation. Existing CLI preflight both remains an ISO/QCOW2 alias. OVA
packaging is outside this change.
Accepted replacement — 2026-10-21: build monolithicSparse directly and compress the distribution bundle. The pinned builder generates the manifest; customize only its VMDK stage's subformat, compression flag and output filename, then execute OSBuild's VMDK export in that same builder. Preserve the builder's container setup for SELinux and loop devices. Reject unexpected manifest layouts and validate final disk capacity/subformat/extents before packaging. This avoids the second disk conversion, not OSBuild's normal raw-disk assembly or the larger extracted sparse disk. The initial Deflate-6 ZIP64 packaging is superseded by the 7z decision below.
This supersedes the uncommitted 2026-10-20 native-streamOptimized-to-monolithicSparse conversion implementation. Two native builds failed guest writes in VMware; user testing confirmed monolithicSparse works in VMware and VirtualBox. These reports support the subformat choice, not validation of the revised workflow.
Accepted export revision — 2026-10-21: the full-size run 39663118651
completed VMDK assembly but exhausted storage during OSBuild's export copy.
Use a disposable OSBuild store under the mounted output directory and replace
only the standard CLI's export callback with a single-file hard-link export.
Preserve CLI validation, pipeline dependency selection and build failures.
Require exactly one regular VMDK and a new export destination; fail on a
cross-filesystem link rather than copying. Remove the private store on runner
exit before host UUID changes and compression. This avoids duplicate export
allocation and releases build intermediates before packaging. The adapter uses
OSBuild's Python CLI/object-store interfaces; review against the pinned version
when updating the builder. Full-size capacity remains a CI validation item.
The Fedora 44 ISO installer pin was refreshed to an available multiarchitecture index on 2026-10-21 after the prior digest became unavailable. AMD64/ARM64 registry metadata checks do not establish installer build or boot compatibility. Mirroring the installer base into a controlled registry remains a proposal; no mirror or automatic pin refresh is implemented.
Latest-channel bundles include exactly the versioned VMDK plus the fixed names
controlthingsplatform.vmx and controlthingsplatform.vbox; branch bundles omit
VBOX. Dated source tags follow their originating channel. Generate configuration
copies from the supplied templates, preserving all settings except the approved
fields: VMX display name/disk basename; VBOX disk basename, matching media UUIDs
and a fresh machine UUID per build. VMX display names are ControlThings Platform
for latest and UPPERCASE_BRANCH - ControlThings Platform otherwise. Initialize
VirtualBox's ddb.uuid.image in reserved VMDK descriptor space if absent, before
checksumming. Do not confuse it with a filesystem UUID or VMware's disk UUID.
Repository templates require explicit approval before edits; placeholders are
unnecessary. The supplied VBOX template's fixed machine UUID stays in the source;
each generated copy replaces it.
Accepted packaging revision — 2026-10-21: VMDK distribution must use .7z,
a user requirement for the destination environment, with no build-time format
option. Use one prebuilt 7z/LZMA2 archive for GitHub and S3. The pinned
upload-artifact action supports archive: false for direct single-file uploads.
Accept both standalone 7zz and distribution 7z executable names; check
availability before VMDK assembly. Ubuntu 24.04's 7zip package provides 7z.
Keep the existing VM membership and disk format. Implementation defaults are
level 5, a 32 MiB dictionary, two threads, and non-solid mode to limit resource
costs and allow configurations to extract independently of the large disk.
These settings are not a measured optimum. Hard-link the disk into temporary
sibling staging to avoid copying it; failed packaging removes partial archives.
JSON provenance/manifest and disk/bundle SHA-256 files remain separate: a metadata
ZIP artifact on GitHub, sibling objects on S3. Keep the unpacked VMDK locally
but exclude it from VMDK S3 uploads. Bundle names include CI run/attempt to prevent
collisions. Checksums describe final bytes after media UUID initialization.
QCOW2 uploads directly to GitHub with archive: false; move its checksums,
provenance and manifest into a separate metadata ZIP. This removes the existing
uncompressed ZIP wrapper without adding a compression step to the disk. Append
the run/attempt to CI QCOW2 filenames because direct artifact names come from
the uploaded filename. Local QCOW2 names and S3's individual-file layout remain.
ISO packaging remains unchanged. This supersedes the earlier VMDK ZIP decision
and combined QCOW2 image/metadata artifact. See
packaging details.
Resolve the source once. Derive filename date/hash from its version label and
record actual digests because same-day tags may be reused and revision labels
may be inherited. Embed/install the local prepared payload; installed systems
follow the originating OS channel. Preserve ics410_l01 and ics410-l01 as
distinct spellings. Never silently pull a different source during assembly.
Keep each format in its own GitHub artifact(s), with checksums, source metadata, and manifests. Optional S3 paths include run/attempt/architecture/format to prevent overwrites. See disk instructions.
Accepted VM template default — 2026-10-21: use two vCPUs in both supplied
configurations. VMware uses one virtual socket with two cores (numvcpus = "2",
cpuid.coresPerSocket = "2"); VirtualBox retains its existing CPU count of two.
This is a starting point for desktop responsiveness on student laptops, not a
measured performance claim. Lower-end Windows 11 host testing remains pending.
Download the latest stable official release ZIP at build time, verify its
published SHA-256, reject unsafe paths/symlinks, and install the complete offline
bundle with matching license/icon and release identity. Keep a stable
/usr/share/cyberchef/index.html link. Install Firefox as an RPM and launch
CyberChef explicitly in Firefox (2026-10-17), superseding xdg-open.
Refresh CyberChef in its own build step so freshness does not invalidate RPM installation. Normalize file timestamps to release publication time; record no build timestamp. Latest-release selection can change across builds; pinning for classrooms remains a proposal. See build details.
Accepted: authenticate CyberChef release metadata requests using the Actions
job's temporary github.token, passed via a private runner file and Podman build
secret. Delete the runner file on exit; never persist the token in image layers
or pass it as a build argument. Local token files are optional. Scope credentials
to https://api.github.com, reject API redirects, and bound HTTP retries/backoff.
The API helper can be reused by variant installers; main has no workbook setup.
This addresses the anonymous API rate-limit failure observed in course run
39346663938. Latest release selection and checksum verification remain intact.
Extend the bounded retry policy to CyberChef ZIP/license downloads and transient
network failures, including truncated transfers. Keep those requests anonymous
and retain checksum verification. Authenticate eligible jobs before image pulls;
rootful Podman receives an explicit temporary auth file, cleaned up with
always(). Preserve Docker authentication for signing and existing branch/PR
conditions. See build instructions.
Use one shared root script for a versioned boot oneshot and the explicit
ujust controlthings-groups command. Automatically add all current wheel members
to wireshark and usbmon; leave setup pending without wheel users and mark
success only after all changes succeed. Preserve existing memberships and vendor
GIDs. Manual --user setup bypasses the marker for later accounts.
This replaces the useradd-hook proposal. Dialout stays with Aurora's DX tooling; module loading and device-permission policy are outside this feature. Actual capture permissions still require hardware validation.
Seed .config/no-show-user-motd through /etc/skel so new users start with Aurora's
MOTD disabled and can enable it with ujust toggle-user-motd. Keep upstream hooks,
existing user preferences, and the custom fish Macchina display. Automatic-update
defaults on main remain inherited; course policy belongs in course records.
Use CT-De-Machina-Logica-Programmabilis for new desktops. Replace Aurora's dark
and light Folder View creation hooks with one script because those hooks override
ordinary theme wallpaper settings. Use the wallpaper package for screen-size
selection, preserve saved desktops, and fail the build if expected hook directories
are absent. Login-screen and installer branding are separate work.
Use CT-Aquila-Aurea-Machina through Aurora's system-default KDE profile, superseding
the initial assumption that it should match the desktop. User preferences take
precedence. Keep authentication, lock timing, and the login screen unchanged.
Accepted: use CT-Aquila-Aurea-Machina for the Plasma Login Manager login screen.
The user confirmed Aurora's plasma-login-manager-6.7.5-1.fc44.x86_64 uses
/usr/lib/plasmalogin/defaults.conf with the image wallpaper plugin; their local
/etc/plasmalogin.conf has no wallpaper override. SDDM theme configuration does
not apply to this deployed login manager.
Update only the distribution's Image and PreviewImage keys using KConfig's
writer, preserving other inherited settings and administrator overrides under
/etc. Select the existing wallpaper package instead of duplicating images.
Fail the build if the defaults file or required wallpaper assets are missing.
This supersedes the previous choice to leave login-screen branding unchanged;
desktop and lock-screen configuration retain their separate mechanisms. See
configuration and validation.
Use pinned Chunkah in CI, matching Aurora's 256-layer packing approach. Transfer configuration from the exact input tag, prune embedded OSTree data/stale labels, import before retagging, clean temporary files, and lint the result. Renovate maintains the pin; the legacy rechunker remains for explicit comparisons. This does not establish smaller downloads or eliminate RPM/DNF state churn.
Disable countme only for build package operations and remove regular per-repository countme cookies before rechunking. Two tiny cookies were observed invalidating a 409.844 MiB layer. Preserve RPM inventory, transaction history, neighboring state, and installed-system repository settings. Reproducible RPM/DNF state and timestamp normalization remain proposals requiring controlled measurements.
Measured evidence, not an accepted normalization implementation. The two images
labelled latest.20261020-0ae7fc9 are distinct builds:
- Old:
sha256:4c460c7f45deba8dbcf179bfd67ae9287de56bd906aca0358d657a22e20b46a0, created label2026-10-20T01:30:23Z, run 39353575656. - New:
sha256:3b69198f1f0db6fcd22e5f2c6f1b09237e9eb726ca7b48948a581f0b8a10e5db, created label2026-10-20T01:46:06Z, run 39355159134.
Both contain 256 layers; 232 compressed digests match. The 24 replacement layers
sum to 604,756,457 bytes (604.756 MB / 576.741 MiB). Complete compressed layer
totals are 6,690,270,731 and 6,690,271,043 bytes: growth is only 312 bytes.
This measures replacement registry blobs, not captured client network traffic or
installed-size growth. Both logs pulled the same 256 Aurora blobs and show base
image ID 23edd628d828, identical 12-package transactions, CyberChef v11.5.0 with
archive SHA-256 f6478925d3eaa16ec08626a85f5b85eed10d531a5e18700615fed7ecb024cccf,
and the same pinned Chunkah. Layer component grouping is identical.
All 48 old/new replacement blobs were downloaded and their compressed SHA-256 verified. Compared every member's payload hash, path/type/link target, size, mtime, ownership, mode, and PAX attributes. Only mtimes and five database-related file payloads differ; no added/removed paths or package/application payload changes were found. Of 848 distinct changed paths, 255 are directories, 2 symlinks, and 591 regular files; 586 regular files differ only by mtime, including 460 samples.
| Replacement layer group | Layers | New compressed bytes | Observed cause |
|---|---|---|---|
| Large sample documents/captures | 10 | 163,902,725 | Mtimes only |
| Wallpapers | 6 | 102,959,357 | Mtimes only |
| Mixed sample/application components | 4 | 185,575,314 | Mtimes only |
| Mixed RPM components | 1 | 16,071,566 | Mtimes only, including directories and Plasma hooks |
| RPM database | 1 | 53,554,539 | Install-time fields plus mtimes |
| Unclaimed content | 1 | 78,048,530 | RPM SQLite shared-memory file plus mtimes |
| DNF history | 1 | 4,644,426 | Transaction times and SQLite state |
The 21 mtime-only layers account for 468,508,962 bytes (77.47%). Git checkout
mtimes enter via COPY system_files and cp -avf; the same large SANS PDF has
mtime 01:28:16Z versus 01:45:23Z. install and package hooks also generate
new mtimes. Parent directory headers recur across packed layers, so normalization
must cover directories as well as leaf files. Unchanged sample/asset bytes make
entire gzip blobs different through their tar headers.
The other three layers account for 136,247,495 bytes. Both RPM databases contain
exactly the same 2,599 package identities. Comparing all changed package headers
finds exactly 12 headers differing only in INSTALLTIME (1008) and INSTALLTID
(1128). DNF's logical tables differ only in the transaction start/end timestamps;
its SQLite main/WAL/SHM bytes differ. The fifth changed payload is RPM's SHM file.
There are no countme cookies in either unclaimed layer; the earlier cleanup is
working, but does not eliminate these independent causes. The sole changed image
config label is org.opencontainers.image.created; config/history metadata alone
cannot explain the 24 changed filesystem diff IDs.
Deferred proposals: controlled deterministic-mtime experiments covering copied
assets, generated files, and directory headers, followed by separate database
component isolation/cleanup experiments. Preserve RPM inventory and DNF history;
do not delete databases as a size workaround. Chunkah documents
SOURCE_DATE_EPOCH, but exact pinned
behavior and interaction with inherited layers must be tested. A commit-wide
epoch can stabilize same-commit rebuilds yet change unchanged assets at every
new commit. Test both identical-input rebuilds and a small subsequent source edit.
The 468.509 MB is the measured timestamp-only portion of this pair, not a proven
future saving; the first normalization deployment can itself replace layers.
Accepted user direction: defer timestamp normalization and related layer-size optimizations. Keep the existing build behavior while reviewing upstream progress when this work is revisited. This records a project watch list; no scheduled monitoring or GitHub notification subscription has been configured.
Watch:
- Aurora PR #3028, merged 2026-10-16: filesystem timestamp normalization and non-RPM component handling.
- Aurora reproducibility tracker #2579 and update-size reporting #2810: measured improvements, remaining causes, and regressions.
- Chunkah #160: independent timestamp clamping without changing layer planning or image creation time.
Revisit when Aurora’s relevant stable images include the changes and useful before/after results or operational experience are available, or when the user prioritizes this work. Upstream normalization does not automatically cover files modified afterward by our customization steps. Any future local change still needs identical-input and small-source-edit comparisons; database state remains separate from filesystem timestamps. No implementation approach is accepted yet.
Schedule Saturdays at 23:00 UTC (0 23 * * 6) instead of daily builds to reduce
scheduled rebuild frequency. Preserve push, pull-request, and manual triggers;
GitHub scheduling may be delayed.
Apply an app-specific X11 socket override after Flatpak installation, only when ZAP is selected. Disk payload preparation applies it system-wide. As of 2026-10-19, the explicit installer applies it per user so it can run without elevating the helper; this supersedes its original system-wide behavior. The disk override covers all accounts without configuring the build account. ARM64 skips it because ZAP is excluded. Existing installs need the command documented in README or the updated installer; OS updates preserve the mutable Flatpak store.
Accepted: main starts Firefox at https://www.controlthings.io and provides one
row of six pinned shortcuts in the user's requested order: ControlThings Website,
ControlThings Discord, ICS410, SANS ICS, CS2AI, and Fedora Project. Widgets, stories,
and Support Firefox/sponsored content default off. Shortcuts use url and label
objects in the default browser.newtabpage.pinned value.
Use an unlocked homepage policy and default-status preferences in Firefox's RPM
application distribution directory under /usr. Avoid prebuilt skeleton profiles
and persistent user.js files; users may change startup behavior, section toggles,
and pins, with saved personal values taking precedence. Initial defaults are
sufficient; maintaining/updating users' customized lists is not required. Existing
profiles with no override can also receive these defaults. Changing only the L01
homepage to localhost is intended later, not part of this main-branch change.
Configuration/build and verification details are in DEVELOPMENT; user controls are in README.