Every arm64 Ubuntu CI image build failed twice on 2026-11-11 (14:11 to 14:34 and 15:58 to 16:18 UTC) with Could not resolve 'ports.ubuntu.com' (PR #1477, job 130634835861). apt-mirrors.sh rewrote only the archive.ubuntu.com and security.ubuntu.com sources to the mirror list; arm64 images point at ports.ubuntu.com/ubuntu-ports, which the archive mirrors do not carry, so ports.ubuntu.com stayed their single source. INFINITO_APT_UBUNTU_PORTS_MIRRORS now lists ports.ubuntu.com, then ftp.tu-chemnitz.de and mirrors.dotsrc.org (both official Launchpad mirrors carrying ubuntu-ports with resolute and resolute-security, in sync with ports.ubuntu.com). apt-mirrors.sh points the ports sources at that list the same way the archive sources use theirs, the image build passes the key through, and the external mirror test checks both lists for speed, freshness and officiality. 🎯 Fix: 92 % 💥 Break: 3 % 💸 Cost: – ⏱️ Time: – 🛡️ Security: 75 % 🏆 Score: 67 🚦 Gate: ⚠️ security 75 % < 100: ask the operator before the commit Security 75 % comes from Trivy DS-0002 (image user should not be root) on the root Dockerfile, present on main before this change, which touches no USER line; the finding carries no line, so security.py cannot accept it. Committed on the operator's word with that cause named. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
utils/env 📐
This directory hosts the Python implementation behind make dotenv.
For the general rules that govern READMEs in code directories see documentation.md.
For the per-variable defaults that this code consumes see default.env.
Purpose 🎯
The package converts the committed default.env plus a small runtime context into the gitignored .env at the repo root.
Every module here belongs to one of four roles: parse, resolve, orchestrate, write.
Structure 🗂️
| File | Role |
|---|---|
parser.py |
Read default.env into (values, comments) tuples. |
runtime.py |
Resolve host-context lookups (disk, RAM, hostname, GHA/Act flags, /proc/version, helper-script invocation). |
builder.py |
Thin orchestrator that defines EnvBuilder, BuildContext, and build_env(). Walks the handler registry. |
writer.py |
Serialise an EnvBuilder into a docker-compose-compatible .env on disk. |
handlers/ |
One module per dynamically-computed variable. See handlers README. |
The CLI entry point lives at __main__.py under cli/meta/env/.
Shell consumers source load.sh under scripts/meta/env/.
File Naming 🏷️
- Module names MUST stay lowercase snake_case.
- Pure passthrough or simple parser/writer helpers MUST live directly under
utils/env/. - Per-variable computation MUST live under
handlers/(see handlers README for the naming convention used there). - Test files live in
tests/unit/python/utils/env/and mirror the module name with atest_prefix.
Machine Facts vs Run Facts 🧭
The writer a handler picks decides how long a value lives. Both kinds are registered in default.env and both are visible to the lints; they differ only in whether the generated .env remembers what the environment said.
| Writer | Meaning | On the next make dotenv |
|---|---|---|
eb.setdefault |
machine fact | a non-empty value in the process env is adopted and written back |
eb.set |
run fact | recomputed from BuildContext; whatever the process env said is discarded |
setdefault is the operator-override contract: exporting a key changes it and the change persists. That persistence is why it MUST NOT carry a value an environment declares for a single run. Makefile exports BASH_ENV, so every make recipe sources .env — a value adopted once is re-exported into every later recipe and into every container that mounts the repo, and a per-run decision has silently become a property of the machine.
A key that some environment declares per run therefore MUST be written by a dedicated handler through eb.set. The exporting shell still wins for its own run, because load.sh preserves non-empty caller values when it sources .env; the file keeps the derived default. INFINITO_RUNNING_ON_GITHUB, INFINITO_CACHE_STACK and INFINITO_GHCR_MIRROR are written this way. Note the consequence: for those keys .env and the running shell can legitimately disagree, and .env is not the authority.
Import Rules 🔗
parser.py,runtime.py, andwriter.pyMUST NOT import frombuilder.pyorhandlers/.builder.pyMUST NOT import individual handler modules; it MUST go through theORDERED_HANDLERSlist exposed byhandlers/__init__.py.- Handler modules MUST NOT import each other (see handlers README).