.
├── mise.toml # mise config (tools + task discovery)
├── mise-tasks/ # self-contained file-tasks -> `mise <name>`
│ ├── build/ # mise build:image, build:chunked-image, build:iso
│ ├── publish/ # mise publish:image/publish:iso
│ ├── keys # mise keys (generate MOK + Cosign keys)
│ ├── verify-keys # mise verify-keys (validate MOK, Deno/TS)
│ ├── branding # mise branding (render logo assets, Deno/TS)
│ ├── syntax # mise syntax
│ └── lint/ # mise lint (+ lint:shell/deno/container/workflows/legal)
├── Containerfile # 2-stage bootc build
├── certs/ # public cert (mok.der/crt); mok.key = private!
├── dependencies.yaml # pinned versions of dependencies
├── image/setup/ # scripts that run INSIDE the image build
│ ├── stage1 # stage 1: runs numbered stage1.d scripts
│ ├── stage1.d/ # stage 1 setup steps, ordered by filename
│ ├── stage2 # stage 2: runs numbered stage2.d scripts
│ └── stage2.d/ # stage 2 setup steps, ordered by filename
├── iso/ # offline installer ISO Kickstart, rootfs and product.img branding
├── assets/branding/ # echocat branding sources + `mise branding`
├── docs/ # legal / NVIDIA / Xbox-firmware documentation
├── LICENSE # MIT (project's own files only)
├── NOTICE.md # third-party attributions
└── image/rootfs/ # files copied verbatim into the image (/)
└── usr/bin/enable-xone-firmware # local opt-in Xbox firmware activator
Two-stage container build (Containerfile):
- builder — compiles NVIDIA (akmod) + xone against the kernel of the base image and signs the modules with the MOK.
- final —
FROM kinoite:44, takes over the signed modules, installs the NVIDIA userspace + defaults, ships the public cert.
The result is a bootc image that an existing Kinoite system is switched to via
bootc switch / rpm-ostree rebase.
- Have Podman installed:
# Ubuntu/Debian sudo apt install -y podman # Fedora/Red Hat/CentOS sudo dnf install -y podman
- Have skopeo installed:
# Ubuntu/Debian sudo apt install -y skopeo # Fedora/Red Hat/CentOS sudo dnf install -y skopeo
- Have mise installed (will ensure all other required tools are present):
curl https://mise.run | sh
Note
Tasks are self-contained mise file-tasks in mise-tasks/; run mise tasks for the full list.
-
Ensure
./.envfile are present:COSIGN_PASSWORD=<the password> -
Ensure keys are present:
mise keys
-
Build the image (always built as the fixed local tag
:local, never pushed) Every build is fully up-to-date: the upstream base image is re-pulled if newer (--pull=newer) and no layer cache is reused (--no-cache), so the latest RPMs (RPM Fusion, NVIDIA, kernel-devel) are fetched every time. Runs rootless (fast native overlay diff); image lands in the user storage.mise build:image # ghcr.io/echocat/bluecat:local is created in podman -
Build the chunked image from
:local: Usesrpm-ostree compose build-chunked-oci --format-version 2for update-efficient layer reuse. The ISO and publish path use this image. The unchunked:localimage is removed by default after successful chunking.mise build:chunked-image # output/bluecat.oci is created with the bluecat:local-chunked referenceThe aggregate
mise buildkeeps this layout after the ISO build so a latermise publish:image ...can push it. The layout is replaced by the nextmise build:chunked-imagerun and is not deleted automatically after publish. -
Build the rolling offline installer ISO: Uses Fedora's Anaconda boot ISO plus
mkksisoto embed a Kickstart and the locally built chunked OCI layout as/images/bluecat.oci. The initial deployment is offline; the installed system records the release image ref for later updates.mise build:iso # output/bluecat.iso, output/bluecat.iso.sha256 and output/bluecat.iso.md5 are created -
Publish the rolling ISO to S3-compatible storage:
mise publish:iso # https://download.bluecat.echocat.org/latest/bluecat.iso -
Test image publishing/signing without touching release tags:
mise publish:image test local # pushes test-local-<UTC-timestamp>, test-local, sha256-<digest>.sig, # and test-local-<UTC-timestamp>.sig
.github/workflows/build.yaml builds the image, decides what to push and, for
release builds, publishes the rolling ISO:
| Trigger | Action |
|---|---|
push to main |
build image + build chunked image + build ISO + mise publish:image release + ISO publish |
schedule on main |
build image + build chunked image + build ISO + mise publish:image release + ISO publish |
PR labeled test image |
build image + build chunked image + build ISO + mise publish:image pr <number> |
| other branches / events | (not triggered) — build locally, no push |
The PR path only fires when the test image label is added to a PR
(labeled event); other labels do not trigger an image build.
The image is pushed to ghcr.io/echocat/bluecat. Authentication uses the
automatic GITHUB_TOKEN (the workflow grants it packages: write), so no
registry credentials need to be stored. Only the signing material is needed as
secrets:
Publishing recompresses output/bluecat.oci to zstd for the registry. The ISO
embeds the layout produced by mise build:chunked-image.
| Secret | Content |
|---|---|
MOK_KEY |
content of certs/mok.key (private) |
MOK_CRT |
content of certs/mok.crt |
MOK_DER_B64 |
base64 -w0 certs/mok.der |
COSIGN_KEY |
content of certs/cosign.key (private) |
COSIGN_PUB |
content of certs/cosign.pub |
COSIGN_PASSWORD |
password used for certs/cosign.key |
For release builds, the workflow builds the rolling offline installer ISO before any
image tag is published. Only after both local artifacts build successfully does it
run mise publish:image release, followed by the ISO upload. It uploads
bluecat.iso, bluecat.iso.sha256 and bluecat.iso.md5 to the configured
S3-compatible bucket/prefix. The public URL is:
https://download.bluecat.echocat.org/latest/bluecat.iso
Required ISO upload secrets:
| Secret | Content |
|---|---|
ISO_S3_ACCESS_KEY_ID |
S3/R2 access key |
ISO_S3_SECRET_ACCESS_KEY |
S3/R2 secret access key |
.github/workflows/lint.yaml runs mise lint (ShellCheck, Deno checks,
hadolint, workflow YAML) without secrets — safe for PRs.