Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ uv run poe test tests/test_boot.py::test_execlineb_installed # Single test
**Building:**
```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-<version>-linux-<arch>.<format>
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`.

All commands require:
- uv (modern Python package manager)
- podman (container runtime for tests)
Expand Down Expand Up @@ -66,7 +72,7 @@ Containers of a pod share files through a user-mounted volume. No env is handed
## Key Components

**Boot Stages:**
- `/init` — Entry point (execlineb wrapper calling stage1)
- `/init` — Entry point (execlineb wrapper calling stage1, with `/usr/lib/ignity/bin` first in PATH)
- `/etc/s6/boot/stage{1,2,3}` — Core boot logic
- `/etc/s6/boot/stage1-envs`, `/etc/s6/boot/stage2-{copy,init,services,cmd}` — Boot sub-scripts
- `/etc/s6/services/.s6-svscan/{crash,finish}` — s6 supervision control
Expand All @@ -80,7 +86,9 @@ Containers of a pod share files through a user-mounted volume. No env is handed
- `/usr/bin/with-retries` — Retry command execution with backoff

**Installation:**
- `/usr/src/install-ignity.sh` — Compiles and installs s6, execline, skalibs from source
- `/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.<arch>.yaml` packages it, `postinstall.sh`/`preremove.sh` manage the `/bin/execlineb` link. No Conflicts/Provides with the distribution's execline/s6

## Container Configuration (User-Provided)

Expand All @@ -105,9 +113,10 @@ Scripts use numbered prefixes (00-, 01-, etc.) for deterministic ordering. Conve
- Each module sets `IMAGE_KIND`; the shared `image` fixture (conftest) builds it from `tests/fixtures/<kind>/`: its own `Containerfile.tpl` if present, else ignity + `rootfs/` (see `DEFAULT_CONTAINERFILE`). Extra images can still be built with `build_image()`
- Test fixtures include rootfs overlays with pre-configured services/scripts
- conftest.py provides the `image` fixture, `build_image(kind)`, `run_in_container(image, cmd, extra_args)`, `env_args(**env)` (`-e` arguments) and `READ_ONLY` (read-only root arguments)
- Containerfile templates install ignity with `{{IGNITY_INSTALL}}`: from source by default, from the dist/ package matching `DOCKER_BASE_IMAGE` (debian/ubuntu/fedora/alpine) with `IGNITY_INSTALL=package`

**Test Flow:**
1. `build_image()` renders Containerfile.tpl (substitutes `{{DOCKER_BASE_IMAGE}}`), builds with random tag via podman
1. `build_image()` renders Containerfile.tpl (substitutes `{{DOCKER_BASE_IMAGE}}` and `{{IGNITY_INSTALL}}`), builds with random tag via podman
2. `run_in_container()` executes shell command inside container with `/init` entrypoint
3. Tests assert on exit codes and stdout content

