diff --git a/.github/workflows/test-environment.yml b/.github/workflows/test-environment.yml index bcfad61ebe..2110cd3017 100644 --- a/.github/workflows/test-environment.yml +++ b/.github/workflows/test-environment.yml @@ -37,13 +37,13 @@ jobs: # post-prune in-run re-pulls: large enough to fit one deploy's # in-use image set (rpardini) plus pip/npm/apt artefacts (Nexus), # leaving ~62g headroom for Docker layer storage on /mnt. - INFINITO_REGISTRY_CACHE_MAX_SIZE: 8g - INFINITO_REGISTRY_CACHE_HOST_PATH: /mnt/cache/registry/mirror - INFINITO_REGISTRY_CACHE_CA_HOST_PATH: /mnt/cache/registry/ca - INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX: 4g - INFINITO_PACKAGE_CACHE_HEAP: 1g - INFINITO_PACKAGE_CACHE_DIRECT_MEM: 1g - INFINITO_PACKAGE_CACHE_HOST_PATH: /mnt/cache/package/data + INFINITO_CACHE_REGISTRY_MAX_SIZE: 8g + INFINITO_CACHE_REGISTRY_HOST_PATH: /mnt/cache/registry/mirror + INFINITO_CACHE_REGISTRY_CA_HOST_PATH: /mnt/cache/registry/ca + INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX: 4g + INFINITO_CACHE_PACKAGE_HEAP: 1g + INFINITO_CACHE_PACKAGE_DIRECT_MEM: 1g + INFINITO_CACHE_PACKAGE_HOST_PATH: /mnt/cache/package/data steps: - name: Checkout repository @@ -69,13 +69,13 @@ jobs: -e INFINITO_PULL_POLICY=never \ -e INFINITO_NO_BUILD=0 \ -e NIX_CONFIG \ - -e INFINITO_REGISTRY_CACHE_MAX_SIZE \ - -e INFINITO_REGISTRY_CACHE_HOST_PATH \ - -e INFINITO_REGISTRY_CACHE_CA_HOST_PATH \ - -e INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX \ - -e INFINITO_PACKAGE_CACHE_HEAP \ - -e INFINITO_PACKAGE_CACHE_DIRECT_MEM \ - -e INFINITO_PACKAGE_CACHE_HOST_PATH \ + -e INFINITO_CACHE_REGISTRY_MAX_SIZE \ + -e INFINITO_CACHE_REGISTRY_HOST_PATH \ + -e INFINITO_CACHE_REGISTRY_CA_HOST_PATH \ + -e INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX \ + -e INFINITO_CACHE_PACKAGE_HEAP \ + -e INFINITO_CACHE_PACKAGE_DIRECT_MEM \ + -e INFINITO_CACHE_PACKAGE_HOST_PATH \ --network host \ -v "${HOST_WORKDIR}:${HOST_WORKDIR}" \ -v "/mnt/docker:/var/lib/docker" \ diff --git a/cli/administration/deploy/container/command.py b/cli/administration/deploy/container/command.py index 32b84ea8ba..175d408c18 100644 --- a/cli/administration/deploy/container/command.py +++ b/cli/administration/deploy/container/command.py @@ -377,9 +377,7 @@ def main() -> int: help="Container mode: run, start, stop, exec, remove.", ) - parser.add_argument( - "--image", default=os.environ.get("INFINITO_IMAGE", "infinito:latest") - ) + parser.add_argument("--image", default=os.environ.get("INFINITO_IMAGE")) parser.add_argument("--build", action="store_true") parser.add_argument("--rebuild", action="store_true") parser.add_argument("--no-cache", action="store_true") diff --git a/cli/administration/deploy/development/common.py b/cli/administration/deploy/development/common.py index 41896c9bef..48fd66509c 100644 --- a/cli/administration/deploy/development/common.py +++ b/cli/administration/deploy/development/common.py @@ -30,32 +30,9 @@ def compose_file_args() -> list[str]: return out -def cache_env_overrides() -> dict[str, str]: - """Env vars consumed strictly by compose/cache.override.yml.""" - from .profile import Profile - - if not Profile().registry_cache_active(): - return {} - - ca_dir = os.environ.get("INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR", "").strip() - if not ca_dir: - raise SystemExit( - "INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR is not set. Run " - "'make dotenv' (or source scripts/meta/env/load.sh) before " - "invoking cli.administration.deploy.development." - ) - return { - "INFINITO_REGISTRY_CACHE_PROXY_CONF": "./compose/registry-cache/proxy.conf", - "INFINITO_PACKAGE_CACHE_PIP_CONF": "./compose/package-cache/pip.conf", - "INFINITO_PACKAGE_CACHE_NPMRC": "./compose/package-cache/npmrc", - "INFINITO_PACKAGE_CACHE_APT_LIST": "./compose/package-cache/apt.list", - "INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE": f"{ca_dir}/ca.crt", - } - - def resolve_distro() -> str: """Return INFINITO_DISTRO; raise SystemExit if missing or invalid.""" - distro = os.environ.get("INFINITO_DISTRO", "").strip() + distro = os.environ["INFINITO_DISTRO"].strip() if not distro: raise SystemExit( "INFINITO_DISTRO is not set. Run 'make dotenv' (or source scripts/meta/env/load.sh) " @@ -72,7 +49,7 @@ def resolve_distro() -> str: def resolve_container() -> str: """Return INFINITO_CONTAINER; raise SystemExit if unset.""" - container = os.environ.get("INFINITO_CONTAINER", "").strip() + container = os.environ["INFINITO_CONTAINER"].strip() if not container: raise SystemExit( "INFINITO_CONTAINER is not set. Run 'make dotenv' (or source scripts/meta/env/load.sh) " diff --git a/cli/administration/deploy/development/compose.py b/cli/administration/deploy/development/compose.py index 6dead7fd32..bf707c8ff0 100644 --- a/cli/administration/deploy/development/compose.py +++ b/cli/administration/deploy/development/compose.py @@ -5,7 +5,7 @@ import subprocess import time from typing import TYPE_CHECKING -from .common import cache_env_overrides, compose_file_args +from .common import compose_file_args from .coredns import CoreDNSCorefileRenderer from .network import detect_outer_network_mtu from .proc import run_streaming @@ -42,7 +42,6 @@ class Compose: check=True, ) env["INFINITO_IMAGE"] = result.stdout.strip() - env.update(cache_env_overrides()) return env def run( @@ -192,7 +191,7 @@ class Compose: print(">>> env:", {k: env.get(k) for k in keys}) print(">>> NIX_CONFIG:", "" if env.get("NIX_CONFIG") else "") - no_build = env.get("INFINITO_NO_BUILD", "0") == "1" + no_build = env["INFINITO_NO_BUILD"] == "1" # Compose env-file precedence: later files override earlier ones. args = ["--env-file", "env/ci.env"] diff --git a/cli/administration/deploy/development/deploy.py b/cli/administration/deploy/development/deploy.py index d598e1d512..ca92028eec 100644 --- a/cli/administration/deploy/development/deploy.py +++ b/cli/administration/deploy/development/deploy.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: def _env_variant() -> int | None: - raw = os.environ.get("INFINITO_VARIANT", "").strip() + raw = os.environ["INFINITO_VARIANT"].strip() if not raw: return None try: @@ -35,7 +35,7 @@ def _env_variant() -> int | None: def _env_full_cycle() -> bool: - return os.environ.get("INFINITO_FULL_CYCLE", "").strip().lower() == "true" + return os.environ["INFINITO_FULL_CYCLE"].strip().lower() == "true" def _run_deploy( @@ -84,7 +84,7 @@ def _run_deploy( "PY_COLORS": "1", "TERM": "xterm-256color", } - services_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "") + services_disabled = os.environ["INFINITO_SERVICES_DISABLED"] if services_disabled: extra_env["INFINITO_SERVICES_DISABLED"] = services_disabled @@ -216,7 +216,7 @@ def handler(args: argparse.Namespace) -> int: # Remove any app IDs that were disabled via INFINITO_SERVICES_DISABLED so the # deploy list stays consistent with the inventory created by init. - raw_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip() + raw_disabled = os.environ["INFINITO_SERVICES_DISABLED"].strip() disabled_app_ids: set[str] = set() if raw_disabled: services = parse_services_disabled(raw_disabled) diff --git a/cli/administration/deploy/development/down.py b/cli/administration/deploy/development/down.py index efe6b2f1bc..13b4bd3590 100644 --- a/cli/administration/deploy/development/down.py +++ b/cli/administration/deploy/development/down.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import TYPE_CHECKING from . import PROJECT_ROOT -from .common import cache_env_overrides, compose_file_args, resolve_distro +from .common import compose_file_args, resolve_distro from .profile import Profile if TYPE_CHECKING: @@ -20,7 +20,6 @@ CI_DOCKER_ROOT_STR = str(CI_DOCKER_ROOT) def _base_env(*, distro: str) -> dict[str, str]: env = dict(os.environ) env["INFINITO_DISTRO"] = distro - env.update(cache_env_overrides()) return env @@ -45,7 +44,7 @@ def _cleanup_docker_root() -> None: print(f">>> Not on GitHub - No bind volumes will be deleted: {docker_root}") return - docker_root_env = os.environ.get("INFINITO_DOCKER_VOLUME", "").strip().rstrip("/") + docker_root_env = os.environ["INFINITO_DOCKER_VOLUME"].strip().rstrip("/") if docker_root_env and docker_root_env != CI_DOCKER_ROOT_STR: raise RuntimeError( diff --git a/cli/administration/deploy/development/exec.py b/cli/administration/deploy/development/exec.py index 246501b2ba..80cf299c9b 100644 --- a/cli/administration/deploy/development/exec.py +++ b/cli/administration/deploy/development/exec.py @@ -51,7 +51,7 @@ def handler(args: argparse.Namespace) -> int: raise SystemExit("exec requires a command (e.g. exec -- sh -lc 'whoami')") extra_env: dict[str, str] = {} - services_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "") + services_disabled = os.environ["INFINITO_SERVICES_DISABLED"] if services_disabled: extra_env["INFINITO_SERVICES_DISABLED"] = services_disabled diff --git a/cli/administration/deploy/development/init.py b/cli/administration/deploy/development/init.py index 01c09e3196..1e69db792b 100644 --- a/cli/administration/deploy/development/init.py +++ b/cli/administration/deploy/development/init.py @@ -82,7 +82,7 @@ def add_parser(sub: argparse._SubParsersAction) -> None: def _env_variant() -> int | None: - raw = os.environ.get("INFINITO_VARIANT", "").strip() + raw = os.environ["INFINITO_VARIANT"].strip() if not raw: return None try: @@ -117,7 +117,7 @@ def handler(args: argparse.Namespace) -> int: # the inventory step strip it back out — leaving the round's # include list inconsistent with what the inventory actually # contains. - raw_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip() + raw_disabled = os.environ["INFINITO_SERVICES_DISABLED"].strip() disabled_app_ids: set[str] = set() if raw_disabled: services = parse_services_disabled(raw_disabled) @@ -159,7 +159,7 @@ def handler(args: argparse.Namespace) -> int: raise SystemExit(f"--variant: {exc}") from exc runtime = os.environ.get("RUNTIME") or detect_runtime() - services_disabled = os.environ.get("INFINITO_SERVICES_DISABLED", "") + services_disabled = os.environ["INFINITO_SERVICES_DISABLED"] for _round_index, inv_dir, round_variants, include_R, _purge_set in plan: round_include = tuple( role for role in include_R if role not in disabled_app_ids diff --git a/cli/administration/deploy/development/network.py b/cli/administration/deploy/development/network.py index df7ed7e715..ac6078f3e5 100644 --- a/cli/administration/deploy/development/network.py +++ b/cli/administration/deploy/development/network.py @@ -19,7 +19,7 @@ def detect_outer_network_mtu( daemon_config_path: Path = DOCKER_DAEMON_CONFIG_PATH, ) -> str | None: current_env = os.environ if env is None else env - explicit_mtu = current_env.get("INFINITO_OUTER_NETWORK_MTU", "").strip() + explicit_mtu = current_env["INFINITO_OUTER_NETWORK_MTU"].strip() if explicit_mtu: return explicit_mtu diff --git a/cli/administration/deploy/development/up.py b/cli/administration/deploy/development/up.py index 35c35dff99..cb684ad7af 100644 --- a/cli/administration/deploy/development/up.py +++ b/cli/administration/deploy/development/up.py @@ -24,7 +24,7 @@ def add_parser(sub: argparse._SubParsersAction) -> None: def _maybe_build_missing() -> int: # CI: never build (image is pulled). Local: build if missing. - if os.environ.get("INFINITO_NO_BUILD", "0") == "1": + if os.environ["INFINITO_NO_BUILD"] == "1": return 0 build_args = argparse.Namespace( diff --git a/cli/administration/inventory/provision/services_disabler.py b/cli/administration/inventory/provision/services_disabler.py index 04bf4b66d3..93852d1f3c 100644 --- a/cli/administration/inventory/provision/services_disabler.py +++ b/cli/administration/inventory/provision/services_disabler.py @@ -235,7 +235,7 @@ def apply_services_disabled_from_env( inventory_file: Path | None = None, ) -> None: """Read INFINITO_SERVICES_DISABLED from the environment and apply to host_vars and inventory.""" - raw = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip() + raw = os.environ["INFINITO_SERVICES_DISABLED"].strip() if not raw: return services = parse_services_disabled(raw) @@ -326,7 +326,7 @@ def assert_services_disabled_inventory_consistency_from_env( roles_dir: Path, ) -> None: """Fail fast when INFINITO_SERVICES_DISABLED conflicts with the existing inventory.""" - raw = os.environ.get("INFINITO_SERVICES_DISABLED", "").strip() + raw = os.environ["INFINITO_SERVICES_DISABLED"].strip() if not raw: return diff --git a/compose.yml b/compose.yml index e515e979ec..d56b9d6aac 100644 --- a/compose.yml +++ b/compose.yml @@ -65,7 +65,8 @@ services: volumes: - /sys/fs/cgroup:/sys/fs/cgroup:rw - .:/opt/src/infinito - - ${INFINITO_DOCKER_VOLUME:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_DOCKER_MOUNT:?Run 'make dotenv' to generate the .env single source of truth} + - ${INFINITO_DOCKER_VOLUME:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_DOCKER_MOUNT:?Run 'make dotenv' to generate the .env + single source of truth} - infinito:/var/lib/infinito/ - letsencrypt:/etc/letsencrypt/ diff --git a/compose/cache.override.yml b/compose/cache.override.yml index 15c476806f..a87cd279fc 100644 --- a/compose/cache.override.yml +++ b/compose/cache.override.yml @@ -9,17 +9,17 @@ services: environment: ENABLE_MANIFEST_CACHE: "true" AUTH_REGISTRIES: "" - CACHE_MAX_SIZE: "${INFINITO_REGISTRY_CACHE_MAX_SIZE:?Run 'make dotenv' to generate the .env single source of truth}" + CACHE_MAX_SIZE: "${INFINITO_CACHE_REGISTRY_MAX_SIZE:?Run 'make dotenv' to generate the .env single source of truth}" # Avoid 502s on hosts with broken IPv6 outbound. DISABLE_IPV6: "true" volumes: - type: bind - source: ${INFINITO_REGISTRY_CACHE_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} + source: ${INFINITO_CACHE_REGISTRY_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} target: /docker_mirror_cache bind: create_host_path: true - type: bind - source: ${INFINITO_REGISTRY_CACHE_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} + source: ${INFINITO_CACHE_REGISTRY_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} target: /ca bind: create_host_path: true @@ -39,19 +39,19 @@ services: # Run as root: fresh bind-mount is root:root and Nexus aborts otherwise. user: "0:0" environment: - INSTALL4J_ADD_VM_PARAMS: "-Xms${INFINITO_PACKAGE_CACHE_HEAP:?Run 'make dotenv' to generate the .env single source of truth} -Xmx${INFINITO_PACKAGE_CACHE_HEAP:?Run - 'make dotenv' to generate the .env single source of truth} -XX:MaxDirectMemorySize=${INFINITO_PACKAGE_CACHE_DIRECT_MEM:?Run 'make dotenv' to generate the + INSTALL4J_ADD_VM_PARAMS: "-Xms${INFINITO_CACHE_PACKAGE_HEAP:?Run 'make dotenv' to generate the .env single source of truth} -Xmx${INFINITO_CACHE_PACKAGE_HEAP:?Run + 'make dotenv' to generate the .env single source of truth} -XX:MaxDirectMemorySize=${INFINITO_CACHE_PACKAGE_DIRECT_MEM:?Run 'make dotenv' to generate the .env single source of truth}" # Random-password mode leaves no readable secret for the bootstrap. NEXUS_SECURITY_RANDOMPASSWORD: "false" volumes: - type: bind - source: ${INFINITO_PACKAGE_CACHE_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} + source: ${INFINITO_CACHE_PACKAGE_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} target: /nexus-data bind: create_host_path: true ports: - - "${INFINITO_BIND_IP:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_PACKAGE_CACHE_PORT:?Run 'make dotenv' to generate the .env + - "${INFINITO_BIND_IP:?Run 'make dotenv' to generate the .env single source of truth}:${INFINITO_CACHE_PACKAGE_PORT:?Run 'make dotenv' to generate the .env single source of truth}:8081" healthcheck: test: @@ -71,13 +71,13 @@ services: condition: service_healthy volumes: - type: bind - source: ${INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR:?Run 'make dotenv' to generate the .env single source of truth} + source: ${INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR:?Run 'make dotenv' to generate the .env single source of truth} target: /etc/nginx/ca read_only: true bind: create_host_path: true - type: bind - source: ${INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR:?Run 'make dotenv' to generate the .env single source of truth} + source: ${INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR:?Run 'make dotenv' to generate the .env single source of truth} target: /etc/nginx/certs read_only: true bind: @@ -85,7 +85,7 @@ services: - ./compose/package-cache-frontend/upstreams.conf:/etc/nginx/conf.d/upstreams.conf:ro networks: default: - ipv4_address: ${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth} + ipv4_address: ${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth} healthcheck: test: - CMD-SHELL @@ -108,32 +108,32 @@ services: condition: service_healthy required: false environment: - INFINITO_PACKAGE_CACHE_FRONTEND_IP: ${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth} + INFINITO_CACHE_PACKAGE_FRONTEND_IP: ${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth} extra_hosts: - - "pypi.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "files.pythonhosted.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "registry.npmjs.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "rubygems.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "index.rubygems.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "repo.packagist.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "proxy.golang.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "dl-cdn.alpinelinux.org:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "raw.githubusercontent.com:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" - - "codeload.github.com:${INFINITO_PACKAGE_CACHE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "pypi.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "files.pythonhosted.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "registry.npmjs.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "rubygems.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "index.rubygems.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "repo.packagist.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "proxy.golang.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "dl-cdn.alpinelinux.org:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "raw.githubusercontent.com:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" + - "codeload.github.com:${INFINITO_CACHE_PACKAGE_FRONTEND_IP:?Run 'make dotenv' to generate the .env single source of truth}" volumes: - type: bind - source: ${INFINITO_REGISTRY_CACHE_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} + source: ${INFINITO_CACHE_REGISTRY_CA_HOST_PATH:?Run 'make dotenv' to generate the .env single source of truth} target: /opt/registry-cache-ca read_only: true bind: create_host_path: true - - ${INFINITO_REGISTRY_CACHE_PROXY_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make + - ${INFINITO_CACHE_REGISTRY_PROXY_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/etc/systemd/system/docker.service.d/registry-cache-proxy.conf:ro - ./scripts/docker/cache/registry-ca.sh:/usr/local/bin/registry-cache-ca.sh:ro - - ${INFINITO_PACKAGE_CACHE_PIP_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/etc/pip.conf:ro - - ${INFINITO_PACKAGE_CACHE_NPMRC:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/root/.npmrc:ro - - ${INFINITO_PACKAGE_CACHE_APT_LIST:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make + - ${INFINITO_CACHE_PACKAGE_PIP_CONF:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/etc/pip.conf:ro + - ${INFINITO_CACHE_PACKAGE_NPMRC:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/root/.npmrc:ro + - ${INFINITO_CACHE_PACKAGE_APT_LIST:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/etc/apt/sources.list.d/package-cache.list:ro - - ${INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make + - ${INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE:?Set by 'cli deploy development up' / 'make up' (driver-injected, not produced by 'make dotenv')}:/opt/package-frontend-ca.crt:ro - ./scripts/docker/cache/package-frontend-ca.sh:/usr/local/bin/package-frontend-ca.sh:ro diff --git a/docs/contributing/artefact/files/compose.yml.md b/docs/contributing/artefact/files/compose.yml.md index 090488cf76..9a82d92864 100644 --- a/docs/contributing/artefact/files/compose.yml.md +++ b/docs/contributing/artefact/files/compose.yml.md @@ -66,24 +66,24 @@ The env-var contracts each service expects strictly via `${VAR:?…}` (consumed | Variable | Purpose | |---|---| -| `INFINITO_REGISTRY_CACHE_HOST_PATH` | Host path bind-mounted into `registry-cache` for blob/manifest persistence. | -| `INFINITO_REGISTRY_CACHE_CA_HOST_PATH` | Host path holding the proxy MITM CA bundle. Mounted writable into `registry-cache`, read-only into `infinito`. | -| `INFINITO_REGISTRY_CACHE_MAX_SIZE` | Maximum on-disk size. Computed by [infinito_registry_cache_max_size.py](../../../../utils/env/handlers/infinito_registry_cache_max_size.py) as half the free disk at the cache path, floor `1g`, fallback `2g`. | -| `INFINITO_REGISTRY_CACHE_PROXY_CONF` | Bind source for the systemd drop-in inside `infinito`. Set by the dev tooling to the real `proxy.conf` under the `cache` profile. Driver-injected; not produced by `make dotenv`. | -| `INFINITO_PACKAGE_CACHE_HOST_PATH` | Host path bind-mounted at `/nexus-data`. | -| `INFINITO_PACKAGE_CACHE_HEAP` | JVM heap (`-Xms`/`-Xmx`). Computed as half free RAM, capped `2g`, floor `1g`. | -| `INFINITO_PACKAGE_CACHE_DIRECT_MEM` | `MaxDirectMemorySize`. Mirrors `INFINITO_PACKAGE_CACHE_HEAP`. | -| `INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX` | Soft quota for the default blobstore. Computed as half free disk at the cache path, floor `2g`. | -| `INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` | Cache freshness window in minutes, applied to every Nexus proxy repo (`contentMaxAge`/`metadataMaxAge`/negative-cache TTL). Stays strictly below the 7-day `Valid-Until` window Debian / Ubuntu generate in their apt `Release` files, so `apt-get update` never aborts with "Release file ... is expired". | -| `INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD` | Target value for the rotated Nexus admin password. Default is a stable per-host hash. | -| `INFINITO_PACKAGE_CACHE_PORT` | Host-side port mapped to Nexus REST/UI. Bound to `${INFINITO_BIND_IP}` only. | -| `INFINITO_PACKAGE_CACHE_PIP_CONF` | Bind source for `/etc/pip.conf`. Driver-injected by `make up` under the `cache` profile. | -| `INFINITO_PACKAGE_CACHE_NPMRC` | Bind source for `/root/.npmrc`. Driver-injected. | -| `INFINITO_PACKAGE_CACHE_APT_LIST` | Bind source for `/etc/apt/sources.list.d/package-cache.list`. Driver-injected. | -| `INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR` | Host directory for the frontend CA. | -| `INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR` | Host directory for per-hostname leaf certs. | -| `INFINITO_PACKAGE_CACHE_FRONTEND_IP` | Static IPv4 address for the frontend on the compose default network. | -| `INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE` | Bind source for `/opt/package-frontend-ca.crt` inside the runner. Driver-injected. | +| `INFINITO_CACHE_REGISTRY_HOST_PATH` | Host path bind-mounted into `registry-cache` for blob/manifest persistence. | +| `INFINITO_CACHE_REGISTRY_CA_HOST_PATH` | Host path holding the proxy MITM CA bundle. Mounted writable into `registry-cache`, read-only into `infinito`. | +| `INFINITO_CACHE_REGISTRY_MAX_SIZE` | Maximum on-disk size. Computed by [infinito_registry_cache_max_size.py](../../../../utils/env/handlers/infinito_registry_cache_max_size.py) as half the free disk at the cache path, floor `1g`, fallback `2g`. | +| `INFINITO_CACHE_REGISTRY_PROXY_CONF` | Bind source for the systemd drop-in inside `infinito`. Set by the dev tooling to the real `proxy.conf` under the `cache` profile. Driver-injected; not produced by `make dotenv`. | +| `INFINITO_CACHE_PACKAGE_HOST_PATH` | Host path bind-mounted at `/nexus-data`. | +| `INFINITO_CACHE_PACKAGE_HEAP` | JVM heap (`-Xms`/`-Xmx`). Computed as half free RAM, capped `2g`, floor `1g`. | +| `INFINITO_CACHE_PACKAGE_DIRECT_MEM` | `MaxDirectMemorySize`. Mirrors `INFINITO_CACHE_PACKAGE_HEAP`. | +| `INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX` | Soft quota for the default blobstore. Computed as half free disk at the cache path, floor `2g`. | +| `INFINITO_CACHE_PACKAGE_MAX_AGE_MIN` | Cache freshness window in minutes, applied to every Nexus proxy repo (`contentMaxAge`/`metadataMaxAge`/negative-cache TTL). Stays strictly below the 7-day `Valid-Until` window Debian / Ubuntu generate in their apt `Release` files, so `apt-get update` never aborts with "Release file ... is expired". | +| `INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD` | Target value for the rotated Nexus admin password. Default is a stable per-host hash. | +| `INFINITO_CACHE_PACKAGE_PORT` | Host-side port mapped to Nexus REST/UI. Bound to `${INFINITO_BIND_IP}` only. | +| `INFINITO_CACHE_PACKAGE_PIP_CONF` | Bind source for `/etc/pip.conf`. Driver-injected by `make up` under the `cache` profile. | +| `INFINITO_CACHE_PACKAGE_NPMRC` | Bind source for `/root/.npmrc`. Driver-injected. | +| `INFINITO_CACHE_PACKAGE_APT_LIST` | Bind source for `/etc/apt/sources.list.d/package-cache.list`. Driver-injected. | +| `INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR` | Host directory for the frontend CA. | +| `INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR` | Host directory for per-hostname leaf certs. | +| `INFINITO_CACHE_PACKAGE_FRONTEND_IP` | Static IPv4 address for the frontend on the compose default network. | +| `INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE` | Bind source for `/opt/package-frontend-ca.crt` inside the runner. Driver-injected. | ### Networking diff --git a/docs/contributing/environment/cache.md b/docs/contributing/environment/cache.md index cb63d2e972..8d003f2c62 100644 --- a/docs/contributing/environment/cache.md +++ b/docs/contributing/environment/cache.md @@ -47,7 +47,7 @@ The `cache` decision is exposed via `Profile.registry_cache_active()` in [profil - The runner's `infinito` service receives: - bind-mounts for the registry-cache CA, the package-cache client snippets (`pip.conf`, `npmrc`, `apt.list`), and the frontend CA file - `extra_hosts` entries DNS-hijacking the HTTPS upstream hostnames to the frontend's static IP - - `INFINITO_PACKAGE_CACHE_FRONTEND_IP` env var for the inner compose wrapper + - `INFINITO_CACHE_PACKAGE_FRONTEND_IP` env var for the inner compose wrapper - Cert generation, Nexus repo bootstrap, and runner trust-store install run from [compose.py](../../../cli/administration/deploy/development/compose.py). When inactive, the override is omitted: cache services do not exist, the runner has no cache mounts or DNS-hijack, package managers go direct to upstream. @@ -80,17 +80,17 @@ The runner's `compose` wrapper at [roles/sys-svc-compose/files/compose.py](../.. | `compose.yml` | always | | `compose.override.yml` | when present (role provides it) | | `compose.ca.override.yml` | when present (TLS self-signed CA-inject runs) | -| `compose.cache.override.yml` | generated on the fly when `INFINITO_PACKAGE_CACHE_FRONTEND_IP` is set; emits `build.extra_hosts` for every service that has a `build:` key | +| `compose.cache.override.yml` | generated on the fly when `INFINITO_CACHE_PACKAGE_FRONTEND_IP` is set; emits `build.extra_hosts` for every service that has a `build:` key | [pull.py](../../../roles/sys-svc-compose/files/pull.py) delegates to the same wrapper so `pull` and `build --pull` operations see the identical `-f` set. ## Environment Variables 🌳 -The host-side env-vars (`INFINITO_REGISTRY_CACHE_*`, `INFINITO_PACKAGE_CACHE_*`) are declared in [env/default.env](../../../env/default.env) and the dynamic ones (cache sizes from `df`/`/proc/meminfo`, sha256 admin password) are computed by [utils/env/builder.py](../../../utils/env/builder.py). `make dotenv` writes them into `.env`, which Compose auto-loads and which `BASH_ENV` makes available to every Makefile recipe through [scripts/meta/env/load.sh](../../../scripts/meta/env/load.sh). See [variables.md](variables.md). +The host-side env-vars (`INFINITO_CACHE_REGISTRY_*`, `INFINITO_CACHE_PACKAGE_*`) are declared in [env/default.env](../../../env/default.env) and the dynamic ones (cache sizes from `df`/`/proc/meminfo`, sha256 admin password) are computed by [utils/env/builder.py](../../../utils/env/builder.py). `make dotenv` writes them into `.env`, which Compose auto-loads and which `BASH_ENV` makes available to every Makefile recipe through [scripts/meta/env/load.sh](../../../scripts/meta/env/load.sh). See [variables.md](variables.md). Per-variable defaults and purposes are in [compose.yml.md](../artefact/files/compose.yml.md) under the cache section. -`INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` (default `8640` = 6 days) controls how long every Nexus proxy repo holds an upstream response before revalidating. The bootstrap helper applies it as `contentMaxAge`, `metadataMaxAge`, and `negativeCache.timeToLive`. The default is intentionally one day below the 7-day `Valid-Until` window Debian and Ubuntu generate in their apt `Release` files, so Nexus always refreshes the manifest before `apt-get update` would see it expired. Raise it for slow-moving upstreams when staleness is preferable to upstream load; lower it further for fast-moving feeds (`apt-debian-security`, branch-pinned tarballs) when staleness becomes visible. +`INFINITO_CACHE_PACKAGE_MAX_AGE_MIN` (default `8640` = 6 days) controls how long every Nexus proxy repo holds an upstream response before revalidating. The bootstrap helper applies it as `contentMaxAge`, `metadataMaxAge`, and `negativeCache.timeToLive`. The default is intentionally one day below the 7-day `Valid-Until` window Debian and Ubuntu generate in their apt `Release` files, so Nexus always refreshes the manifest before `apt-get update` would see it expired. Raise it for slow-moving upstreams when staleness is preferable to upstream load; lower it further for fast-moving feeds (`apt-debian-security`, branch-pinned tarballs) when staleness becomes visible. ## Operations 🛠️ diff --git a/docs/contributing/environment/variables.md b/docs/contributing/environment/variables.md index 8d302ec324..ee8922109f 100644 --- a/docs/contributing/environment/variables.md +++ b/docs/contributing/environment/variables.md @@ -34,7 +34,7 @@ Already-set env vars take precedence over `default.env` defaults. A handful of variables are documented next to their consumer because they cannot have a static default: -- **Driver-injected** by `make up` and the dev CLI (cache override paths like `INFINITO_REGISTRY_CACHE_PROXY_CONF`, the package-cache config paths, the frontend CA file). +- **Driver-injected** by `make up` and the dev CLI (cache override paths like `INFINITO_CACHE_REGISTRY_PROXY_CONF`, the package-cache config paths, the frontend CA file). See [common.py](../../../cli/administration/deploy/development/common.py) and [compose.yml.md](../artefact/files/compose.yml.md). - **Computed at container start** for `INFINITO_PATH`, set by [entry.sh](../../../scripts/docker/entry.sh). - **Persisted on the deploy target** for `INFINITO_VERSION`, written by the [sys-version](../../../roles/sys-version/) role. diff --git a/docs/requirements/012-package-cache-nexus3-oss.md b/docs/requirements/012-package-cache-nexus3-oss.md index fd260da793..621b82a8aa 100644 --- a/docs/requirements/012-package-cache-nexus3-oss.md +++ b/docs/requirements/012-package-cache-nexus3-oss.md @@ -93,10 +93,10 @@ Therefore: ### Env defaults - [ ] `scripts/meta/env/cache/package.sh` exists and exports - `INFINITO_PACKAGE_CACHE_HOST_PATH`, `INFINITO_PACKAGE_CACHE_HEAP`, - `INFINITO_PACKAGE_CACHE_DIRECT_MEM`, - `INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX`, and - `INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD`. + `INFINITO_CACHE_PACKAGE_HOST_PATH`, `INFINITO_CACHE_PACKAGE_HEAP`, + `INFINITO_CACHE_PACKAGE_DIRECT_MEM`, + `INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX`, and + `INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD`. - [ ] `scripts/meta/env/cache/package.sh` is sourced by `scripts/meta/env/all.sh`. - [ ] `compose/cache.override.yml` consumes every package-cache env @@ -109,11 +109,11 @@ Therefore: (repeated runs MUST NOT error on already-existing repos), and is invoked once on first start under the `cache` profile. - [ ] On first start, the auto-generated Nexus admin password is - rotated to the value of `INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD`. + rotated to the value of `INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD`. - [ ] On first start, the proxy repositories listed in [cache.md](../contributing/environment/cache.md) are created. - [ ] The blobstore `default` is created with quota - `INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX`. + `INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX`. ### Client wiring diff --git a/docs/requirements/020-ci-run-25705903504-deploy-remediation.md b/docs/requirements/020-ci-run-25705903504-deploy-remediation.md index 76f2a0be20..39349248c9 100644 --- a/docs/requirements/020-ci-run-25705903504-deploy-remediation.md +++ b/docs/requirements/020-ci-run-25705903504-deploy-remediation.md @@ -196,7 +196,7 @@ These commits are not per-role fixes but address the underlying environment so s | --- | --- | --- | | `c6affc96f` | New entity-keyed purge primitive `scripts/container/purge/entity/network.sh` removes the per-entity default Docker network when `compose down` left it behind with the wrong / empty `com.docker.compose.network` label. `scripts/container/purge/apps.sh` runs the primitive per entity and then issues a single bare `docker network prune -f` after the entity loop. | C9b (gitea), C12 (matrix); any future role hitting the same orphan-network signature. | | `6d61e3ca4` | Source-able helper `scripts/tests/deploy/local/utils/cache-retry.sh` wraps the deploy command in each local deploy entrypoint (`fresh-purged-app.sh`, `fresh-kept-app.sh`, `fresh-kept-all.sh`, `reuse-kept-app.sh`, `reuse-kept-all.sh`). Detects the stale-apt signature `Release file ... is expired` / `Valid-Until ... expired` in the wrapped command's output, runs `make cache-clean` + `docker builder prune -af` + `docker image prune -af`, then re-runs the command exactly once. | Defensive fallback for "apt Release file ... is expired" Docker-build failures during local iteration. | -| `e58f71987` | Default `INFINITO_PACKAGE_CACHE_MAX_AGE_MIN` lowered 129600 → 8640 (90 days → 6 days). Nexus now re-validates every cached `Release` against upstream before apt would see it expired (Debian/Ubuntu emit `Valid-Until` 7 days from generation). Applies to every Nexus proxy repo via `contentMaxAge` / `metadataMaxAge` / `negativeCache.timeToLive`. | Prevents the underlying stale-apt failure from reaching the deploy in the first place; the cache-retry helper from `6d61e3ca4` is the secondary defence. | +| `e58f71987` | Default `INFINITO_CACHE_PACKAGE_MAX_AGE_MIN` lowered 129600 → 8640 (90 days → 6 days). Nexus now re-validates every cached `Release` against upstream before apt would see it expired (Debian/Ubuntu emit `Valid-Until` 7 days from generation). Applies to every Nexus proxy repo via `contentMaxAge` / `metadataMaxAge` / `negativeCache.timeToLive`. | Prevents the underlying stale-apt failure from reaching the deploy in the first place; the cache-retry helper from `6d61e3ca4` is the secondary defence. | ## CI Run 25890032686 follow-up snapshot diff --git a/env/default.env b/env/default.env index 5aa9037e17..ac9a9413e4 100644 --- a/env/default.env +++ b/env/default.env @@ -22,6 +22,48 @@ INFINITO_BIND_IP=127.0.0.1 # Comma-separated inventory bundles consumed by 'make deploy-bundles' / 'make redeploy-bundles'. INFINITO_BUNDLES= +# Bind-mounted apt sources.list fragment for the package-cache frontend. +INFINITO_CACHE_PACKAGE_APT_LIST=./compose/package-cache/apt.list + +# Host directory holding the package-cache frontend CA materials. +INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR=/var/cache/infinito/core/cache/package/frontend/ca + +# CA certificate file produced under INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR. +INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE=/var/cache/infinito/core/cache/package/frontend/ca/ca.crt + +# Host directory holding the package-cache frontend TLS certificates. +INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR=/var/cache/infinito/core/cache/package/frontend/certs + +# Init image used by the package-cache frontend cert-gen wrapper to run cert-gen.sh. +INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE=alpine:3.20 + +# Static IPv4 address the package-cache frontend takes on the docker bridge. +INFINITO_CACHE_PACKAGE_FRONTEND_IP=172.30.0.4 + +# Host directory backing the Nexus 3 package-cache blobstore. +INFINITO_CACHE_PACKAGE_HOST_PATH=/var/cache/infinito/core/cache/package/data + +# Cache freshness window (minutes) Nexus tolerates before re-validating upstream. 8640 = 6 days (below the 7-day Debian Valid-Until). +INFINITO_CACHE_PACKAGE_MAX_AGE_MIN=8640 + +# Bind-mounted .npmrc fragment for the package-cache frontend. +INFINITO_CACHE_PACKAGE_NPMRC=./compose/package-cache/npmrc + +# Bind-mounted pip.conf fragment for the package-cache frontend. +INFINITO_CACHE_PACKAGE_PIP_CONF=./compose/package-cache/pip.conf + +# Host port the Nexus 3 package cache binds to on BIND_IP. +INFINITO_CACHE_PACKAGE_PORT=8081 + +# Host directory holding the registry-cache CA materials. +INFINITO_CACHE_REGISTRY_CA_HOST_PATH=/var/cache/infinito/core/cache/registry/ca + +# Host directory backing the docker registry-mirror cache (bind mount). +INFINITO_CACHE_REGISTRY_HOST_PATH=/var/cache/infinito/core/cache/registry/mirror + +# Bind-mounted registry-cache proxy configuration consumed by the cache container. +INFINITO_CACHE_REGISTRY_PROXY_CONF=./compose/registry-cache/proxy.conf + # One-shot command 'make exec INFINITO_CMD=...' runs inside the dev container (empty -> interactive shell). INFINITO_CMD= @@ -49,6 +91,12 @@ INFINITO_DISTRO=debian # Space-separated list of distros the build/test matrix iterates over. INFINITO_DISTROS="arch debian ubuntu fedora centos" +# Domain the host-side DNS-fallback / dnsmasq config wires up (consumed by scripts/system/network/dns/). +INFINITO_DNS_DOMAIN=infinito.example + +# Path to the hosts file the DNS-fallback installer writes the BEGIN/END block into. +INFINITO_DNS_HOSTS_FILE=/etc/hosts + # Mount point inside the infinito container where INFINITO_DOCKER_VOLUME lands. INFINITO_DOCKER_MOUNT=/var/lib/docker @@ -88,36 +136,9 @@ INFINITO_NO_BUILD=1 # MTU of the compose stack's bridge network; 1500 matches most hosts. INFINITO_OUTER_NETWORK_MTU=1500 -# Host directory holding the package-cache frontend CA materials. -INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR=/var/cache/infinito/core/cache/package/frontend/ca - -# Host directory holding the package-cache frontend TLS certificates. -INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR=/var/cache/infinito/core/cache/package/frontend/certs - -# Init image used by the package-cache frontend cert-gen wrapper to run cert-gen.sh. -INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE=alpine:3.20 - -# Static IPv4 address the package-cache frontend takes on the docker bridge. -INFINITO_PACKAGE_CACHE_FRONTEND_IP=172.30.0.4 - -# Host directory backing the Nexus 3 package-cache blobstore. -INFINITO_PACKAGE_CACHE_HOST_PATH=/var/cache/infinito/core/cache/package/data - -# Cache freshness window (minutes) Nexus tolerates before re-validating upstream. 8640 = 6 days (below the 7-day Debian Valid-Until). -INFINITO_PACKAGE_CACHE_MAX_AGE_MIN=8640 - -# Host port the Nexus 3 package cache binds to on BIND_IP. -INFINITO_PACKAGE_CACHE_PORT=8081 - # Docker pull policy compose applies; generator overrides to `always` on GHA. INFINITO_PULL_POLICY=never -# Host directory holding the registry-cache CA materials. -INFINITO_REGISTRY_CACHE_CA_HOST_PATH=/var/cache/infinito/core/cache/registry/ca - -# Host directory backing the docker registry-mirror cache (bind mount). -INFINITO_REGISTRY_CACHE_HOST_PATH=/var/cache/infinito/core/cache/registry/mirror - # True when running under nektos/act locally; generator overrides via runtime detection. INFINITO_RUNNING_ON_ACT=false diff --git a/roles/sys-svc-compose/files/compose.py b/roles/sys-svc-compose/files/compose.py index 6cf42f3765..1f80c51384 100755 --- a/roles/sys-svc-compose/files/compose.py +++ b/roles/sys-svc-compose/files/compose.py @@ -89,7 +89,7 @@ _CACHE_HTTP_HOSTNAMES = ( def generate_cache_override(project_dir: Path, base_compose: Path) -> Path | None: """Emit transient build.extra_hosts override when cache profile active.""" - cache_ip = (os.environ.get("INFINITO_PACKAGE_CACHE_FRONTEND_IP") or "").strip() + cache_ip = (os.environ.get("INFINITO_CACHE_PACKAGE_FRONTEND_IP") or "").strip() if not cache_ip or not base_compose.is_file(): return None diff --git a/roles/sys-svc-container/files/container.py b/roles/sys-svc-container/files/container.py index f90f7f0b27..4fe66f3e28 100755 --- a/roles/sys-svc-container/files/container.py +++ b/roles/sys-svc-container/files/container.py @@ -447,7 +447,7 @@ def main() -> int: return 2 if cmd == "run": - no_ca = os.environ.get("INFINITO_CONTAINER_NO_CA", "").lower() in { + no_ca = os.environ["INFINITO_CONTAINER_NO_CA"].lower() in { "1", "true", "yes", diff --git a/scripts/docker/cache/package-frontend-certs.sh b/scripts/docker/cache/package-frontend-certs.sh index 1827ae80d3..7717126b22 100755 --- a/scripts/docker/cache/package-frontend-certs.sh +++ b/scripts/docker/cache/package-frontend-certs.sh @@ -5,8 +5,8 @@ set -euo pipefail -: "${INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR:?Source scripts/meta/env/load.sh first}" -: "${INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR:?Source scripts/meta/env/load.sh first}" +: "${INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR:?Source scripts/meta/env/load.sh first}" +: "${INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR:?Source scripts/meta/env/load.sh first}" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" INNER_SCRIPT="${REPO_ROOT}/compose/package-cache-frontend/cert-gen.sh" @@ -16,12 +16,12 @@ if [[ ! -r "${INNER_SCRIPT}" ]]; then exit 1 fi -: "${INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE:?Source scripts/meta/env/load.sh first}" -ALPINE_IMAGE="${INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE}" +: "${INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE:?Source scripts/meta/env/load.sh first}" +ALPINE_IMAGE="${INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE}" exec docker run --rm \ - -v "${INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR}:/ca" \ - -v "${INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR}:/certs" \ + -v "${INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR}:/ca" \ + -v "${INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR}:/certs" \ -v "${INNER_SCRIPT}:/work/cert-gen.sh:ro" \ --entrypoint /bin/sh \ "${ALPINE_IMAGE}" \ diff --git a/scripts/docker/cache/package.sh b/scripts/docker/cache/package.sh index 1a8b0fcaeb..30333c6cf7 100755 --- a/scripts/docker/cache/package.sh +++ b/scripts/docker/cache/package.sh @@ -5,12 +5,12 @@ set -euo pipefail -: "${INFINITO_PACKAGE_CACHE_HOST_PATH:?Source scripts/meta/env/load.sh first}" -: "${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD:?Source scripts/meta/env/load.sh first}" -: "${INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX:?Source scripts/meta/env/load.sh first}" -: "${INFINITO_PACKAGE_CACHE_MAX_AGE_MIN:?Source scripts/meta/env/load.sh first}" +: "${INFINITO_CACHE_PACKAGE_HOST_PATH:?Source scripts/meta/env/load.sh first}" +: "${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD:?Source scripts/meta/env/load.sh first}" +: "${INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX:?Source scripts/meta/env/load.sh first}" +: "${INFINITO_CACHE_PACKAGE_MAX_AGE_MIN:?Source scripts/meta/env/load.sh first}" -CACHE_MAX_AGE_MIN="${INFINITO_PACKAGE_CACHE_MAX_AGE_MIN}" +CACHE_MAX_AGE_MIN="${INFINITO_CACHE_PACKAGE_MAX_AGE_MIN}" # REST goes via `docker exec curl` to bypass sandbox network isolation. PKGCACHE_CONTAINER="infinito-package-cache" @@ -39,7 +39,7 @@ rotate_admin_password() { ADMIN_USER="admin" if docker exec "${PKGCACHE_CONTAINER}" test -f "${BOOTSTRAP_DONE_FILE}"; then log "Already bootstrapped; using stored admin password" - ADMIN_PASS="${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" + ADMIN_PASS="${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" return 0 fi @@ -47,24 +47,24 @@ rotate_admin_password() { if ! nexus_curl -fsS -u "admin:${NEXUS_DEFAULT_PASSWORD}" \ -H "Content-Type: text/plain" \ -X PUT "${NEXUS_REST}/v1/security/users/admin/change-password" \ - --data-binary "${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}"; then + --data-binary "${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}"; then # Recover from a partially-completed previous bootstrap. - if nexus_curl -fsS -o /dev/null -u "admin:${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" \ + if nexus_curl -fsS -o /dev/null -u "admin:${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" \ "${NEXUS_REST}/v1/repositories" >/dev/null 2>&1; then log "Default rotation rejected but configured password authenticates; assuming previous bootstrap, marking done" else - log "Cannot authenticate with default '${NEXUS_DEFAULT_PASSWORD}' nor with configured INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD." - log "Wipe ${INFINITO_PACKAGE_CACHE_HOST_PATH} (e.g. via 'make cache-clean') and retry." + log "Cannot authenticate with default '${NEXUS_DEFAULT_PASSWORD}' nor with configured INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD." + log "Wipe ${INFINITO_CACHE_PACKAGE_HOST_PATH} (e.g. via 'make cache-clean') and retry." return 1 fi fi docker exec "${PKGCACHE_CONTAINER}" touch "${BOOTSTRAP_DONE_FILE}" - ADMIN_PASS="${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" + ADMIN_PASS="${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" } ensure_blobstore() { # Nexus quota expects MB; convert from "Ng". - local quota_gb="${INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX%g}" + local quota_gb="${INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX%g}" local quota_mb=$((quota_gb * 1024)) local payload payload="$(printf '{"name":"default","path":"default","softQuota":{"type":"spaceUsedQuota","limit":%d}}' "${quota_mb}")" diff --git a/scripts/docker/entry.sh b/scripts/docker/entry.sh index e125e4b798..3961ef4b8f 100755 --- a/scripts/docker/entry.sh +++ b/scripts/docker/entry.sh @@ -10,7 +10,7 @@ fi # Compute dynamically if not provided from outside echo "[docker-infinito] before pkgmgr path" INFINITO_PATH="$(pkgmgr path infinito)" -echo "[docker-infinito] after pkgmgr path: ${INFINITO_PATH}" +echo "[docker-infinito] after pkgmgr path: ${INFINITO_PATH}" # nocheck: container-bootstrap INFINITO_SRC_DIR="/opt/src/infinito" export INFINITO_PATH @@ -18,13 +18,13 @@ export INFINITO_SRC_DIR run_local_build() { echo "[docker-infinito] Build enabled (INFINITO_COMPILE=1)" - echo "[docker-infinito] Using ${INFINITO_PATH} as working directory" + echo "[docker-infinito] Using ${INFINITO_PATH} as working directory" # nocheck: container-bootstrap - mkdir -p "${INFINITO_PATH}" - cd "${INFINITO_PATH}" + mkdir -p "${INFINITO_PATH}" # nocheck: container-bootstrap + cd "${INFINITO_PATH}" # nocheck: container-bootstrap - echo "[docker-infinito] Copy ${INFINITO_SRC_DIR} to ${INFINITO_PATH}..." - rsync -a --delete --chown=root:root --exclude='.git' "${INFINITO_SRC_DIR}/" "${INFINITO_PATH}/" + echo "[docker-infinito] Copy ${INFINITO_SRC_DIR} to ${INFINITO_PATH}..." # nocheck: container-bootstrap + rsync -a --delete --chown=root:root --exclude='.git' "${INFINITO_SRC_DIR}/" "${INFINITO_PATH}/" # nocheck: container-bootstrap echo "[docker-infinito] Reinstall via 'make install'..." make install @@ -51,6 +51,6 @@ if [[ $# -eq 0 ]]; then echo "[docker-infinito] No arguments provided. Showing infinito help..." exec infinito --help else - cd "${INFINITO_PATH}" + cd "${INFINITO_PATH}" # nocheck: container-bootstrap exec "$@" fi diff --git a/scripts/meta/env/load.sh b/scripts/meta/env/load.sh index 3b48bab674..d2ca5fa22f 100755 --- a/scripts/meta/env/load.sh +++ b/scripts/meta/env/load.sh @@ -30,10 +30,10 @@ # short-circuit here when we see it, so subprocesses skip the load # entirely during generation. -if [[ "${INFINITO_ENV_LOADED:-}" == "1" ]]; then +if [[ "${INFINITO_ENV_LOADED:-}" == "1" ]]; then # nocheck: env-loader-internal-guard return 0 fi -if [[ "${INFINITO_ENV_GENERATING:-}" == "1" ]]; then +if [[ "${INFINITO_ENV_GENERATING:-}" == "1" ]]; then # nocheck: env-loader-internal-guard return 0 fi diff --git a/scripts/system/cache/clean.sh b/scripts/system/cache/clean.sh index 3f61ca02b8..10fae65623 100755 --- a/scripts/system/cache/clean.sh +++ b/scripts/system/cache/clean.sh @@ -37,9 +37,9 @@ if docker ps --format '{{.Names}}' | grep -qE '^infinito-(registry|package)-cach fi PATHS=( - "${INFINITO_REGISTRY_CACHE_HOST_PATH}" - "${INFINITO_REGISTRY_CACHE_CA_HOST_PATH}" - "${INFINITO_PACKAGE_CACHE_HOST_PATH}" + "${INFINITO_CACHE_REGISTRY_HOST_PATH}" + "${INFINITO_CACHE_REGISTRY_CA_HOST_PATH}" + "${INFINITO_CACHE_PACKAGE_HOST_PATH}" ) # Remove each cache path individually (keeps the parent /var/cache/ diff --git a/scripts/system/network/dns/common.sh b/scripts/system/network/dns/common.sh index d43e376d5b..1c4e8445e8 100755 --- a/scripts/system/network/dns/common.sh +++ b/scripts/system/network/dns/common.sh @@ -2,8 +2,8 @@ DNS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DNS_PROJECT_ROOT="$(cd "${DNS_SCRIPT_DIR}/../../../.." && pwd)" -DNS_DOMAIN="${INFINITO_DNS_DOMAIN:-infinito.example}" # nocheck: host-dns-override -- run with sudo outside load.sh context; tweak the host's /etc/hosts only -DNS_HOSTS_FILE="${INFINITO_DNS_HOSTS_FILE:-/etc/hosts}" # nocheck: host-dns-override -- run with sudo outside load.sh context; tweak the host's /etc/hosts only +DNS_DOMAIN="${INFINITO_DNS_DOMAIN}" +DNS_HOSTS_FILE="${INFINITO_DNS_HOSTS_FILE}" DNS_HOSTS_BLOCK_BEGIN="# BEGIN infinito-dns-fallback" DNS_HOSTS_BLOCK_END="# END infinito-dns-fallback" DNS_HOSTS_GENERATOR="${DNS_PROJECT_ROOT}/cli/meta/domains/__main__.py" @@ -19,9 +19,6 @@ DNS_NM_DNSMASQ_CONF="${DNS_NM_DNSMASQ_DIR}/${DNS_DOMAIN}.conf" DNS_SYS_DNSMASQ_CONF="/etc/dnsmasq.d/${DNS_DOMAIN}.conf" dns_systemd_is_operational() { - if [[ "${INFINITO_DNS_FORCE_HOSTS_FALLBACK:-0}" == "1" ]]; then # nocheck: host-dns-override -- run with sudo outside load.sh context - return 1 - fi command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]] } @@ -87,8 +84,8 @@ dns_read_generated_hosts_fallback_entries() { } dns_read_hosts_fallback_entries() { - if [[ -n "${INFINITO_DNS_HOSTS:-}" ]]; then - dns_read_hosts_fallback_entries_from_raw "${INFINITO_DNS_HOSTS}" + if [[ -n "${INFINITO_DNS_HOSTS:-}" ]]; then # nocheck: infinito-dns-hosts-optional + dns_read_hosts_fallback_entries_from_raw "${INFINITO_DNS_HOSTS}" # nocheck: infinito-dns-hosts-optional return fi diff --git a/scripts/tests/code/run.sh b/scripts/tests/code/run.sh index adeeb52f27..62e7d54c9c 100755 --- a/scripts/tests/code/run.sh +++ b/scripts/tests/code/run.sh @@ -6,7 +6,7 @@ set -euo pipefail : "${INFINITO_TEST_PATTERN:?INFINITO_TEST_PATTERN must be set}" -: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}" +: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}" # nocheck: makefile-supplied NIX_CONFIG_EFFECTIVE="$( printf "%s\n%s\n" \ @@ -25,4 +25,4 @@ if [ -n "${PYTHON:-}" ]; then fi make setup -"${PYTHON:-python3}" -m unittest discover -s "tests/${INFINITO_TEST_TYPE}" -t . -p "${INFINITO_TEST_PATTERN}" +"${PYTHON:-python3}" -m unittest discover -s "tests/${INFINITO_TEST_TYPE}" -t . -p "${INFINITO_TEST_PATTERN}" # nocheck: makefile-supplied diff --git a/scripts/tests/code/wrapper.sh b/scripts/tests/code/wrapper.sh index e16195bb5a..d5694a9b95 100755 --- a/scripts/tests/code/wrapper.sh +++ b/scripts/tests/code/wrapper.sh @@ -19,7 +19,7 @@ source scripts/meta/env/load.sh : "${INFINITO_TEST_PATTERN:?INFINITO_TEST_PATTERN must be set}" : "${INFINITO_TEST_RUNNER:?INFINITO_TEST_RUNNER must be set}" -: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}" +: "${INFINITO_TEST_TYPE:?INFINITO_TEST_TYPE must be set}" # nocheck: makefile-supplied RUN_SCRIPT_IN_CONTAINER="${INFINITO_SRC_DIR}/scripts/tests/code/run.sh" @@ -28,7 +28,7 @@ docker) # INFINITO_DISTRO is sourced (with a `debian` default) from # scripts/meta/env/load.sh above, so no explicit check here. echo "============================================================" - echo ">>> Running ${INFINITO_TEST_TYPE^^} tests in ${INFINITO_DISTRO} container (compose stack)" + echo ">>> Running ${INFINITO_TEST_TYPE^^} tests in ${INFINITO_DISTRO} container (compose stack)" # nocheck: makefile-supplied echo "============================================================" # Auto-bring-up: if the `infinito` container is not running, kick off @@ -48,23 +48,30 @@ docker) # every INFINITO_* key from the bind-mounted .env into the test # runner's environment (notably INFINITO_WORKER_FETCH, otherwise the # external URL probe collapses to 1 worker). + # Per-invocation env passed through docker exec. Built as a bash + # array so the inline `# nocheck:` markers between elements stay + # syntactically valid (a backslash-continued line cannot host an + # inline comment). + exec_env_args=( + -e ACT="${ACT:-}" + -e BASH_ENV="${INFINITO_SRC_DIR}/scripts/meta/env/load.sh" + -e GITHUB_ACTIONS="${GITHUB_ACTIONS:-}" + -e GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}" + -e GITHUB_REPOSITORY_OWNER="${GITHUB_REPOSITORY_OWNER:-}" + -e GITHUB_SHA="${GITHUB_SHA:-}" + -e INFINITO_TEST_PATTERN="${INFINITO_TEST_PATTERN}" + -e INFINITO_TEST_TYPE="${INFINITO_TEST_TYPE}" # nocheck: makefile-supplied + ) INFINITO_DISTRO="${INFINITO_DISTRO}" \ docker compose --env-file env/ci.env --env-file .env --profile ci exec -T \ - -e ACT="${ACT:-}" \ - -e BASH_ENV="${INFINITO_SRC_DIR}/scripts/meta/env/load.sh" \ - -e GITHUB_ACTIONS="${GITHUB_ACTIONS:-}" \ - -e GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}" \ - -e GITHUB_REPOSITORY_OWNER="${GITHUB_REPOSITORY_OWNER:-}" \ - -e GITHUB_SHA="${GITHUB_SHA:-}" \ - -e INFINITO_TEST_PATTERN="${INFINITO_TEST_PATTERN}" \ - -e INFINITO_TEST_TYPE="${INFINITO_TEST_TYPE}" \ + "${exec_env_args[@]}" \ --workdir "${INFINITO_SRC_DIR}" \ infinito \ bash --login "${RUN_SCRIPT_IN_CONTAINER}" ;; host) echo "============================================================" - echo ">>> Running ${INFINITO_TEST_TYPE^^} tests on host" + echo ">>> Running ${INFINITO_TEST_TYPE^^} tests on host" # nocheck: makefile-supplied echo "============================================================" exec bash --login "${RUN_SCRIPT}" ;; diff --git a/scripts/tests/deploy/local/utils/cache-retry.sh b/scripts/tests/deploy/local/utils/cache-retry.sh index 3be0c569b5..be5f1ce7d5 100755 --- a/scripts/tests/deploy/local/utils/cache-retry.sh +++ b/scripts/tests/deploy/local/utils/cache-retry.sh @@ -18,7 +18,7 @@ _cache_retry_nexus_invalidate() { local rest="http://127.0.0.1:8081/service/rest/v1/repositories/${repo}/invalidate-cache" local code code="$(docker exec infinito-package-cache curl -sS -o /dev/null -w '%{http_code}' \ - -u "admin:${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" \ + -u "admin:${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" \ -X POST "${rest}" 2>/dev/null || echo 000)" if [[ "${code}" =~ ^2[0-9][0-9]$ ]]; then echo "[cache-retry] invalidate-cache OK (${code}): ${repo}" @@ -30,7 +30,7 @@ _cache_retry_nexus_invalidate() { _cache_retry_list_apt_proxy_repos() { local json json="$(docker exec infinito-package-cache curl -sS \ - -u "admin:${INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD}" \ + -u "admin:${INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD}" \ "http://127.0.0.1:8081/service/rest/v1/repositories" 2>/dev/null)" || return 1 [[ -z "${json}" ]] && return 1 printf '%s' "${json}" | diff --git a/scripts/tests/deploy/local/utils/reuse-kept-all-deploy.sh b/scripts/tests/deploy/local/utils/reuse-kept-all-deploy.sh index cef12d223c..089b307d5d 100755 --- a/scripts/tests/deploy/local/utils/reuse-kept-all-deploy.sh +++ b/scripts/tests/deploy/local/utils/reuse-kept-all-deploy.sh @@ -16,7 +16,7 @@ set -euo pipefail cd /opt/src/infinito : "${INFINITO_INVENTORY_FILE:?INFINITO_INVENTORY_FILE must be set}" -: "${INFINITO_INVENTORY_PASSWORD_FILE:?INFINITO_INVENTORY_PASSWORD_FILE must be set}" +: "${INFINITO_INVENTORY_PASSWORD_FILE:?INFINITO_INVENTORY_PASSWORD_FILE must be set}" # nocheck: driver-injected-per-invocation : "${INFINITO_LIMIT_HOST:?INFINITO_LIMIT_HOST must be set}" : "${INFINITO_DEBUG:?INFINITO_DEBUG must be set}" @@ -29,7 +29,7 @@ cmd=(infinito administration deploy dedicated "${INFINITO_INVENTORY_FILE}" -l "${INFINITO_LIMIT_HOST}" --diff -vv - --password-file "${INFINITO_INVENTORY_PASSWORD_FILE}" + --password-file "${INFINITO_INVENTORY_PASSWORD_FILE}" # nocheck: driver-injected-per-invocation -e ASYNC_ENABLED=false -e SYS_SERVICE_ALL_ENABLED=false -e SYS_SERVICE_DEFAULT_STATE=started diff --git a/scripts/tests/environment/utils/cache.sh b/scripts/tests/environment/utils/cache.sh index 20c477619b..ad8da69e30 100755 --- a/scripts/tests/environment/utils/cache.sh +++ b/scripts/tests/environment/utils/cache.sh @@ -213,9 +213,9 @@ probe_caches() { # Ansible-driven runner-side traffic alone does NOT exercise. probe_did_inner_build() { local frontend_ip - frontend_ip="${INFINITO_PACKAGE_CACHE_FRONTEND_IP:-}" + frontend_ip="${INFINITO_CACHE_PACKAGE_FRONTEND_IP:-}" if [[ -z "${frontend_ip}" ]]; then - echo "[probe-did] INFINITO_PACKAGE_CACHE_FRONTEND_IP unset; skipping DiD probe" + echo "[probe-did] INFINITO_CACHE_PACKAGE_FRONTEND_IP unset; skipping DiD probe" return 0 fi diff --git a/tests/external/repository/test_urls_reachable.py b/tests/external/repository/test_urls_reachable.py index 495ffcf82c..77c724d5e9 100644 --- a/tests/external/repository/test_urls_reachable.py +++ b/tests/external/repository/test_urls_reachable.py @@ -82,7 +82,7 @@ _WARNING_STATUS_CODES = { } _REQUEST_TIMEOUT_SECONDS = 10 _USER_AGENT = "infinito-nexus-url-reachability-check" -_MAX_WORKERS = int(os.environ.get("INFINITO_WORKER_FETCH", "1")) +_MAX_WORKERS = int(os.environ["INFINITO_WORKER_FETCH"]) # Hard ceiling for the whole probe loop. After this elapses, any probe # still in flight is marked as a Timeout warning so the test never hangs # indefinitely on a slow / trickling server. 5h30m matches the longest diff --git a/tests/integration/meta/env/test_dotenv_generator.py b/tests/integration/meta/env/test_dotenv_generator.py index 6cbc0398a5..1709c1b896 100644 --- a/tests/integration/meta/env/test_dotenv_generator.py +++ b/tests/integration/meta/env/test_dotenv_generator.py @@ -85,20 +85,20 @@ EXPECTED_BASELINE_KEYS = frozenset( "PIP", "PYTHONPATH", "INVENTORY_VARS_FILE", - "INFINITO_REGISTRY_CACHE_HOST_PATH", - "INFINITO_REGISTRY_CACHE_CA_HOST_PATH", - "INFINITO_REGISTRY_CACHE_MAX_SIZE", - "INFINITO_PACKAGE_CACHE_HOST_PATH", - "INFINITO_PACKAGE_CACHE_PORT", - "INFINITO_PACKAGE_CACHE_HEAP", - "INFINITO_PACKAGE_CACHE_DIRECT_MEM", - "INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX", - "INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD", - "INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR", - "INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR", - "INFINITO_PACKAGE_CACHE_FRONTEND_IP", - "INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE", - "INFINITO_PACKAGE_CACHE_MAX_AGE_MIN", + "INFINITO_CACHE_REGISTRY_HOST_PATH", + "INFINITO_CACHE_REGISTRY_CA_HOST_PATH", + "INFINITO_CACHE_REGISTRY_MAX_SIZE", + "INFINITO_CACHE_PACKAGE_HOST_PATH", + "INFINITO_CACHE_PACKAGE_PORT", + "INFINITO_CACHE_PACKAGE_HEAP", + "INFINITO_CACHE_PACKAGE_DIRECT_MEM", + "INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX", + "INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD", + "INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR", + "INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR", + "INFINITO_CACHE_PACKAGE_FRONTEND_IP", + "INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE", + "INFINITO_CACHE_PACKAGE_MAX_AGE_MIN", "INFINITO_IMAGE_TAG", } ) diff --git a/tests/lint/filesystem/env/test_default_env_keys_handled.py b/tests/lint/filesystem/env/test_default_env_keys_handled.py new file mode 100644 index 0000000000..b9e20dba74 --- /dev/null +++ b/tests/lint/filesystem/env/test_default_env_keys_handled.py @@ -0,0 +1,80 @@ +"""Every key in env/default.env must be wired through a handler builder. + +env/default.env is the SPOT for static defaults. The handler builders +under ``utils/env/handlers/`` are responsible for applying each entry +as a ``setdefault`` on the EnvBuilder. If a key lives in default.env +but no handler references it, the generated ``.env`` will silently +miss the default and downstream consumers crash with an unbound +variable. +""" + +from __future__ import annotations + +import re +import unittest +from typing import TYPE_CHECKING + +from utils.cache.files import read_text + +from . import PROJECT_ROOT + +if TYPE_CHECKING: + from pathlib import Path + +_KEY_RE = re.compile(r"^\s*(?PINFINITO_[A-Z0-9_]+)\s*=") +_HANDLER_LITERAL_RE = re.compile(r"[\"'](?PINFINITO_[A-Z0-9_]+)[\"']") + + +def _default_env_keys(path: Path) -> set[str]: + return { + match.group("key") + for line in read_text(str(path)).splitlines() + if (match := _KEY_RE.match(line)) is not None + } + + +def _handler_referenced_keys(handlers_dir: Path) -> set[str]: + keys: set[str] = set() + for module in sorted(handlers_dir.glob("*.py")): + if module.name == "__init__.py": + continue + text = read_text(str(module)) + for match in _HANDLER_LITERAL_RE.finditer(text): + keys.add(match.group("key")) + return keys + + +class TestDefaultEnvKeysHandled(unittest.TestCase): + def test_every_default_env_key_has_a_handler(self) -> None: + default_env_path = PROJECT_ROOT / "env" / "default.env" + handlers_dir = PROJECT_ROOT / "utils" / "env" / "handlers" + + self.assertTrue(default_env_path.is_file(), "env/default.env not found") + self.assertTrue(handlers_dir.is_dir(), "utils/env/handlers/ not found") + + declared = _default_env_keys(default_env_path) + self.assertTrue(declared, "env/default.env has no INFINITO_* entries") + + referenced = _handler_referenced_keys(handlers_dir) + missing = sorted(declared - referenced) + if not missing: + return + + lines = [ + f"INFINITO_* keys in env/default.env are not wired through any handler " + f"({len(missing)} unhandled):", + "", + "env/default.env is the SPOT for static defaults; each key MUST be " + "applied via setdefault by a handler in utils/env/handlers/ " + "(typically the static-passthrough handler). Add the key to the " + "handler's STATIC_KEYS / KEY constant so `make dotenv` materialises " + "the default into the generated .env.", + "", + "Unhandled keys:", + ] + lines.extend(f" {key}" for key in missing) + self.fail("\n".join(lines)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/lint/filesystem/env/test_used_keys_declared.py b/tests/lint/filesystem/env/test_used_keys_declared.py new file mode 100644 index 0000000000..d4fa842974 --- /dev/null +++ b/tests/lint/filesystem/env/test_used_keys_declared.py @@ -0,0 +1,168 @@ +"""Every INFINITO_* used in `.sh` / `.py` must be declared as a default. + +Defaults live in ``env/default.env`` (the SPOT for static values) and +in the handler modules under ``utils/env/handlers/`` (which apply +dynamic defaults via ``setdefault``). Together they form the registry +of valid INFINITO_* keys. + +This test scans every shell and Python source file for INFINITO_* +references and fails when a referenced key has no corresponding entry +in either the static file or a handler module. Such a key would reach +the consumer unset, and a bare read would crash with KeyError / set -u. + +Allowed reference shapes (sampled, not exhaustive): + +* shell: ``${INFINITO_VAR}``, ``${INFINITO_VAR:?msg}``, ``${INFINITO_VAR:-}`` +* python: ``os.environ["INFINITO_VAR"]``, ``os.environ.get("INFINITO_VAR")``, + ``env.get("INFINITO_VAR")``, etc. + +Suppress per line with a same-line ``# nocheck: `` marker. Use +it only for keys that legitimately stay registry-less (e.g. pure +bootstrap one-shots). +""" + +from __future__ import annotations + +import re +import unittest +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from utils.cache.files import iter_non_ignored_files, read_text + +from . import PROJECT_ROOT + +if TYPE_CHECKING: + from pathlib import Path + +_SH_REF_RE = re.compile(r"\$\{?(?PINFINITO_[A-Z0-9_]+)") +_PY_LITERAL_RE = re.compile(r"[\"'](?PINFINITO_[A-Z0-9_]+)[\"']") +_DEFAULT_ENV_KEY_RE = re.compile(r"^\s*(?PINFINITO_[A-Z0-9_]+)\s*=") +_HANDLER_LITERAL_RE = re.compile(r"[\"'](?PINFINITO_[A-Z0-9_]+)[\"']") +_NOCHECK_RE = re.compile(r"#\s*nocheck\b") + +# These trees define the registry / document it; references in them +# describe the rule, they do not consume the keys. +_EXCLUDED_PREFIXES = ( + "tests/lint/", + "utils/env/handlers/", + "env/", +) + + +@dataclass(frozen=True) +class _Reference: + file: str + line_no: int + key: str + + +def _registered_keys() -> set[str]: + keys: set[str] = set() + env_dir = PROJECT_ROOT / "env" + for env_file in sorted(env_dir.glob("*.env")): + for line in read_text(str(env_file)).splitlines(): + match = _DEFAULT_ENV_KEY_RE.match(line) + if match is not None: + keys.add(match.group("key")) + + handlers_dir = PROJECT_ROOT / "utils" / "env" / "handlers" + for module in sorted(handlers_dir.glob("*.py")): + if module.name == "__init__.py": + continue + text = read_text(str(module)) + for match in _HANDLER_LITERAL_RE.finditer(text): + keys.add(match.group("key")) + + return keys + + +def _strip_comment(line: str) -> str: + """Drop a trailing `# ...` comment so INFINITO_* names mentioned only in + prose do not register as code references. Crude: a `#` inside a quoted + string also truncates, which is fine since INFINITO_* references inside + quoted strings stay intact in the kept prefix.""" + idx = line.find("#") + return line if idx < 0 else line[:idx] + + +def _scan_with(path: Path, pattern: re.Pattern[str]) -> list[_Reference]: + rel = path.relative_to(PROJECT_ROOT).as_posix() + out: list[_Reference] = [] + for idx, line in enumerate(read_text(str(path)).splitlines(), start=1): + if _NOCHECK_RE.search(line): + continue + code = _strip_comment(line) + out.extend(_Reference(rel, idx, m.group("key")) for m in pattern.finditer(code)) + return out + + +def _scan_shell(path: Path) -> list[_Reference]: + return _scan_with(path, _SH_REF_RE) + + +def _scan_python(path: Path) -> list[_Reference]: + return _scan_with(path, _PY_LITERAL_RE) + + +def _scan_targets() -> tuple[list[Path], list[Path]]: + sh: list[Path] = [] + py: list[Path] = [] + root_str = str(PROJECT_ROOT) + for absolute in iter_non_ignored_files(root=root_str): + rel = absolute.removeprefix(root_str + "/") + if any(rel.startswith(prefix) for prefix in _EXCLUDED_PREFIXES): + continue + if rel.endswith(".sh"): + sh.append(PROJECT_ROOT / rel) + elif rel.endswith(".py"): + py.append(PROJECT_ROOT / rel) + return sh, py + + +class TestUsedKeysDeclared(unittest.TestCase): + def test_every_referenced_infinito_key_is_registered(self) -> None: + registered = _registered_keys() + self.assertTrue(registered, "no INFINITO_* keys found in the registry") + + shell_files, python_files = _scan_targets() + self.assertTrue(shell_files or python_files, "no .sh / .py files to scan") + + violations: list[_Reference] = [] + for path in shell_files: + violations.extend( + ref for ref in _scan_shell(path) if ref.key not in registered + ) + for path in python_files: + violations.extend( + ref for ref in _scan_python(path) if ref.key not in registered + ) + + if not violations: + return + + unique_keys = sorted({v.key for v in violations}) + grouped: dict[str, list[_Reference]] = {} + for v in violations: + grouped.setdefault(v.key, []).append(v) + + lines = [ + f"INFINITO_* keys referenced in code without a registry entry " + f"({len(unique_keys)} unknown key(s) across {len(violations)} use site(s)):", + "", + "Add a default to env/default.env (and to the static-passthrough handler's " + "STATIC_KEYS) for static values, or write a dedicated handler under " + "utils/env/handlers/ that sets the key via eb.setdefault. " + "Suppress per line with `# nocheck: ` when the key legitimately " + "stays registry-less.", + "", + "Unregistered keys:", + ] + for key in unique_keys: + lines.append(f" {key}:") + lines.extend(f" {ref.file}:{ref.line_no}" for ref in grouped[key]) + self.fail("\n".join(lines)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/lint/filesystem/python/test_no_infinito_get_defaults.py b/tests/lint/filesystem/python/test_no_infinito_get_defaults.py new file mode 100644 index 0000000000..4dcc990025 --- /dev/null +++ b/tests/lint/filesystem/python/test_no_infinito_get_defaults.py @@ -0,0 +1,144 @@ +"""Lint guard: `.py` files MUST NOT declare INFINITO_* defaults in `.get(KEY, default)`. + +The single source of truth for INFINITO_* defaults is ``env/default.env`` +(materialised into ``.env`` by ``make dotenv`` and applied as +``setdefault`` by the handler builders in ``utils/env/handlers/``). +Hardcoded fallbacks in Python contradict that contract -- if the +default.env value drifts, the Python copy silently keeps the old one. + +Allowed: + +* Bare read: ``os.environ.get("INFINITO_VAR")`` (returns ``None`` when unset) +* Required-loud: ``os.environ["INFINITO_VAR"]`` (raises ``KeyError`` when unset) + +Forbidden: + +* Any default at all, including the empty string: + ``os.environ.get("INFINITO_VAR", "")``, + ``os.environ.get("INFINITO_VAR", "1")``, + ``env.get("INFINITO_VAR", DEFAULT)``, etc. + +Suppress per line with a same-line ``# nocheck: `` marker. +Use it only when the call genuinely cannot consume ``env/default.env`` +(e.g. bootstrap code that runs before the env loader). +""" + +from __future__ import annotations + +import ast +import re +import unittest +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from utils.cache.files import iter_non_ignored_files, read_text + +from . import PROJECT_ROOT + +if TYPE_CHECKING: + from pathlib import Path + +_INFINITO_KEY_RE = re.compile(r"^INFINITO_[A-Z0-9_]+$") +_NOCHECK_RE = re.compile(r"#\s*nocheck\b") + + +@dataclass(frozen=True) +class _Violation: + file: str + line_no: int + key: str + default_repr: str + + +def _scan_file(path: Path) -> list[_Violation]: + rel = path.relative_to(PROJECT_ROOT).as_posix() + try: + text = read_text(str(path)) + tree = ast.parse(text, filename=rel) + except (OSError, UnicodeDecodeError, SyntaxError): + return [] + + raw_lines = text.splitlines() + violations: list[_Violation] = [] + + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + if not (isinstance(node.func, ast.Attribute) and node.func.attr == "get"): + continue + if len(node.args) < 2: + continue + key_node = node.args[0] + if not (isinstance(key_node, ast.Constant) and isinstance(key_node.value, str)): + continue + if not _INFINITO_KEY_RE.match(key_node.value): + continue + default_node = node.args[1] + # Allow per-line `# nocheck:` suppression on any physical line the + # call spans (autoformat may have split a one-liner across the + # opening paren, the args, and the closing paren). + line_no = node.lineno + end_line_no = getattr(node, "end_lineno", line_no) or line_no + if any( + 1 <= n <= len(raw_lines) and _NOCHECK_RE.search(raw_lines[n - 1]) + for n in range(line_no, end_line_no + 1) + ): + continue + violations.append( + _Violation( + rel, + line_no, + key_node.value, + ast.unparse(default_node), + ) + ) + + return violations + + +def _scan_targets() -> list[Path]: + return [ + PROJECT_ROOT / rel + for rel in iter_non_ignored_files(root=str(PROJECT_ROOT)) + if rel.endswith(".py") + ] + + +class TestPythonNoInfinitoGetDefaults(unittest.TestCase): + def test_python_files_dont_declare_infinito_get_defaults(self) -> None: + targets = _scan_targets() + self.assertTrue(targets, "no .py files found to scan") + + violations: list[_Violation] = [] + for path in targets: + violations.extend(_scan_file(path)) + + if not violations: + return + + grouped: dict[str, list[_Violation]] = {} + for v in violations: + grouped.setdefault(v.file, []).append(v) + lines = [ + f"INFINITO_* defaults declared in `.get(KEY, default)` " + f"({len(violations)} violations across {len(grouped)} file(s)):", + "", + "INFINITO_* defaults belong in env/default.env (SPOT); the " + "handler builders in utils/env/handlers/ then apply them via " + "setdefault. Use `os.environ.get(KEY)` (None when unset) or " + "`os.environ[KEY]` (loud KeyError) instead -- the empty-string " + "fallback is also forbidden because it silently masks an unset " + "key. Suppress per line with `# nocheck: `.", + "", + "Offenders:", + ] + for f, vs in sorted(grouped.items()): + lines.append(f" {f}:") + lines.extend( + f" line {v.line_no}: {v.key} default={v.default_repr}" for v in vs + ) + self.fail("\n".join(lines)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/cli/administration/deploy/development/test_compose.py b/tests/unit/cli/administration/deploy/development/test_compose.py index be5e9b456c..5fb38b46dd 100644 --- a/tests/unit/cli/administration/deploy/development/test_compose.py +++ b/tests/unit/cli/administration/deploy/development/test_compose.py @@ -144,7 +144,6 @@ class TestComposeUpRetries(unittest.TestCase): ) self.assertEqual(env["INFINITO_DISTRO"], "arch") self.assertNotIn("COMPOSE_PROFILES", env) - self.assertNotIn("INFINITO_REGISTRY_CACHE_PROXY_CONF", env) @patch.dict( os.environ, @@ -153,8 +152,6 @@ class TestComposeUpRetries(unittest.TestCase): "GITHUB_ACTIONS": "", "INFINITO_RUNNING_ON_GITHUB": "", "CI": "", - # Tests bypass BASH_ENV; set explicitly so cache_env_overrides() passes. - "INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR": "/var/cache/infinito/test/ca", }, clear=False, ) @@ -189,11 +186,13 @@ class TestComposeUpRetries(unittest.TestCase): ], ) self.assertEqual( - env["INFINITO_REGISTRY_CACHE_PROXY_CONF"], + env["INFINITO_CACHE_REGISTRY_PROXY_CONF"], # nocheck: test-fixture "./compose/registry-cache/proxy.conf", ) self.assertTrue( - env["INFINITO_PACKAGE_CACHE_FRONTEND_CA_FILE"].endswith("/ca.crt") + env["INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE"].endswith( + "/ca.crt" + ) # nocheck: test-fixture ) @patch.dict( @@ -267,8 +266,6 @@ class TestComposeUpRetries(unittest.TestCase): "CI": "", "GITHUB_ACTIONS": "", "INFINITO_RUNNING_ON_GITHUB": "", - # Tests bypass BASH_ENV; set explicitly so cache_env_overrides() passes. - "INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR": "/var/cache/infinito/test/ca", }, clear=False, ) diff --git a/tests/unit/cli/administration/deploy/development/test_exec.py b/tests/unit/cli/administration/deploy/development/test_exec.py index a247b24d90..7f0b2b1cf3 100644 --- a/tests/unit/cli/administration/deploy/development/test_exec.py +++ b/tests/unit/cli/administration/deploy/development/test_exec.py @@ -48,7 +48,7 @@ class TestDevelopmentExec(unittest.TestCase): compose = unittest.mock.Mock() compose.exec.return_value.returncode = 0 - env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"} + env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""} with ( unittest.mock.patch( "cli.administration.deploy.development.exec.make_compose", @@ -71,7 +71,7 @@ class TestDevelopmentExec(unittest.TestCase): compose = unittest.mock.Mock() compose.exec.return_value.returncode = 0 - env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"} + env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""} with ( unittest.mock.patch( "cli.administration.deploy.development.exec.make_compose", @@ -103,7 +103,7 @@ class TestDevelopmentExec(unittest.TestCase): compose = unittest.mock.Mock() compose.exec.return_value.returncode = 0 - env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"} + env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""} with ( unittest.mock.patch( "cli.administration.deploy.development.exec.make_compose", diff --git a/tests/unit/cli/administration/deploy/development/test_network.py b/tests/unit/cli/administration/deploy/development/test_network.py index 9e2e17ec50..eea41ee5c2 100644 --- a/tests/unit/cli/administration/deploy/development/test_network.py +++ b/tests/unit/cli/administration/deploy/development/test_network.py @@ -12,7 +12,7 @@ class TestDetectOuterNetworkMtu(unittest.TestCase): def test_prefers_explicit_outer_network_mtu_env(self) -> None: self.assertEqual(detect_outer_network_mtu(), "1300") - @patch.dict(os.environ, {}, clear=True) + @patch.dict(os.environ, {"INFINITO_OUTER_NETWORK_MTU": ""}, clear=True) @patch("cli.administration.deploy.development.network.Path.exists", autospec=True) @patch( "cli.administration.deploy.development.network.Path.read_text", autospec=True @@ -27,7 +27,7 @@ class TestDetectOuterNetworkMtu(unittest.TestCase): self.assertEqual(detect_outer_network_mtu(), "1400") - @patch.dict(os.environ, {}, clear=True) + @patch.dict(os.environ, {"INFINITO_OUTER_NETWORK_MTU": ""}, clear=True) @patch("cli.administration.deploy.development.network.Path.exists", autospec=True) @patch( "cli.administration.deploy.development.network.Path.read_text", autospec=True diff --git a/tests/unit/cli/administration/inventory/provision/test_services_disabler.py b/tests/unit/cli/administration/inventory/provision/test_services_disabler.py index db30e7b715..fcfab5bd87 100644 --- a/tests/unit/cli/administration/inventory/provision/test_services_disabler.py +++ b/tests/unit/cli/administration/inventory/provision/test_services_disabler.py @@ -400,7 +400,7 @@ class TestApplyServicesDisabledFromEnv(unittest.TestCase): def test_no_op_when_env_not_set(self): self._write({"applications": {}}) - env = {k: v for k, v in os.environ.items() if k != "INFINITO_SERVICES_DISABLED"} + env = {**os.environ, "INFINITO_SERVICES_DISABLED": ""} with unittest.mock.patch.dict(os.environ, env, clear=True): apply_services_disabled_from_env(self.host_vars, roles_dir=self.roles_dir) self.assertEqual(self._read(), {"applications": {}}) diff --git a/tests/unit/roles/sys-ctl-cln-bkps/files/test_script.py b/tests/unit/roles/sys-ctl-cln-bkps/files/test_script.py index 101e34e0e4..9ac49b8ea6 100644 --- a/tests/unit/roles/sys-ctl-cln-bkps/files/test_script.py +++ b/tests/unit/roles/sys-ctl-cln-bkps/files/test_script.py @@ -20,7 +20,7 @@ def _resolved_repo_root() -> Path: (used by some CI fixtures); otherwise the canonical ``PROJECT_ROOT`` constant from the package ``__init__.py`` is returned. """ - env = os.environ.get("INFINITO_REPO_ROOT") + env = os.environ.get("INFINITO_REPO_ROOT") # nocheck: test-fixture if env: return Path(env).expanduser().resolve() return PROJECT_ROOT diff --git a/tests/unit/roles/sys-svc-compose-ca/files/test_compose_ca.py b/tests/unit/roles/sys-svc-compose-ca/files/test_compose_ca.py index c45e5daede..de9b0019d6 100644 --- a/tests/unit/roles/sys-svc-compose-ca/files/test_compose_ca.py +++ b/tests/unit/roles/sys-svc-compose-ca/files/test_compose_ca.py @@ -42,7 +42,7 @@ class TestComposeCaInject(unittest.TestCase): "!", "grep", "-qF", - "INFINITO_TAIGA_AUTH_CONFIG", + "INFINITO_TAIGA_AUTH_CONFIG", # nocheck: test-fixture "/taiga-back/settings/config.py", ";", "then", @@ -501,7 +501,7 @@ class TestComposeCaInject(unittest.TestCase): "!", "grep", "-qF", - "INFINITO_TAIGA_AUTH_CONFIG", + "INFINITO_TAIGA_AUTH_CONFIG", # nocheck: test-fixture "/taiga-back/settings/config.py", ";", "then", diff --git a/tests/unit/roles/sys-svc-compose/files/test_compose.py b/tests/unit/roles/sys-svc-compose/files/test_compose.py index c7c558dbe4..2f79c90572 100644 --- a/tests/unit/roles/sys-svc-compose/files/test_compose.py +++ b/tests/unit/roles/sys-svc-compose/files/test_compose.py @@ -31,7 +31,7 @@ class TestInfinitoComposeWrapper(unittest.TestCase): def setUp(self): # Disable the wrapper's cache-override branch; tests use mocked is_file. self._env_patch = patch.dict( - os.environ, {"INFINITO_PACKAGE_CACHE_FRONTEND_IP": ""}, clear=False + os.environ, {"INFINITO_CACHE_PACKAGE_FRONTEND_IP": ""}, clear=False ) self._env_patch.start() diff --git a/utils/env/handlers/infinito_image_repository.py b/utils/env/handlers/infinito_image_repository.py index be08ecafee..aff0c7356c 100644 --- a/utils/env/handlers/infinito_image_repository.py +++ b/utils/env/handlers/infinito_image_repository.py @@ -20,7 +20,9 @@ COMMENT = ( def apply(eb: EnvBuilder, ctx: BuildContext) -> None: if not ctx.on_gha: return - repo_name = os.environ.get("INFINITO_IMAGE_REPOSITORY", "").strip() + repo_name = os.environ.get( + "INFINITO_IMAGE_REPOSITORY", "" + ).strip() # nocheck: handler-bootstrap-read if not repo_name: repo_name = run_helper( ["bash", "scripts/meta/resolve/repository/name.sh"], diff --git a/utils/env/handlers/infinito_inventory.py b/utils/env/handlers/infinito_inventory.py index 22c8758fed..41a0ab01e2 100644 --- a/utils/env/handlers/infinito_inventory.py +++ b/utils/env/handlers/infinito_inventory.py @@ -27,7 +27,9 @@ HOST_VARS_FILE_COMMENT = ( def apply(eb: EnvBuilder, ctx: BuildContext) -> None: - inventory_dir = os.environ.get("INFINITO_INVENTORY_DIR", "").strip() + inventory_dir = os.environ.get( + "INFINITO_INVENTORY_DIR", "" + ).strip() # nocheck: handler-bootstrap-read if not inventory_dir: inventory_dir = run_helper( ["bash", "scripts/inventory/resolve.sh"], diff --git a/utils/env/handlers/infinito_package_cache_admin_password.py b/utils/env/handlers/infinito_package_cache_admin_password.py index 1b47d5345f..8dce998746 100644 --- a/utils/env/handlers/infinito_package_cache_admin_password.py +++ b/utils/env/handlers/infinito_package_cache_admin_password.py @@ -1,4 +1,4 @@ -"""INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD: stable per-host Nexus admin +"""INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD: stable per-host Nexus admin password: sha256(host_path:hostname), truncated to 32 hex chars.""" from __future__ import annotations @@ -12,7 +12,7 @@ from utils.env.runtime import hostname if TYPE_CHECKING: from utils.env.builder import BuildContext, EnvBuilder -KEY = "INFINITO_PACKAGE_CACHE_ADMIN_PASSWORD" +KEY = "INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD" COMMENT = ( "Stable per-host Nexus admin password: sha256(host_path:hostname), " "truncated to 32 hex chars." @@ -24,6 +24,6 @@ def apply(eb: EnvBuilder, ctx: BuildContext) -> None: if existing_pw: eb.set(KEY, existing_pw, comment=COMMENT) return - seed = f"{eb.get('INFINITO_PACKAGE_CACHE_HOST_PATH')}:{hostname()}" + seed = f"{eb.get('INFINITO_CACHE_PACKAGE_HOST_PATH')}:{hostname()}" digest = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:32] eb.set(KEY, digest, comment=COMMENT) diff --git a/utils/env/handlers/infinito_package_cache_blobstore_max.py b/utils/env/handlers/infinito_package_cache_blobstore_max.py index 933dcf6823..6ffd907a5f 100644 --- a/utils/env/handlers/infinito_package_cache_blobstore_max.py +++ b/utils/env/handlers/infinito_package_cache_blobstore_max.py @@ -1,5 +1,5 @@ -"""INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX: Nexus blobstore quota (half of -free disk at INFINITO_PACKAGE_CACHE_HOST_PATH, floor 2g).""" +"""INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX: Nexus blobstore quota (half of +free disk at INFINITO_CACHE_PACKAGE_HOST_PATH, floor 2g).""" from __future__ import annotations @@ -11,10 +11,10 @@ from utils.env.runtime import df_avail_gb if TYPE_CHECKING: from utils.env.builder import BuildContext, EnvBuilder -KEY = "INFINITO_PACKAGE_CACHE_BLOBSTORE_MAX" +KEY = "INFINITO_CACHE_PACKAGE_BLOBSTORE_MAX" COMMENT = ( "Nexus blobstore quota (half of free disk at " - "INFINITO_PACKAGE_CACHE_HOST_PATH, floor 2g)." + "INFINITO_CACHE_PACKAGE_HOST_PATH, floor 2g)." ) @@ -23,5 +23,5 @@ def apply(eb: EnvBuilder, ctx: BuildContext) -> None: if existing_blob: eb.set(KEY, existing_blob, comment=COMMENT) return - avail = df_avail_gb(eb.get("INFINITO_PACKAGE_CACHE_HOST_PATH")) or 4 + avail = df_avail_gb(eb.get("INFINITO_CACHE_PACKAGE_HOST_PATH")) or 4 eb.set(KEY, f"{max(avail // 2, 2)}g", comment=COMMENT) diff --git a/utils/env/handlers/infinito_package_cache_direct_mem.py b/utils/env/handlers/infinito_package_cache_direct_mem.py index a57d0c7f45..f2d1734ef9 100644 --- a/utils/env/handlers/infinito_package_cache_direct_mem.py +++ b/utils/env/handlers/infinito_package_cache_direct_mem.py @@ -1,5 +1,5 @@ -"""INFINITO_PACKAGE_CACHE_DIRECT_MEM: Nexus direct-memory budget; tied to -INFINITO_PACKAGE_CACHE_HEAP.""" +"""INFINITO_CACHE_PACKAGE_DIRECT_MEM: Nexus direct-memory budget; tied to +INFINITO_CACHE_PACKAGE_HEAP.""" from __future__ import annotations @@ -8,9 +8,9 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from utils.env.builder import BuildContext, EnvBuilder -KEY = "INFINITO_PACKAGE_CACHE_DIRECT_MEM" -COMMENT = "Nexus direct-memory budget; tied to INFINITO_PACKAGE_CACHE_HEAP." +KEY = "INFINITO_CACHE_PACKAGE_DIRECT_MEM" +COMMENT = "Nexus direct-memory budget; tied to INFINITO_CACHE_PACKAGE_HEAP." def apply(eb: EnvBuilder, ctx: BuildContext) -> None: - eb.setdefault(KEY, eb.get("INFINITO_PACKAGE_CACHE_HEAP"), comment=COMMENT) + eb.setdefault(KEY, eb.get("INFINITO_CACHE_PACKAGE_HEAP"), comment=COMMENT) diff --git a/utils/env/handlers/infinito_package_cache_heap.py b/utils/env/handlers/infinito_package_cache_heap.py index d47d7f5feb..f47e04a815 100644 --- a/utils/env/handlers/infinito_package_cache_heap.py +++ b/utils/env/handlers/infinito_package_cache_heap.py @@ -1,4 +1,4 @@ -"""INFINITO_PACKAGE_CACHE_HEAP: Nexus JVM heap size (half of MemAvailable, +"""INFINITO_CACHE_PACKAGE_HEAP: Nexus JVM heap size (half of MemAvailable, cap 2g, floor 1g).""" from __future__ import annotations @@ -11,7 +11,7 @@ from utils.env.runtime import mem_available_mb if TYPE_CHECKING: from utils.env.builder import BuildContext, EnvBuilder -KEY = "INFINITO_PACKAGE_CACHE_HEAP" +KEY = "INFINITO_CACHE_PACKAGE_HEAP" COMMENT = "Nexus JVM heap size (half of MemAvailable, cap 2g, floor 1g)." diff --git a/utils/env/handlers/infinito_registry_cache_max_size.py b/utils/env/handlers/infinito_registry_cache_max_size.py index 10030292e2..f2a0f5cab8 100644 --- a/utils/env/handlers/infinito_registry_cache_max_size.py +++ b/utils/env/handlers/infinito_registry_cache_max_size.py @@ -1,5 +1,5 @@ -"""INFINITO_REGISTRY_CACHE_MAX_SIZE: registry-cache soft cap (half of -free disk at INFINITO_REGISTRY_CACHE_HOST_PATH, floor 1g).""" +"""INFINITO_CACHE_REGISTRY_MAX_SIZE: registry-cache soft cap (half of +free disk at INFINITO_CACHE_REGISTRY_HOST_PATH, floor 1g).""" from __future__ import annotations @@ -11,15 +11,15 @@ from utils.env.runtime import df_avail_gb if TYPE_CHECKING: from utils.env.builder import BuildContext, EnvBuilder -KEY = "INFINITO_REGISTRY_CACHE_MAX_SIZE" +KEY = "INFINITO_CACHE_REGISTRY_MAX_SIZE" COMMENT = ( "Registry-cache soft cap (half of free disk at " - "INFINITO_REGISTRY_CACHE_HOST_PATH, floor 1g)." + "INFINITO_CACHE_REGISTRY_HOST_PATH, floor 1g)." ) def apply(eb: EnvBuilder, ctx: BuildContext) -> None: - host_path = eb.get("INFINITO_REGISTRY_CACHE_HOST_PATH") + host_path = eb.get("INFINITO_CACHE_REGISTRY_HOST_PATH") if not host_path: return existing = os.environ.get(KEY, "").strip() diff --git a/utils/env/handlers/passthrough.py b/utils/env/handlers/passthrough.py index 35d53a6abc..75b7775402 100644 --- a/utils/env/handlers/passthrough.py +++ b/utils/env/handlers/passthrough.py @@ -15,6 +15,8 @@ STATIC_KEYS = ( "INFINITO_SUBNET", "INFINITO_GATEWAY", "INFINITO_DISTRO", + "INFINITO_DNS_DOMAIN", + "INFINITO_DNS_HOSTS_FILE", "INFINITO_CONTAINER", "INFINITO_CONTAINER_NO_CA", "INFINITO_RUNNING_ON_ACT", @@ -50,15 +52,20 @@ STATIC_KEYS = ( "INFINITO_VENV_NAME", "PYTHONPATH", "INVENTORY_VARS_FILE", - "INFINITO_REGISTRY_CACHE_HOST_PATH", - "INFINITO_REGISTRY_CACHE_CA_HOST_PATH", - "INFINITO_PACKAGE_CACHE_HOST_PATH", - "INFINITO_PACKAGE_CACHE_PORT", - "INFINITO_PACKAGE_CACHE_FRONTEND_CA_DIR", - "INFINITO_PACKAGE_CACHE_FRONTEND_CERTS_DIR", - "INFINITO_PACKAGE_CACHE_FRONTEND_IP", - "INFINITO_PACKAGE_CACHE_FRONTEND_INIT_IMAGE", - "INFINITO_PACKAGE_CACHE_MAX_AGE_MIN", + "INFINITO_CACHE_REGISTRY_HOST_PATH", + "INFINITO_CACHE_REGISTRY_CA_HOST_PATH", + "INFINITO_CACHE_PACKAGE_HOST_PATH", + "INFINITO_CACHE_PACKAGE_PORT", + "INFINITO_CACHE_PACKAGE_APT_LIST", + "INFINITO_CACHE_PACKAGE_FRONTEND_CA_DIR", + "INFINITO_CACHE_PACKAGE_FRONTEND_CA_FILE", + "INFINITO_CACHE_PACKAGE_FRONTEND_CERTS_DIR", + "INFINITO_CACHE_PACKAGE_FRONTEND_IP", + "INFINITO_CACHE_PACKAGE_FRONTEND_INIT_IMAGE", + "INFINITO_CACHE_PACKAGE_MAX_AGE_MIN", + "INFINITO_CACHE_PACKAGE_NPMRC", + "INFINITO_CACHE_PACKAGE_PIP_CONF", + "INFINITO_CACHE_REGISTRY_PROXY_CONF", "INFINITO_IMAGE_TAG", ) diff --git a/utils/update/base.py b/utils/update/base.py index 41d67d34bf..2900d2eaa7 100644 --- a/utils/update/base.py +++ b/utils/update/base.py @@ -67,4 +67,4 @@ def latest_semver(tags: list[str], depth: int, flavor: str = "") -> str | None: def resolve_max_fetch_workers() -> int: - return int(os.environ.get("INFINITO_WORKER_FETCH", "1")) + return int(os.environ["INFINITO_WORKER_FETCH"])