Silence style-only shellcheck advisories in plugin scripts (oleks/ci-scripts#14, #15)
ci/woodpecker/tag/build/1 Pipeline is pending
ci/woodpecker/tag/build/2 Pipeline is pending
ci/woodpecker/tag/manifest Pipeline is pending

This commit is contained in:
2026-07-05 16:51:18 +03:00
parent a04c5aec12
commit f4483915d5
2 changed files with 199 additions and 191 deletions
+74 -70
View File
@@ -1,4 +1,8 @@
#!/usr/bin/env bash
# shellcheck disable=SC2250,SC2292,SC2312
# (style-only advisories: brace-optional var refs, [[ ]] preference,
# masked-return-value on `uname -m` in the banner echo — none affect
# correctness here.)
# Dev-parity build script for the ci-fleet-publish plugin image itself
# (oleks/ci-scripts#14, #15). CI runs this SAME script (cluster #196,
# emmett#44 convention) so CI and local can't drift; the only CI-specific
@@ -14,88 +18,88 @@ ARCHES=(amd64 arm64)
echo "▸ arch=$(uname -m)"
registry_login() {
if [ -n "${REGISTRY_TOKEN:-}" ]; then
echo "${REGISTRY_TOKEN}" | docker login git.oleks.space -u oleks --password-stdin
fi
if [ -n "${REGISTRY_TOKEN:-}" ]; then
echo "${REGISTRY_TOKEN}" | docker login git.oleks.space -u oleks --password-stdin
fi
}
ensure_builder() {
if [ -n "${BUILDKIT_ADDR:-}" ]; then
if ! docker buildx inspect ci-fleet-publish >/dev/null 2>&1; then
docker buildx create --name ci-fleet-publish --driver remote "${BUILDKIT_ADDR}"
fi
docker buildx use ci-fleet-publish
fi
if [ -n "${BUILDKIT_ADDR:-}" ]; then
if ! docker buildx inspect ci-fleet-publish >/dev/null 2>&1; then
docker buildx create --name ci-fleet-publish --driver remote "${BUILDKIT_ADDR}"
fi
docker buildx use ci-fleet-publish
fi
}
usage() {
echo "usage: $0 --arch ARCH | --manifest" >&2
exit 1
echo "usage: $0 --arch ARCH | --manifest" >&2
exit 1
}
mode=""
arch=""
while [ $# -gt 0 ]; do
case "$1" in
--arch)
arch="$2"
mode="build"
shift 2
;;
--manifest)
mode="manifest"
shift
;;
*)
usage
;;
esac
case "$1" in
--arch)
arch="$2"
mode="build"
shift 2
;;
--manifest)
mode="manifest"
shift
;;
*)
usage
;;
esac
done
case "${mode}" in
build)
: "${arch:?--arch required}"
registry_login
ensure_builder
tag="${VERSION}-${arch}"
push_flag="--load"
if [ "${PUBLISH}" = "1" ]; then
push_flag="--push"
fi
echo "▸ building ${IMAGE}:${tag} for linux/${arch}"
docker buildx build --platform "linux/${arch}" "${push_flag}" -t "${IMAGE}:${tag}" .
;;
manifest)
registry_login
major="${VERSION%%.*}"
dest_tags=("${VERSION}" "${major}" "latest")
found=()
for a in "${ARCHES[@]}"; do
ref="${IMAGE}:${VERSION}-${a}"
echo "▸ probing ${ref}"
if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect "${ref}" >/dev/null 2>&1; then
echo "▸ found ${ref}"
found+=("${ref}")
else
echo "▸ missing ${ref} (oleks/ci-scripts#9: skip this arch, not the whole manifest)"
fi
done
if [ "${#found[@]}" -eq 0 ]; then
echo "no per-arch tags landed for ${IMAGE}:${VERSION}; failing manifest step" >&2
exit 1
fi
if [ "${PUBLISH}" != "1" ]; then
echo "▸ dry-run: would create manifest ${dest_tags[*]} from ${found[*]}"
exit 0
fi
for dest in "${dest_tags[@]}"; do
dest_ref="${IMAGE}:${dest}"
echo "▸ creating manifest ${dest_ref} from ${#found[@]}/${#ARCHES[@]} arches"
docker manifest create "${dest_ref}" "${found[@]}"
docker manifest push "${dest_ref}"
done
;;
*)
usage
;;
build)
: "${arch:?--arch required}"
registry_login
ensure_builder
tag="${VERSION}-${arch}"
push_flag="--load"
if [ "${PUBLISH}" = "1" ]; then
push_flag="--push"
fi
echo "▸ building ${IMAGE}:${tag} for linux/${arch}"
docker buildx build --platform "linux/${arch}" "${push_flag}" -t "${IMAGE}:${tag}" .
;;
manifest)
registry_login
major="${VERSION%%.*}"
dest_tags=("${VERSION}" "${major}" "latest")
found=()
for a in "${ARCHES[@]}"; do
ref="${IMAGE}:${VERSION}-${a}"
echo "▸ probing ${ref}"
if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect "${ref}" >/dev/null 2>&1; then
echo "▸ found ${ref}"
found+=("${ref}")
else
echo "▸ missing ${ref} (oleks/ci-scripts#9: skip this arch, not the whole manifest)"
fi
done
if [ "${#found[@]}" -eq 0 ]; then
echo "no per-arch tags landed for ${IMAGE}:${VERSION}; failing manifest step" >&2
exit 1
fi
if [ "${PUBLISH}" != "1" ]; then
echo "▸ dry-run: would create manifest ${dest_tags[*]} from ${found[*]}"
exit 0
fi
for dest in "${dest_tags[@]}"; do
dest_ref="${IMAGE}:${dest}"
echo "▸ creating manifest ${dest_ref} from ${#found[@]}/${#ARCHES[@]} arches"
docker manifest create "${dest_ref}" "${found[@]}"
docker manifest push "${dest_ref}"
done
;;
*)
usage
;;
esac
+125 -121
View File
@@ -1,4 +1,8 @@
#!/usr/bin/env bash
# shellcheck disable=SC2250,SC2292,SC2310,SC2312
# (style-only: brace-optional var refs, [[ ]] preference, set -e-in-||
# advisories — none affect correctness here; the explicit run_status/
# attic_status capture pattern is intentional, not an oversight.)
# ci-fleet-publish entrypoint (oleks/ci-scripts#14, #15).
#
# Woodpecker plugin entrypoint: reads PLUGIN_* settings (Woodpecker maps a
@@ -34,172 +38,172 @@ echo "▸ arch=$(uname -m)"
# --- resolv.conf shim (s390x cross-build DNS fix, oleks/ci-scripts#4) ---
if [ -n "${PLUGIN_NAMESERVER:-}" ]; then
echo "nameserver ${PLUGIN_NAMESERVER}" >/etc/resolv.conf
echo "options ndots:1" >>/etc/resolv.conf
echo "nameserver ${PLUGIN_NAMESERVER}" >/etc/resolv.conf
echo "options ndots:1" >>/etc/resolv.conf
fi
# --- nix.conf shims ---
if [ "${PLUGIN_MAX_JOBS:-}" = "true" ]; then
echo "max-jobs = 1" >>/etc/nix/nix.conf
echo "max-jobs = 1" >>/etc/nix/nix.conf
fi
if [ -n "${PLUGIN_CORES:-}" ]; then
echo "cores = ${PLUGIN_CORES}" >>/etc/nix/nix.conf
echo "cores = ${PLUGIN_CORES}" >>/etc/nix/nix.conf
fi
echo "sandbox = false" >>/etc/nix/nix.conf
normalize_arch() {
case "$1" in
x86_64) echo "amd64" ;;
aarch64) echo "arm64" ;;
*) echo "$1" ;;
esac
case "$1" in
x86_64) echo "amd64" ;;
aarch64) echo "arm64" ;;
*) echo "$1" ;;
esac
}
render_tag() {
# render_tag SCHEME VERSION ARCH
local scheme="$1" version="$2" arch="$3"
scheme="${scheme//\{version\}/$version}"
scheme="${scheme//\{arch\}/$arch}"
echo "$scheme"
# render_tag SCHEME VERSION ARCH
local scheme="$1" version="$2" arch="$3"
scheme="${scheme//\{version\}/$version}"
scheme="${scheme//\{arch\}/$arch}"
echo "$scheme"
}
# --- attic watch-store lifecycle: explicit non-masking shutdown, never `|| true` ---
ATTIC_PID=""
attic_start() {
if [ "${PLUGIN_CACHE:-true}" != "true" ] || [ -z "${ATTIC_TOKEN:-}" ]; then
return 0
fi
local server="${PLUGIN_ATTIC_SERVER:-https://nix-cache-upload.oleks.space}"
local cache="${PLUGIN_ATTIC_CACHE:-attic-infra-cache-k3s-1}"
attic login ci "$server" "$ATTIC_TOKEN"
attic watch-store "$cache" &
ATTIC_PID=$!
sleep 2
echo "▸ attic watch-store started for ${cache} (pid ${ATTIC_PID})"
if [ "${PLUGIN_CACHE:-true}" != "true" ] || [ -z "${ATTIC_TOKEN:-}" ]; then
return 0
fi
local server="${PLUGIN_ATTIC_SERVER:-https://nix-cache-upload.oleks.space}"
local cache="${PLUGIN_ATTIC_CACHE:-attic-infra-cache-k3s-1}"
attic login ci "$server" "$ATTIC_TOKEN"
attic watch-store "$cache" &
ATTIC_PID=$!
sleep 2
echo "▸ attic watch-store started for ${cache} (pid ${ATTIC_PID})"
}
attic_stop() {
# attic_stop RUN_STATUS — folds a non-zero shutdown into the exit code
# only if the run itself succeeded (a failing run's status always wins).
local run_status="$1"
if [ -z "$ATTIC_PID" ]; then
return 0
fi
if ! kill "$ATTIC_PID" 2>/dev/null; then
echo "▸ attic watch-store already exited before shutdown" >&2
return 0
fi
if wait "$ATTIC_PID"; then
echo "▸ attic watch-store exited cleanly after shutdown signal"
return 0
fi
local status=$?
if [ "$status" -eq 143 ] || [ "$status" -eq 137 ]; then
echo "▸ attic watch-store terminated by shutdown signal (exit ${status}, expected)"
return 0
fi
echo "▸ attic watch-store exited non-zero (${status}) after shutdown signal" >&2
if [ "$run_status" -eq 0 ]; then
return "$status"
fi
return 0
# attic_stop RUN_STATUS — folds a non-zero shutdown into the exit code
# only if the run itself succeeded (a failing run's status always wins).
local run_status="$1"
if [ -z "$ATTIC_PID" ]; then
return 0
fi
if ! kill "$ATTIC_PID" 2>/dev/null; then
echo "▸ attic watch-store already exited before shutdown" >&2
return 0
fi
if wait "$ATTIC_PID"; then
echo "▸ attic watch-store exited cleanly after shutdown signal"
return 0
fi
local status=$?
if [ "$status" -eq 143 ] || [ "$status" -eq 137 ]; then
echo "▸ attic watch-store terminated by shutdown signal (exit ${status}, expected)"
return 0
fi
echo "▸ attic watch-store exited non-zero (${status}) after shutdown signal" >&2
if [ "$run_status" -eq 0 ]; then
return "$status"
fi
return 0
}
publish_flow() {
: "${PLUGIN_RUN:?PLUGIN_RUN is required when PLUGIN_FLOW=publish}"
# Delegate netrc/redis setup to nixos-ci-entrypoint, but WITHOUT
# ATTIC_TOKEN: this script already owns the attic lifecycle above, so
# nixos-ci-entrypoint must not start a second watch-store.
env -u ATTIC_TOKEN nixos-ci-entrypoint bash -lc "$PLUGIN_RUN"
: "${PLUGIN_RUN:?PLUGIN_RUN is required when PLUGIN_FLOW=publish}"
# Delegate netrc/redis setup to nixos-ci-entrypoint, but WITHOUT
# ATTIC_TOKEN: this script already owns the attic lifecycle above, so
# nixos-ci-entrypoint must not start a second watch-store.
env -u ATTIC_TOKEN nixos-ci-entrypoint bash -lc "$PLUGIN_RUN"
}
buildx_build_flow() {
: "${PLUGIN_IMAGE_NAME:?PLUGIN_IMAGE_NAME is required for buildx-build flow}"
local arch
arch=$(normalize_arch "$(uname -m)")
# Note: default assigned in a separate statement, not
# ${PLUGIN_TAG_SCHEME:-{version}-{arch}} — bash's brace-matching for the
# default word gets confused by literal `{...}` braces in the fallback
# and silently corrupts the rendered tag.
local tag_scheme="{version}-{arch}"
if [ -n "${PLUGIN_TAG_SCHEME:-}" ]; then
tag_scheme="${PLUGIN_TAG_SCHEME}"
fi
local version="${CI_COMMIT_TAG:-${PLUGIN_VERSION:-latest}}"
local tag
tag=$(render_tag "$tag_scheme" "$version" "$arch")
local context="${PLUGIN_CONTEXT:-.}"
echo "▸ building ${PLUGIN_IMAGE_NAME}:${tag} for linux/${arch} (context ${context})"
docker buildx build --push --platform "linux/${arch}" -t "${PLUGIN_IMAGE_NAME}:${tag}" "${context}"
: "${PLUGIN_IMAGE_NAME:?PLUGIN_IMAGE_NAME is required for buildx-build flow}"
local arch
arch=$(normalize_arch "$(uname -m)")
# Note: default assigned in a separate statement, not
# ${PLUGIN_TAG_SCHEME:-{version}-{arch}} — bash's brace-matching for the
# default word gets confused by literal `{...}` braces in the fallback
# and silently corrupts the rendered tag.
local tag_scheme="{version}-{arch}"
if [ -n "${PLUGIN_TAG_SCHEME:-}" ]; then
tag_scheme="${PLUGIN_TAG_SCHEME}"
fi
local version="${CI_COMMIT_TAG:-${PLUGIN_VERSION:-latest}}"
local tag
tag=$(render_tag "$tag_scheme" "$version" "$arch")
local context="${PLUGIN_CONTEXT:-.}"
echo "▸ building ${PLUGIN_IMAGE_NAME}:${tag} for linux/${arch} (context ${context})"
docker buildx build --push --platform "linux/${arch}" -t "${PLUGIN_IMAGE_NAME}:${tag}" "${context}"
}
buildx_manifest_flow() {
: "${PLUGIN_IMAGE_NAME:?PLUGIN_IMAGE_NAME is required for buildx-manifest flow}"
: "${PLUGIN_PLATFORMS:?PLUGIN_PLATFORMS is required for buildx-manifest flow (comma-separated arches to probe)}"
local tag_scheme="{version}-{arch}"
if [ -n "${PLUGIN_TAG_SCHEME:-}" ]; then
tag_scheme="${PLUGIN_TAG_SCHEME}"
fi
local version="${CI_COMMIT_TAG:-${PLUGIN_VERSION:-latest}}"
local dest_tags="${PLUGIN_DEST_TAGS:-${version},latest}"
: "${PLUGIN_IMAGE_NAME:?PLUGIN_IMAGE_NAME is required for buildx-manifest flow}"
: "${PLUGIN_PLATFORMS:?PLUGIN_PLATFORMS is required for buildx-manifest flow (comma-separated arches to probe)}"
local tag_scheme="{version}-{arch}"
if [ -n "${PLUGIN_TAG_SCHEME:-}" ]; then
tag_scheme="${PLUGIN_TAG_SCHEME}"
fi
local version="${CI_COMMIT_TAG:-${PLUGIN_VERSION:-latest}}"
local dest_tags="${PLUGIN_DEST_TAGS:-${version},latest}"
IFS=',' read -ra platforms <<<"$PLUGIN_PLATFORMS"
local found=()
local arch tag ref
for arch in "${platforms[@]}"; do
arch="$(echo "$arch" | xargs)"
tag=$(render_tag "$tag_scheme" "$version" "$arch")
ref="${PLUGIN_IMAGE_NAME}:${tag}"
echo "▸ probing ${ref}"
if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect "$ref" >/dev/null 2>&1; then
echo "▸ found ${ref}"
found+=("$ref")
else
echo "▸ missing ${ref} (oleks/ci-scripts#9: skipping this arch, not failing the manifest)"
fi
done
IFS=',' read -ra platforms <<<"$PLUGIN_PLATFORMS"
local found=()
local arch tag ref
for arch in "${platforms[@]}"; do
arch="$(echo "$arch" | xargs)"
tag=$(render_tag "$tag_scheme" "$version" "$arch")
ref="${PLUGIN_IMAGE_NAME}:${tag}"
echo "▸ probing ${ref}"
if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect "$ref" >/dev/null 2>&1; then
echo "▸ found ${ref}"
found+=("$ref")
else
echo "▸ missing ${ref} (oleks/ci-scripts#9: skipping this arch, not failing the manifest)"
fi
done
if [ "${#found[@]}" -eq 0 ]; then
echo "no per-arch tags landed for ${PLUGIN_IMAGE_NAME}:${version}; failing manifest step" >&2
return 1
fi
if [ "${#found[@]}" -eq 0 ]; then
echo "no per-arch tags landed for ${PLUGIN_IMAGE_NAME}:${version}; failing manifest step" >&2
return 1
fi
local dests=()
IFS=',' read -ra dests <<<"$dest_tags"
local dest dest_ref
for dest in "${dests[@]}"; do
dest="$(echo "$dest" | xargs)"
dest_ref="${PLUGIN_IMAGE_NAME}:${dest}"
echo "▸ creating manifest ${dest_ref} from ${#found[@]}/${#platforms[@]} arches"
docker manifest create "$dest_ref" "${found[@]}"
docker manifest push "$dest_ref"
done
local dests=()
IFS=',' read -ra dests <<<"$dest_tags"
local dest dest_ref
for dest in "${dests[@]}"; do
dest="$(echo "$dest" | xargs)"
dest_ref="${PLUGIN_IMAGE_NAME}:${dest}"
echo "▸ creating manifest ${dest_ref} from ${#found[@]}/${#platforms[@]} arches"
docker manifest create "$dest_ref" "${found[@]}"
docker manifest push "$dest_ref"
done
}
attic_start
run_status=0
case "$PLUGIN_FLOW" in
publish)
publish_flow || run_status=$?
;;
buildx-build)
buildx_build_flow || run_status=$?
;;
buildx-manifest)
buildx_manifest_flow || run_status=$?
;;
*)
echo "unknown PLUGIN_FLOW: ${PLUGIN_FLOW} (expected publish|buildx-build|buildx-manifest)" >&2
run_status=1
;;
publish)
publish_flow || run_status=$?
;;
buildx-build)
buildx_build_flow || run_status=$?
;;
buildx-manifest)
buildx_manifest_flow || run_status=$?
;;
*)
echo "unknown PLUGIN_FLOW: ${PLUGIN_FLOW} (expected publish|buildx-build|buildx-manifest)" >&2
run_status=1
;;
esac
attic_status=0
attic_stop "$run_status" || attic_status=$?
if [ "$attic_status" -ne 0 ] && [ "$run_status" -eq 0 ]; then
run_status="$attic_status"
run_status="$attic_status"
fi
exit "$run_status"