Expand All @@ -126,9 +135,10 @@ Follow conventional commits: `type: description` (types: build, ci, docs, feat,

**Key Files:**
- `pyproject.toml` — Project manifest, poe tasks, uv config
- `scripts/binds/` — Thin wrappers around container (docker/podman), nfpm, git, gh; `scripts/tasks/` — poe task implementations (build, package, release, project)
- `tests/conftest.py` — Pytest helpers, immutable tuples, type annotations
- `tests/fixtures/` — Containerfile templates and rootfs overlays (not actual test code)
- `.github/workflows/ci.yml` — Runs `uv run poe test` on PR/push
- `.github/workflows/ci.yml` — On PR/push (and reused by release): `poe test` (source), `package` per arch (native runners), `sanitize` runs the suite from the packages on debian/ubuntu/alpine/fedora

**Environment Variables (Runtime):**
- `IGNITY_CMD_WAIT_FOR_SERVICES` — Wait for services before CMD (0/1)
Expand Down
100 changes: 98 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name: CI

on:
workflow_call: ~
pull_request:
push:
branches:
Expand All @@ -10,14 +11,109 @@ on:
permissions:
contents: read

env:
# Ref: https://docs.astral.sh/uv/concepts/projects/sync/#checking-if-the-lockfile-is-up-to-date
UV_FROZEN: "true"

## Concurrency only allowed in the main branch.
## So old builds running for old commits within the same Pull Request are cancelled
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- name: Install environment
uses: ./.github/workflows/env-install

- name: Run tests
run: uv run poe test

# The bundle is packaged in the job building it: artifacts don't keep file modes
package:
permissions:
id-token: write
attestations: write
artifact-metadata: write
strategy:
matrix:
os:
- runner: ubuntu-24.04
target: linux-amd64
- runner: ubuntu-24.04-arm
target: linux-arm64
runs-on: ${{ matrix.os.runner }}
steps:
- uses: actions/checkout@v7

- name: Install environment
uses: ./.github/workflows/env-install

- name: Build
run: uv run poe build

- name: Build deb packages
run: uv run poe package:deb

- name: Build rpm packages
run: uv run poe package:rpm

- name: Build apk packages
run: uv run poe package:apk

- uses: actions/attest@v4
with:
subject-path: |
dist/*.deb
dist/*.rpm
dist/*.apk

- uses: actions/upload-artifact@v7
with:
name: ignity-package-${{ matrix.os.target }}
path: |
dist/*.deb
dist/*.rpm
dist/*.apk

# Run the whole suite with ignity installed from its package
sanitize:
needs:
- package
strategy:
fail-fast: false
matrix:
os:
- runner: ubuntu-24.04
target: linux-amd64
- runner: ubuntu-24.04-arm
target: linux-arm64
# Not ubuntu: its image already has a uid/gid 1000 user, which the tests map
base:
- debian:bookworm-slim
- alpine:3
- fedora:latest
runs-on: ${{ matrix.os.runner }}
steps:
- uses: actions/checkout@v7

- name: Install environment
uses: ./.github/workflows/env-install

- name: Install podman
run: command -v podman || { sudo apt-get update && sudo apt-get install -y podman; }

- uses: actions/download-artifact@v8
with:
name: ignity-package-${{ matrix.os.target }}
path: dist

- name: Run tests
run: uv run poe test
env:
IGNITY_INSTALL: package
DOCKER_BASE_IMAGE: ${{ matrix.base }}
16 changes: 16 additions & 0 deletions .github/workflows/env-install/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: Environment installation
description: Install uv, and dependencies
runs:
using: "composite"
steps:
- name: Install uv
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 #v10.3.0

- name: Install dependencies
run: |
# Enable frozen mode to avoid updating `uv.lock` in CI
echo "UV_FROZEN=true" >> "${GITHUB_ENV}"
uv sync
uv run poe env:configure
shell: "bash"
42 changes: 42 additions & 0 deletions .github/workflows/pre-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
name: Pre release

on:
workflow_dispatch:
inputs:
version:
description: "The version to release (e.g. 1.2.3). This workflow will automatically perform the required version bumps"
required: true

permissions:
contents: read

env:
# Ref: https://docs.astral.sh/uv/concepts/projects/sync/#checking-if-the-lockfile-is-up-to-date
UV_FROZEN: "true"

concurrency:
group: ${{ github.workflow }}

jobs:
pre-release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup github actions bot
uses: techcode-io/github-actions/setup-github-bot@setup-github-bot/v1

- name: Install environment
uses: ./.github/workflows/env-install

- name: Run pre-release step
run: uv run poe release:pre
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ inputs.version }}
76 changes: 76 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
name: Release

on:
workflow_dispatch:
inputs:
version:
description: "The next iteration version. This workflow will automatically perform the required version bumps"
required: true

permissions:
contents: read

env:
# Ref: https://docs.astral.sh/uv/concepts/projects/sync/#checking-if-the-lockfile-is-up-to-date
UV_FROZEN: "true"

concurrency:
group: ${{ github.workflow }}

jobs:
ci:
permissions:
contents: read
id-token: write
attestations: write
artifact-metadata: write
uses: ./.github/workflows/ci.yml
secrets: inherit

release:
runs-on: ubuntu-latest
needs:
- ci
permissions:
contents: write
steps:
- uses: actions/checkout@v7

- uses: actions/download-artifact@v8
with:
pattern: ignity-package-*
merge-multiple: true
path: dist

- name: Install environment
uses: ./.github/workflows/env-install

- name: Release
env:
GH_TOKEN: ${{ github.token }}
run: uv run poe release

post-release:
needs:
- release
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup github actions bot
uses: techcode-io/github-actions/setup-github-bot@setup-github-bot/v1

- name: Install environment
uses: ./.github/workflows/env-install

- name: Run post-release step
run: uv run poe release:post
env:
GH_TOKEN: ${{ github.token }}
NEXT_VERSION: ${{ inputs.version }}
Loading
Loading