From fefaaf91c8f3e0da1a08e5301cfb2c8535259635 Mon Sep 17 00:00:00 2001 From: Adrien Mannocci Date: Fri, 23 Oct 2026 11:09:23 +0200 Subject: [PATCH] feat: split the package into ignity-core and ignity-utils ignity-core ships everything but fix-perms and depends on the virtual ignity-utils-impl. ignity-utils ships the execline fix-perms and provides it, like ignity-utils-rs, which can be installed instead. Both replace the single ignity package of 0.1.0. The nfpm configs are now one per package, with ARCH and VERSION given by `poe package:`: the version only lives in pyproject.toml. --- .claude/CLAUDE.md | 6 +- README.md | 28 +++++--- distributions/packages/nfpm.arm64.yaml | 66 ------------------- .../{nfpm.amd64.yaml => nfpm.core.yaml} | 43 +++++++++--- distributions/packages/nfpm.utils.yaml | 37 +++++++++++ scripts/binds/nfpm.py | 9 ++- scripts/tasks/package.py | 17 +++-- scripts/tasks/release.py | 16 +---- 8 files changed, 114 insertions(+), 108 deletions(-) delete mode 100644 distributions/packages/nfpm.arm64.yaml rename distributions/packages/{nfpm.amd64.yaml => nfpm.core.yaml} (53%) create mode 100644 distributions/packages/nfpm.utils.yaml diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index ccfbf66..df1a694 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -25,12 +25,12 @@ uv run poe test tests/test_boot.py::test_execlineb_installed # Single test ```bash uv run poe package # Create dist/ignity.tar.gz tarball from src/ uv run poe build # Static (musl) execline/s6/s6-portable-utils + src/ into dist/ignity, in a container -uv run poe package:deb # Also package:rpm, package:apk (Linux only): nfpm, dist/ignity--linux-. +uv run poe package:deb # Also package:rpm, package:apk (Linux only): nfpm, dist/ignity-{core,utils}--linux-. IGNITY_INSTALL=package DOCKER_BASE_IMAGE=alpine:3 uv run poe test # Suite against the package in dist/ ``` **Releasing** (GitHub Actions, workflow_dispatch, same flow as techcode-io/terranova): -`Pre release` (version) opens a bump PR (`release:pre`) -> `Release` (next `x.y.z-dev`) reuses CI, tags `x.y.z` (no `v`), attaches the packages (`release`) and opens the next iteration PR (`release:post`). The version lives in `pyproject.toml` and `distributions/packages/nfpm.{amd64,arm64}.yaml`. +`Pre release` (version) opens a bump PR (`release:pre`) -> `Release` (next `x.y.z-dev`) reuses CI, tags `x.y.z` (no `v`), attaches the packages (`release`) and opens the next iteration PR (`release:post`). The version lives in `pyproject.toml` only (passed to nfpm as `VERSION`, with `ARCH`). All commands require: - uv (modern Python package manager) @@ -88,7 +88,7 @@ Containers of a pod share files through a user-mounted volume. No env is handed **Installation:** - `/usr/src/install-ignity.sh` — Compiles and installs s6, execline, skalibs from source (Debian/Ubuntu). Its `readonly *_VERSION`/`*_URL` are the single source of truth for the skarnet versions, also read by `distributions/build/build-ignity.sh` and bumped by `poe project:upgrade` - execline, s6 and s6-portable-utils live in `/usr/lib/ignity/{bin,libexec}` (both installs, built with `--enable-absolute-paths`), never in `/bin`. `/init` puts `/usr/lib/ignity/bin` first in PATH via `/usr/lib/ignity/libexec/with-ignity-path`; entry points that can run outside /init (`preboot`, `fix-perms`, `gen-accounts`, `load-envfile`, `with-retries`) start with it and find the other tools through the caller's PATH. Ignity's own scripts use `#!/usr/lib/ignity/bin/execlineb`; `/bin/execlineb` is only a compat link for user scripts, created when absent -- Linux packages (deb/rpm/apk, amd64/arm64): `distributions/build/` builds the bundle (static binaries), `distributions/packages/nfpm..yaml` packages it, `postinstall.sh`/`preremove.sh` manage the `/bin/execlineb` link. No Conflicts/Provides with the distribution's execline/s6 +- Linux packages (deb/rpm/apk, amd64/arm64): `distributions/build/` builds the bundle (static binaries), `distributions/packages/nfpm.{core,utils}.yaml` package it in two: `ignity-core` (all but fix-perms, depends on the virtual `ignity-utils-impl`) and `ignity-utils` (execline `fix-perms`, provides `ignity-utils-impl`; techcode-io/ignity-utils-rs is the Rust alternative providing it too). Both conflict with/replace the `ignity` package of <= 0.1.0. `postinstall.sh`/`preremove.sh` manage the `/bin/execlineb` link (also rpm posttrans, as the obsoleted `ignity` preun runs after `%post`). No Conflicts/Provides with the distribution's execline/s6 ## Container Configuration (User-Provided) diff --git a/README.md b/README.md index 8803948..6345fd5 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ uv run poe package:apk ``` - `build` compiles static (musl) `execline`, `s6` and `s6-portable-utils` in a container, into `dist/ignity`. -- `package:` packages it with [nfpm](https://nfpm.goreleaser.com) into `dist/ignity--linux-.`. +- `package:` packages it with [nfpm](https://nfpm.goreleaser.com) into `dist/ignity-core--linux-.` and `dist/ignity-utils--linux-.`. - To run the tests with ignity installed from these packages instead of from source: ```bash @@ -133,35 +133,47 @@ IGNITY_INSTALL=package DOCKER_BASE_IMAGE=alpine:3 uv run poe test - Each [release](https://github.com/techcode-io/ignity/releases) ships `.deb`, `.rpm` and `.apk` packages for `amd64` and `arm64`. - They bundle static builds of `execline`, `s6` and `s6-portable-utils`: nothing is compiled in your image. - They install them in `/usr/lib/ignity` (see "Where ignity installs its binaries"): nothing conflicts with the packages of the distribution. +- ignity comes in two packages, to install together: + - `ignity-core`: the boot stages, `execline`, `s6`, `s6-portable-utils` and the utilities, except `fix-perms`. + - `ignity-utils`: the execline implementation of `fix-perms`. ```Dockerfile # Debian / Ubuntu ARG IGNITY_VERSION=0.1.0 ARG TARGETARCH -ADD https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-${IGNITY_VERSION}-linux-${TARGETARCH}.deb /tmp/ignity.deb +ADD https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-core-${IGNITY_VERSION}-linux-${TARGETARCH}.deb \ + https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-utils-${IGNITY_VERSION}-linux-${TARGETARCH}.deb \ + /tmp/ignity/ RUN apt-get update \ - && apt-get install -y --no-install-recommends /tmp/ignity.deb \ - && rm -rf /tmp/ignity.deb /var/lib/apt/lists/* + && apt-get install -y --no-install-recommends /tmp/ignity/*.deb \ + && rm -rf /tmp/ignity /var/lib/apt/lists/* ``` ```Dockerfile # Fedora / RHEL / UBI ARG IGNITY_VERSION=0.1.0 ARG TARGETARCH -ADD https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-${IGNITY_VERSION}-linux-${TARGETARCH}.rpm /tmp/ignity.rpm -RUN dnf install -y /tmp/ignity.rpm && dnf clean all && rm -f /tmp/ignity.rpm +ADD https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-core-${IGNITY_VERSION}-linux-${TARGETARCH}.rpm \ + https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-utils-${IGNITY_VERSION}-linux-${TARGETARCH}.rpm \ + /tmp/ignity/ +RUN dnf install -y /tmp/ignity/*.rpm && dnf clean all && rm -rf /tmp/ignity ``` ```Dockerfile # Alpine (the packages aren't signed, their provenance is attested instead) ARG IGNITY_VERSION=0.1.0 ARG TARGETARCH -ADD https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-${IGNITY_VERSION}-linux-${TARGETARCH}.apk /tmp/ignity.apk -RUN apk add --no-cache --allow-untrusted /tmp/ignity.apk && rm -f /tmp/ignity.apk +ADD https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-core-${IGNITY_VERSION}-linux-${TARGETARCH}.apk \ + https://github.com/techcode-io/ignity/releases/download/${IGNITY_VERSION}/ignity-utils-${IGNITY_VERSION}-linux-${TARGETARCH}.apk \ + /tmp/ignity/ +RUN apk add --no-cache --allow-untrusted /tmp/ignity/*.apk && rm -rf /tmp/ignity ``` - `TARGETARCH` is set by `docker buildx` and `podman build`: otherwise, set it to `amd64` or `arm64`. - The provenance of a package can be checked with `gh attestation verify --repo techcode-io/ignity`. +- `ignity-core` depends on the virtual `ignity-utils-impl` package, provided by `ignity-utils`. + [ignity-utils-rs](https://github.com/techcode-io/ignity-utils-rs) provides it too, with a static Rust `fix-perms` at the same path: install it instead of `ignity-utils`. +- Up to `0.1.0`, ignity came as a single `ignity` package: `ignity-core` and `ignity-utils` replace it on upgrade (on Alpine, `apk del ignity` first: they conflict with it). ### How to integrate ignity in your project from source diff --git a/distributions/packages/nfpm.arm64.yaml b/distributions/packages/nfpm.arm64.yaml deleted file mode 100644 index 12dec85..0000000 --- a/distributions/packages/nfpm.arm64.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: "ignity" -arch: "arm64" -version: "0.1.1-dev" -section: "default" -priority: "extra" -maintainer: "Adrien Mannocci " -description: | - Ignity is a dead simple process supervision system for containers based on s6. - It bundles static builds of execline, s6 and s6-portable-utils. -vendor: "techcode.io" -homepage: "https://github.com/techcode-io/ignity" -license: "Apache-2.0" -overrides: - # useradd, groupadd and getent (preboot, gen-accounts), GNU find (fix-perms) and grep (gen-accounts) - deb: - depends: - - "passwd" - - "findutils" - - "grep" - rpm: - depends: - - "shadow-utils" - - "findutils" - - "grep" - apk: - depends: - - "shadow" - - "findutils" - - "grep" -# execline, s6 and s6-portable-utils are installed in /usr/lib/ignity: nothing conflicts with the -# distribution, /init puts them first in PATH -scripts: - postinstall: ./distributions/packages/postinstall.sh - preremove: ./distributions/packages/preremove.sh -contents: - - src: ./dist/ignity/usr/lib/ignity - dst: /usr/lib/ignity - type: tree - - - src: ./dist/ignity/init - dst: /init - - - src: ./dist/ignity/etc/s6 - dst: /etc/s6 - type: tree - - - src: ./dist/ignity/usr/bin/* - dst: /usr/bin/ - - - dst: /etc/ignity/envs - type: dir - - - dst: /etc/ignity/init - type: dir - - - dst: /etc/ignity/services - type: dir - - - dst: /etc/ignity/perms - type: dir - - - dst: /etc/ignity/finalize - type: dir - - - dst: /run/ignity - type: dir diff --git a/distributions/packages/nfpm.amd64.yaml b/distributions/packages/nfpm.core.yaml similarity index 53% rename from distributions/packages/nfpm.amd64.yaml rename to distributions/packages/nfpm.core.yaml index a36ed51..b1d4c22 100644 --- a/distributions/packages/nfpm.amd64.yaml +++ b/distributions/packages/nfpm.core.yaml @@ -1,6 +1,7 @@ -name: "ignity" -arch: "amd64" -version: "0.1.1-dev" +# ARCH and VERSION are set by `poe package:` +name: "ignity-core" +arch: "${ARCH}" +version: "${VERSION}" section: "default" priority: "extra" maintainer: "Adrien Mannocci " @@ -10,28 +11,40 @@ description: | vendor: "techcode.io" homepage: "https://github.com/techcode-io/ignity" license: "Apache-2.0" +# Takes over the single package of ignity <= 0.1.0 +conflicts: + - "ignity" +replaces: + - "ignity" overrides: - # useradd, groupadd and getent (preboot, gen-accounts), GNU find (fix-perms) and grep (gen-accounts) + # fix-perms (ignity-utils or ignity-utils-rs, which both provide ignity-utils-impl), useradd, + # groupadd and getent (preboot, gen-accounts) and grep (gen-accounts) deb: depends: + - "ignity-utils-impl" - "passwd" - - "findutils" - "grep" rpm: depends: + - "ignity-utils-impl" - "shadow-utils" - - "findutils" - "grep" apk: + # nfpm drops conflicts for apk, where they are `!` dependencies depends: + - "ignity-utils-impl" - "shadow" - - "findutils" - "grep" + - "!ignity" # execline, s6 and s6-portable-utils are installed in /usr/lib/ignity: nothing conflicts with the # distribution, /init puts them first in PATH scripts: postinstall: ./distributions/packages/postinstall.sh preremove: ./distributions/packages/preremove.sh +rpm: + scripts: + # The obsoleted ignity package is erased after %post, and its %preun removes the link + posttrans: ./distributions/packages/postinstall.sh contents: - src: ./dist/ignity/usr/lib/ignity dst: /usr/lib/ignity @@ -44,8 +57,20 @@ contents: dst: /etc/s6 type: tree - - src: ./dist/ignity/usr/bin/* - dst: /usr/bin/ + - src: ./dist/ignity/usr/bin/gen-accounts + dst: /usr/bin/gen-accounts + + - src: ./dist/ignity/usr/bin/load-envfile + dst: /usr/bin/load-envfile + + - src: ./dist/ignity/usr/bin/preboot + dst: /usr/bin/preboot + + - src: ./dist/ignity/usr/bin/with-env + dst: /usr/bin/with-env + + - src: ./dist/ignity/usr/bin/with-retries + dst: /usr/bin/with-retries - dst: /etc/ignity/envs type: dir diff --git a/distributions/packages/nfpm.utils.yaml b/distributions/packages/nfpm.utils.yaml new file mode 100644 index 0000000..7f60243 --- /dev/null +++ b/distributions/packages/nfpm.utils.yaml @@ -0,0 +1,37 @@ +# ARCH and VERSION are set by `poe package:` +name: "ignity-utils" +arch: "${ARCH}" +version: "${VERSION}" +section: "default" +priority: "extra" +maintainer: "Adrien Mannocci " +description: | + Execline implementation of the ignity utilities: fix-perms. + ignity-utils-rs is a drop-in replacement, written in Rust. +vendor: "techcode.io" +homepage: "https://github.com/techcode-io/ignity" +license: "Apache-2.0" +# What ignity-core depends on, also provided by ignity-utils-rs +provides: + - "ignity-utils-impl" +# Takes over fix-perms from the single package of ignity <= 0.1.0 +conflicts: + - "ignity" + - "ignity-utils-rs" +replaces: + - "ignity" +# execlineb (shebang, from ignity-core) and GNU find (fix-perms) +depends: + - "ignity-core" + - "findutils" +overrides: + apk: + # nfpm drops conflicts for apk, where they are `!` dependencies + depends: + - "ignity-core" + - "findutils" + - "!ignity" + - "!ignity-utils-rs" +contents: + - src: ./dist/ignity/usr/bin/fix-perms + dst: /usr/bin/fix-perms diff --git a/scripts/binds/nfpm.py b/scripts/binds/nfpm.py index 65b56d0..8135a04 100644 --- a/scripts/binds/nfpm.py +++ b/scripts/binds/nfpm.py @@ -1,11 +1,17 @@ +from collections.abc import Mapping + from scripts.binds.container import Container class Nfpm(Container): """Represents a bind to the `goreleaser/nfpm` packaging container.""" - def package(self, config: str, target: str, packager: str) -> None: + def package( + self, config: str, target: str, packager: str, env: Mapping[str, str] + ) -> None: """Package a build bundle into an OS package using nfpm.""" + # Expanded by nfpm in the config + env_args = [arg for key, value in env.items() for arg in ("-e", f"{key}={value}")] self._exec( "run", "--rm", @@ -13,6 +19,7 @@ def package(self, config: str, target: str, packager: str) -> None: ".:/tmp", "-w", "/tmp", + *env_args, "goreleaser/nfpm", "package", "--config", diff --git a/scripts/tasks/package.py b/scripts/tasks/package.py index 0ec0400..acf95d0 100644 --- a/scripts/tasks/package.py +++ b/scripts/tasks/package.py @@ -5,11 +5,13 @@ from scripts.binds.nfpm import Nfpm DISTRIBUTIONS_PACKAGES_PATH = "distributions/packages" +# ignity-core, and ignity-utils providing ignity-utils-impl (like ignity-utils-rs) +PACKAGES = ("core", "utils") PYPROJECT_PATH = Path("pyproject.toml") def __build_package(packager: str) -> None: - """Package the current architecture bundle with nfpm.""" + """Package the current architecture bundle with nfpm, one package per PACKAGES.""" arch = platform.machine().lower() arch = "amd64" if arch == "x86_64" else arch arch = "arm64" if arch == "aarch64" else arch @@ -18,11 +20,14 @@ def __build_package(packager: str) -> None: with PYPROJECT_PATH.open("rb") as file: version = tomllib.load(file)["project"]["version"] - Nfpm().package( - config=f"{DISTRIBUTIONS_PACKAGES_PATH}/nfpm.{arch}.yaml", - target=f"/tmp/dist/ignity-{version}-linux-{arch}.{packager}", - packager=packager, - ) + nfpm = Nfpm() + for package in PACKAGES: + nfpm.package( + config=f"{DISTRIBUTIONS_PACKAGES_PATH}/nfpm.{package}.yaml", + target=f"/tmp/dist/ignity-{package}-{version}-linux-{arch}.{packager}", + packager=packager, + env={"ARCH": arch, "VERSION": version}, + ) def deb() -> None: diff --git a/scripts/tasks/release.py b/scripts/tasks/release.py index eca45d0..c1ba382 100644 --- a/scripts/tasks/release.py +++ b/scripts/tasks/release.py @@ -10,12 +10,6 @@ from scripts.binds.git import Git PYPROJECT_PATH: Final[Path] = Path("pyproject.toml") -DISTRIBUTIONS_NFPM_AMD64_PATH: Final[Path] = Path( - "distributions/packages/nfpm.amd64.yaml" -) -DISTRIBUTIONS_NFPM_ARM64_PATH: Final[Path] = Path( - "distributions/packages/nfpm.arm64.yaml" -) # Packages built for the release: deb, rpm and apk RELEASE_ASSETS_PATTERNS: Final[tuple[str, ...]] = ( "ignity*.deb", @@ -47,20 +41,12 @@ def __update_file_version(file_path: Path, pattern: str, replacement: str) -> No def __set_version(version: str) -> None: - """Update version in pyproject.toml and the nfpm package configs.""" + """Update version in pyproject.toml, which the nfpm package configs are given.""" # Update project version __update_file_version( PYPROJECT_PATH, r"version = \"(.+)\"", f'version = "{version}"' ) - # Update nfpm package versions - __update_file_version( - DISTRIBUTIONS_NFPM_AMD64_PATH, r"version: \"(.+)\"", f'version: "{version}"' - ) - __update_file_version( - DISTRIBUTIONS_NFPM_ARM64_PATH, r"version: \"(.+)\"", f'version: "{version}"' - ) - def __project_version() -> str: """Return the project version from pyproject.toml."""