Branch-deploy repos (event: push + branch:, tagging each push from CI_COMMIT_*/pipeline-number) are a deliberate continuous-deploy guard, not the absence of one. cms-plugins/emdash/kotkanagrilli -> 9/9; trio + self-test unchanged; a tagless default-version publish still FAILs.
parity-lib
Shared per-archetype publish-app builders for the ~51 "parity" repos
(cluster #192/#193/#194, emmett#44). Instead of each repo hand-inlining its own
stage / publish / push-staged shell, every repo consumes ONE versioned
source of truth: this flake's lib.mk*Publish builders generate the flake apps
that implement the corrected parity standard.
The corrected parity standard (emmett#44)
Parity has two halves, and only one of them can be cluster-independent:
- BUILD/STAGE parity is cluster-independent.
nix run .#stage-<arch>cross-builds the artifact into a local on-disk store (./.parity-stage/<arch>by convention) and makes NO registry contact, so it runs identically on emmett even when armer / the registry is down. - PUBLISH parity shares fate with the cluster (the only registry is
co-located with it). The publish apps name the blocker (
registry-down) up front and never half-push.
Every push entrypoint DRY-RUNS by default — it stages and prints what it
WOULD push. You must pass --publish (or set PUBLISH=1) to mutate the
registry, so an accidental local run can never push.
App shape each builder emits
| app | parity half | registry | mutates? |
|---|---|---|---|
stage-<arch> |
BUILD | none | no |
publish-<arch> |
BUILD + PUBLISH | one arch | only with --publish |
publish-index |
PUBLISH | multi-arch assembly (regctl) | only with --publish |
publish |
BUILD + PUBLISH | all local arches + index + :latest |
only with --publish |
push-staged |
PUBLISH | replay .parity-stage |
only with --publish |
publish-indexis build-free: it assembles<image>:<TAG>from the per-arch digest-pinned tags viaregctl index create, and is fail-closed — if a required arch was not pushed this run, it refuses to assemble a partial index.publishruns all locally-buildable arches, then the index, then copies:<TAG>→:latestas the LAST (single, idempotent) mutation.push-stagedreplays artifacts from./.parity-stageto the registry, for when the cluster was down at build time.
Single-arch archetypes (PyPI wheel, npm addon, generic/Go binary, Helm chart)
have no multi-arch index, so they expose stage-<arch> / publish-<arch> /
publish / push-staged only. The nix2container (OCI) builder is the one that
yields the full publish-index / :latest set.
Shared building blocks (also exposed)
All builders source one shell library (ci/parity-lib.sh, materialized in the
Nix store) so behavior cannot drift between repos:
- Token resolution —
$REGISTRY_TOKEN→pass infra/gitea/...fallback → named hard-fail. The token is never printed and scripts run underset -euo pipefailonly (neverset -x). - Dev-tag guard — refuses a real (
:latest/release) publish unless$VERSIONis set or$CI_COMMIT_TAGis av*tag. - Version derivation —
$VERSION→ strip leadingv+ trailing-Nfrom$CI_COMMIT_TAG→ the flake's pinned default. Identical for CI and local.
API — lib.*
lib is system-independent. Two ways to consume it:
# (a) one call, all builders, with your own pkgs:
parity.lib.mkParityBuilders pkgs # -> { mkPyPiWheelPublish, ... }
# (b) per-builder wrapper that takes pkgs as the first argument:
parity.lib.mkPyPiWheelPublish pkgs { pname = "..."; version = "..."; ... }
Exposed attrs:
lib.mkParityBuilders—pkgs -> { the six builders + mkApp + shellLib }lib.mkPyPiWheelPublish—pkgs -> args -> { apps }lib.mkS390xNpmPublish—pkgs -> args -> { apps }lib.mkGenericBinaryPublish—pkgs -> args -> { apps }lib.mkNix2ContainerPublish—pkgs -> args -> { apps }lib.mkGoBinaryPublish—pkgs -> args -> { apps }(alias of generic-binary)lib.mkHelmPublish—pkgs -> args -> { apps }
Builder arguments
mkPyPiWheelPublish {
pname = "asyncpg";
version = "0.31.0"; # default; overridden by $VERSION / $CI_COMMIT_TAG
wheel = self.packages.x86_64-linux.default; # drv/dir to glob *.whl, or a .whl path
arch = "s390x"; # default
}
mkS390xNpmPublish {
pname = "@rollup/rollup-linux-s390x-gnu";
version = "4.0.0";
nodeFile = self.packages.x86_64-linux.addon; # the *.node
nodeFileName = "rollup.linux-s390x-gnu.node";
packageJson = ''{ "name": "@rollup/...", "version": "$VERSION", ... }'';
}
mkGenericBinaryPublish { # mkGoBinaryPublish is an alias
pname = "geesefs";
version = "0.43.5";
binary = "${self.packages.x86_64-linux.default}/bin/geesefs";
assetName = "geesefs-linux-s390x";
arch = "s390x";
}
mkNix2ContainerPublish {
imageName = "git.oleks.space/oleks/nix-ci";
version = "1.2.3";
images = { # only arches buildable on THIS host
amd64 = { copyTo = self.packages.x86_64-linux.nix-ci.copyTo; };
};
arches = [ "amd64" "arm64" ]; # the full set the index must cover (fail-closed)
}
mkHelmPublish {
pname = "firecrawl";
version = "0.1.3";
chartSrc = ./charts/firecrawl;
ociRepo = "oci://git.oleks.space/oleks"; # default
}
Common optional args on every builder: registryHost (git.oleks.space),
registryOwner (oleks), passEntry
(infra/gitea/personal_access_token_packages_rw).
Consuming it in a parity repo's flake
{
inputs.parity.url = "git+https://git.oleks.space/oleks/parity-lib";
outputs = { self, nixpkgs, parity, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; };
in {
apps = parity.lib.mkGenericBinaryPublish pkgs {
pname = "geesefs";
version = "0.43.5";
binary = "${self.packages.${system}.default}/bin/geesefs";
assetName = "geesefs-linux-s390x";
};
});
}
.woodpecker.yaml stays thin — it runs the identical app, so CI and local
cannot drift:
commands:
- PUBLISH=1 nix run .#publish
pipeline-doctor (cluster #193)
nix run .#pipeline-doctor -- <repo-path> asserts the parity contract on a
repo: archetype declared, consumes parity-lib, token =
$REGISTRY_TOKEN + pass fallback and never printed, dev-tag guard present, a
--dry-run default exists, apps carry meta.description, and an enumerable
publish-* / stage-* naming. It prints the local-equivalent commands. It is
read-only (no token, no registry contact) and exits non-zero if any required
check fails.
Verifying changes locally
nix eval .#lib --apply builtins.attrNames --json
nix flake show
nix build .#pipeline-doctor
shellcheck ci/*.sh
statix check .
markdownlint-cli2 README.md
The git.oleks.space server runs a pre-receive linter; the commands above mirror it.