Oleks 9107923c5a fix(devtag-guard): snapshot explicit VERSION at source time (#194 finding)
The guard read $VERSION, but app bodies set VERSION to the derived default
before calling it, so accidental local --publish without an explicit version
or v* tag still pushed. Capture PARITY_VERSION_EXPLICIT at source time and
gate on that instead.
2026-06-02 05:08:05 +03:00

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-index is build-free: it assembles <image>:<TAG> from the per-arch digest-pinned tags via regctl index create, and is fail-closed — if a required arch was not pushed this run, it refuses to assemble a partial index.
  • publish runs all locally-buildable arches, then the index, then copies :<TAG>:latest as the LAST (single, idempotent) mutation.
  • push-staged replays artifacts from ./.parity-stage to 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_TOKENpass infra/gitea/... fallback → named hard-fail. The token is never printed and scripts run under set -euo pipefail only (never set -x).
  • Dev-tag guard — refuses a real (:latest/release) publish unless $VERSION is set or $CI_COMMIT_TAG is a v* tag.
  • Version derivation$VERSION → strip leading v + trailing -N from $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.mkParityBuilderspkgs -> { the six builders + mkApp + shellLib }
  • lib.mkPyPiWheelPublishpkgs -> args -> { apps }
  • lib.mkS390xNpmPublishpkgs -> args -> { apps }
  • lib.mkGenericBinaryPublishpkgs -> args -> { apps }
  • lib.mkNix2ContainerPublishpkgs -> args -> { apps }
  • lib.mkGoBinaryPublishpkgs -> args -> { apps } (alias of generic-binary)
  • lib.mkHelmPublishpkgs -> 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.

S
Description
Shared versioned flake-module for local pipeline parity (emmett#44, cluster #192): mkPyPiWheelPublish / mkS390xNpmPublish / mkGenericBinaryPublish / mkNix2ContainerPublish + pipeline-doctor. Single source of truth so per-repo publish apps cannot drift.
Readme
253 KiB
Languages
Nix 68.7%
Shell 31.3%