feat(parity): mkAtticClosurePublish builder + pipeline-doctor non-flake mode (#198, #193)

Adds the attic-closure archetype builder (build closure + attic push, no
registry artifact) so caddy/overlay-xonsh/flake-hub/woodpecker-peek share one
implementation. Adds non-flake mode to pipeline-doctor so ci-script repos
(gitea-mcp, helms) pass the gate. Self-check 9/9; gitea-mcp now passes.
This commit is contained in:
Oleks
2026-06-02 23:30:59 +03:00
parent 79f9a2dd62
commit db0bf3b9ab
4 changed files with 237 additions and 12 deletions
+157
View File
@@ -971,6 +971,162 @@ let
in
mkApp app "Replay the staged ${pname} ${arch} artifact (${kind}) from .parity-stage (dry-run by default; --publish to push).";
# =========================================================================
# Attic closure (NO registry artifact). The ATTIC-CLOSURE archetype: the
# "artifact" is a Nix closure pushed to an Attic binary cache, not a registry
# blob/image. Modelled on the bespoke attic CI the caddy-with-replace /
# woodpecker-peek / flake-hub repos run (`attic login ci <endpoint> $ATTIC_TOKEN`
# then push/watch-store the built store paths).
#
# Apps:
# stage-<arch> BUILD-parity: `nix build` the drvs into the local store and
# record their out-paths in .parity-stage (NO push, no token).
# publish-<arch> / publish stage + `attic login` + `attic push` the built
# paths to the cache. DRY-RUN by default; --publish / PUBLISH=1
# actually pushes. Token from $ATTIC_TOKEN or `pass`.
# push-staged replay the staged out-paths to the cache (no rebuild).
#
# TOKEN HYGIENE: $ATTIC_TOKEN is never echoed and no `set -x` is enabled; the
# token is piped straight into `attic login` and any output is sed-redacted.
#
# Args: { drvs (list of derivations/attrs to build),
# cache ? "attic-infra-cache-k3s-1",
# endpoint ? "http://attic.infra-cache.k3s",
# arch ? "x86_64-linux",
# passEntry ? "infra/attic/token", ... }
# =========================================================================
mkAtticClosurePublish =
{
drvs,
cache ? "attic-infra-cache-k3s-1",
endpoint ? "http://attic.infra-cache.k3s",
arch ? "x86_64-linux",
passEntry ? "infra/attic/token",
}:
let
# Closure parity does not contact the Gitea registry; only the Attic cache
# endpoint matters, but reuse the shared preamble for the stage-dir + arg
# conventions. passEntry here points at the ATTIC token, not the registry.
head = preamble { inherit passEntry; };
drvList = if builtins.isList drvs then drvs else [ drvs ];
drvRefs = lib.concatMapStringsSep " " (d: lib.escapeShellArg "${d}") drvList;
atticInputs = baseInputs ++ [
pkgs.nix
pkgs.attic-client
];
# Resolve the Attic token: $ATTIC_TOKEN, else `pass <passEntry>`. Never
# echoed; prints on stdout for a caller to capture (the one blessed emit).
atticTokenFn = ''
parity_attic_token() {
if [ -n "''${ATTIC_TOKEN:-}" ]; then printf '%s' "$ATTIC_TOKEN"; return 0; fi
if command -v pass >/dev/null 2>&1; then
t="$(pass show ${lib.escapeShellArg passEntry} 2>/dev/null || true)"
if [ -n "$t" ]; then printf '%s' "$t"; return 0; fi
fi
echo "BLOCKER(empty-token): set \$ATTIC_TOKEN or store ${passEntry} in pass; refusing to push without credentials." >&2
return 1
}
'';
# Log into the cache + push the given out-paths. Token piped, never echoed;
# any attic output is sed-redacted so a token can never reach the log.
atticPushFn = ''
parity_attic_push() {
tok="$(parity_attic_token)" || return 1
set +e
out="$(attic login ci ${lib.escapeShellArg endpoint} "$tok" 2>&1)"
rc=$?
set -e
printf '%s\n' "$out" | sed "s/$tok/***REDACTED***/g"
if [ "$rc" -ne 0 ]; then return "$rc"; fi
attic push ${lib.escapeShellArg cache} "$@"
}
'';
stage = pkgs.writeShellApplication {
name = "stage-${arch}";
runtimeInputs = atticInputs;
text = ''
${head}
echo " staging attic closure (${arch}, ${toString (builtins.length drvList)} drv(s), no push)"
d="$(parity_stage_reset ${lib.escapeShellArg arch})"
: >"$d/out-paths"
# The drv refs are a fixed Nix-eval list (may be a single literal path).
# shellcheck disable=SC2043
for drv in ${drvRefs}; do
# Realise the path into the local store (build-parity, no push).
p="$(nix build --no-link --print-out-paths "$drv")"
printf '%s\n' "$p" >>"$d/out-paths"
echo " built: $p"
done
printf 'attic %s %s\n' ${lib.escapeShellArg cache} ${lib.escapeShellArg endpoint} >"$d/.parity-meta"
echo " staged out-paths under $d"
'';
};
publish = pkgs.writeShellApplication {
name = "publish-${arch}";
runtimeInputs = atticInputs;
text = ''
${head}
${atticTokenFn}
${atticPushFn}
parity_parse_args "Build + push the ${arch} Nix closure to the Attic cache ${cache}" "$@"
${lib.getExe stage}
d="$(parity_stage_path ${lib.escapeShellArg arch})"
mapfile -t paths <"$d/out-paths"
if [ "''${#paths[@]}" -eq 0 ]; then
echo "BLOCKER(no-stage): no built out-paths under $d run 'nix run .#stage-${arch}' first." >&2
exit 1
fi
if [ "$PARITY_PUBLISH" != "1" ]; then
echo "[dry-run] would 'attic push ${cache}' these paths to ${endpoint}:"
for p in "''${paths[@]}"; do echo " - $p"; done
echo "[dry-run] re-run with --publish / PUBLISH=1 to push."
exit 0
fi
parity_attic_push "''${paths[@]}"
echo "Pushed ''${#paths[@]} path(s) to ${cache}"
'';
};
pushStaged = pkgs.writeShellApplication {
name = "push-staged";
runtimeInputs = atticInputs;
text = ''
${head}
${atticTokenFn}
${atticPushFn}
parity_parse_args "Replay the staged ${arch} closure out-paths to the Attic cache ${cache}" "$@"
d="$(parity_stage_path ${lib.escapeShellArg arch})"
if [ ! -f "$d/out-paths" ]; then
echo "BLOCKER(no-stage): no staged out-paths at $d run 'nix run .#stage-${arch}' first." >&2
exit 1
fi
mapfile -t paths <"$d/out-paths"
if [ "$PARITY_PUBLISH" != "1" ]; then
echo "[dry-run] would 'attic push ${cache}' these staged paths to ${endpoint}:"
for p in "''${paths[@]}"; do echo " - $p"; done
echo "[dry-run] re-run with --publish / PUBLISH=1 to push."
exit 0
fi
parity_attic_push "''${paths[@]}"
echo "Replayed ''${#paths[@]} path(s) to ${cache}"
'';
};
in
{
"stage-${arch}" =
mkApp stage "Build the ${arch} Nix closure into the local store + stage its out-paths (no push).";
"publish-${arch}" =
mkApp publish "Build + push the ${arch} closure to the Attic cache ${cache} (dry-run by default; --publish to push).";
"publish" =
mkApp publish "Push all locally-buildable arches to the Attic cache (${arch} only for this closure repo).";
"push-staged" =
mkApp pushStaged "Replay the staged ${arch} closure out-paths to the Attic cache (dry-run by default; --publish to push).";
};
mkPushStagedHelm =
{
pname,
@@ -1019,6 +1175,7 @@ in
mkNix2ContainerPublish
mkGoBinaryPublish
mkHelmPublish
mkAtticClosurePublish
;
# Shared building blocks, exposed for advanced/bespoke consumers.
inherit mkApp shellLib;