From 29121c4bb7288e2714878aa6e62d85674745b35a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 11:28:46 +0000 Subject: [PATCH 1/6] release.sh: build RHEL 7/8/9/10 RPMs into static nginx layout Adds a self-contained script that builds the existing Dockerfile-rhel images, extracts /root/rpmbuild/{RPMS,SRPMS} from each, and arranges the artifacts under ../rhel//{x86_64,SRPMS}/ so the directory can be served directly by nginx. Runs createrepo_c locally when available; otherwise leaves a plain RPM drop. No signing, no external hosting, no yum repo publishing. --- release.sh | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100755 release.sh diff --git a/release.sh b/release.sh new file mode 100755 index 000000000..b8c12547e --- /dev/null +++ b/release.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Build RHEL 7/8/9/10 RPMs for SEMS and lay them out for static hosting (e.g. nginx). +# +# Usage: +# cd /path/to/sems-sources # a checked-out sems repository +# ./release.sh # writes RPMs to ../rhel +# +# Environment overrides: +# EL_VERSIONS Space-separated list of EL majors to build. Default "7 8 9 10". +# OUT_DIR Output directory. Default "$(dirname $PWD)/rhel". +# CONTAINER_ENGINE "docker" or "podman". Auto-detected. +# +# Layout produced under $OUT_DIR: +# /x86_64/*.rpm +# /SRPMS/*.src.rpm +# /{x86_64,SRPMS}/repodata/ (only if createrepo_c is installed locally) +# +# The existing pkg/rpm/sems.spec + Dockerfile-rhel are the single source of truth +# for the build; this script just orchestrates them and extracts the artifacts. + +set -euo pipefail + +SRC_DIR="$(pwd)" +if [[ ! -f "$SRC_DIR/VERSION" || ! -f "$SRC_DIR/pkg/rpm/sems.spec" ]]; then + echo "error: run this script from the root of a sems checkout" >&2 + exit 1 +fi + +VERSION="$(cat "$SRC_DIR/VERSION")" +OUT_DIR="${OUT_DIR:-$(cd "$SRC_DIR/.." && pwd)/rhel}" +EL_VERSIONS="${EL_VERSIONS:-7 8 9 10}" + +ENGINE="${CONTAINER_ENGINE:-}" +if [[ -z "$ENGINE" ]]; then + if command -v docker >/dev/null 2>&1; then + ENGINE=docker + elif command -v podman >/dev/null 2>&1; then + ENGINE=podman + else + echo "error: neither docker nor podman found in PATH" >&2 + exit 1 + fi +fi + +echo "sems version : $VERSION" +echo "source dir : $SRC_DIR" +echo "output dir : $OUT_DIR" +echo "el targets : $EL_VERSIONS" +echo "engine : $ENGINE" + +mkdir -p "$OUT_DIR" + +build_one_el() { + local el="$1" + local dockerfile="$SRC_DIR/Dockerfile-rhel${el}" + local image="sems-release-el${el}:${VERSION}" + local dest="$OUT_DIR/${el}" + local staging cid + + if [[ ! -f "$dockerfile" ]]; then + echo "skip el${el}: $dockerfile not found" + return 0 + fi + + echo + echo "==============================================" + echo " Building RPMs for el${el}" + echo "==============================================" + "$ENGINE" build -t "$image" -f "$dockerfile" "$SRC_DIR" + + # Replace any previous artifacts for this EL but leave siblings alone. + rm -rf "$dest" + mkdir -p "$dest/x86_64" "$dest/SRPMS" + + cid=$("$ENGINE" create "$image") + staging=$(mktemp -d) + + # Dockerfile-rhel runs `rpmbuild -ba` + `rpmbuild -bs`, so artifacts + # live under /root/rpmbuild/{RPMS/,SRPMS}. Copy both trees out, then + # flatten RPMs into a single x86_64/ directory (the spec only emits x86_64 + # and noarch subpackages). + "$ENGINE" cp "$cid:/root/rpmbuild/RPMS" "$staging/" + "$ENGINE" cp "$cid:/root/rpmbuild/SRPMS" "$staging/" + "$ENGINE" rm -f "$cid" >/dev/null + + find "$staging/RPMS" -name '*.rpm' -exec cp {} "$dest/x86_64/" \; + find "$staging/SRPMS" -name '*.src.rpm' -exec cp {} "$dest/SRPMS/" \; + rm -rf "$staging" + + if command -v createrepo_c >/dev/null 2>&1; then + createrepo_c --quiet "$dest/x86_64" + createrepo_c --quiet "$dest/SRPMS" + else + echo "note: createrepo_c not installed; skipping repodata for el${el}" + fi + + echo "el${el} artifacts:" + ( cd "$dest" && find . -name '*.rpm' | sort | sed 's|^\./| |' ) +} + +for el in $EL_VERSIONS; do + build_one_el "$el" +done + +echo +echo "Done. Point nginx's document root (or an alias) at:" +echo " $OUT_DIR" From 17bdaaa3daf88596cf4408ad807d6db656724a4f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 11:32:29 +0000 Subject: [PATCH 2/6] release.sh: require arg, build from isolated worktree The script now takes the git tag/branch/SHA to build as its first argument and stages the build in a git worktree instead of the caller's working tree. This keeps uncommitted local changes out of the release and lets operators run the script from a long-lived checkout without stashing. --- release.sh | 68 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/release.sh b/release.sh index b8c12547e..dcb75eb58 100755 --- a/release.sh +++ b/release.sh @@ -1,9 +1,14 @@ #!/usr/bin/env bash -# Build RHEL 7/8/9/10 RPMs for SEMS and lay them out for static hosting (e.g. nginx). +# Build RHEL 7/8/9/10 RPMs for SEMS at a specific git ref and lay them out for +# static hosting (e.g. nginx). # # Usage: -# cd /path/to/sems-sources # a checked-out sems repository -# ./release.sh # writes RPMs to ../rhel +# cd /path/to/sems-sources # a checked-out sems repository +# ./release.sh # e.g. ./release.sh v2.1.0 +# +# is any tag, branch or commit SHA that exists in the local clone. +# The build runs in an isolated `git worktree` so the caller's working tree is +# never modified. Output goes to ../rhel relative to the current repository. # # Environment overrides: # EL_VERSIONS Space-separated list of EL majors to build. Default "7 8 9 10". @@ -15,19 +20,58 @@ # /SRPMS/*.src.rpm # /{x86_64,SRPMS}/repodata/ (only if createrepo_c is installed locally) # -# The existing pkg/rpm/sems.spec + Dockerfile-rhel are the single source of truth -# for the build; this script just orchestrates them and extracts the artifacts. +# The existing pkg/rpm/sems.spec + Dockerfile-rhel are the single source of +# truth for the build; this script just orchestrates them and extracts the +# artifacts. set -euo pipefail -SRC_DIR="$(pwd)" +if [[ $# -lt 1 || "$1" == "-h" || "$1" == "--help" ]]; then + cat >&2 < + tag, branch, or SHA to build from (e.g. v2.1.0) + +Run 'git fetch --tags' first if the tag isn't in the local clone. +EOF + exit 2 +fi + +REF="$1" +REPO_DIR="$(pwd)" + +if ! git -C "$REPO_DIR" rev-parse --git-dir >/dev/null 2>&1; then + echo "error: run this script from the root of a sems git checkout" >&2 + exit 1 +fi + +# Resolve the ref up front so we fail fast with a clear message. +if ! git -C "$REPO_DIR" rev-parse --verify --quiet "${REF}^{commit}" >/dev/null; then + echo "error: git ref '$REF' not found in $REPO_DIR" >&2 + echo " (try: git fetch --tags origin)" >&2 + exit 1 +fi +RESOLVED_SHA="$(git -C "$REPO_DIR" rev-parse "${REF}^{commit}")" +RESOLVED_SHORT="$(git -C "$REPO_DIR" rev-parse --short "$RESOLVED_SHA")" + +# Build in an isolated worktree so the caller's working tree (and any +# uncommitted changes they have) is untouched. +WORKTREE_DIR="$(mktemp -d -t sems-release-XXXXXX)" +cleanup() { + git -C "$REPO_DIR" worktree remove --force "$WORKTREE_DIR" >/dev/null 2>&1 || true + rm -rf "$WORKTREE_DIR" +} +trap cleanup EXIT + +git -C "$REPO_DIR" worktree add --detach "$WORKTREE_DIR" "$RESOLVED_SHA" >/dev/null + +SRC_DIR="$WORKTREE_DIR" if [[ ! -f "$SRC_DIR/VERSION" || ! -f "$SRC_DIR/pkg/rpm/sems.spec" ]]; then - echo "error: run this script from the root of a sems checkout" >&2 + echo "error: checkout at $REF does not look like a sems tree" >&2 exit 1 fi VERSION="$(cat "$SRC_DIR/VERSION")" -OUT_DIR="${OUT_DIR:-$(cd "$SRC_DIR/.." && pwd)/rhel}" +OUT_DIR="${OUT_DIR:-$(cd "$REPO_DIR/.." && pwd)/rhel}" EL_VERSIONS="${EL_VERSIONS:-7 8 9 10}" ENGINE="${CONTAINER_ENGINE:-}" @@ -42,8 +86,9 @@ if [[ -z "$ENGINE" ]]; then fi fi +echo "git ref : $REF ($RESOLVED_SHORT)" echo "sems version : $VERSION" -echo "source dir : $SRC_DIR" +echo "source tree : $SRC_DIR (worktree)" echo "output dir : $OUT_DIR" echo "el targets : $EL_VERSIONS" echo "engine : $ENGINE" @@ -53,7 +98,7 @@ mkdir -p "$OUT_DIR" build_one_el() { local el="$1" local dockerfile="$SRC_DIR/Dockerfile-rhel${el}" - local image="sems-release-el${el}:${VERSION}" + local image="sems-release-el${el}:${VERSION}-${RESOLVED_SHORT}" local dest="$OUT_DIR/${el}" local staging cid @@ -103,5 +148,6 @@ for el in $EL_VERSIONS; do done echo -echo "Done. Point nginx's document root (or an alias) at:" +echo "Done. Built $REF ($RESOLVED_SHORT) as sems-$VERSION." +echo "Point nginx's document root (or an alias) at:" echo " $OUT_DIR" From 287f8068e88ecf877bc8dd55d2aacfe50781bbe4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 11:35:11 +0000 Subject: [PATCH 3/6] release.sh: write per-release README with timestamp and build-host rpm list Each // now contains a README.txt recording when the release was produced, the git ref/SHA it was built from, the SEMS version, and the full output of `rpm -qa` inside the build container. That captures the toolchain and dependency versions responsible for the binaries so releases are traceable without re-running the build. --- release.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/release.sh b/release.sh index dcb75eb58..2d1b9eb9a 100755 --- a/release.sh +++ b/release.sh @@ -132,6 +132,14 @@ build_one_el() { find "$staging/SRPMS" -name '*.src.rpm' -exec cp {} "$dest/SRPMS/" \; rm -rf "$staging" + # Snapshot the build host's installed-package inventory from the image we + # just built, so the release folder records exactly which toolchain and + # dependency versions produced these RPMs. + local build_host_rpms + build_host_rpms=$("$ENGINE" run --rm --entrypoint rpm "$image" -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | sort) + + write_readme "$el" "$dest" "$build_host_rpms" + if command -v createrepo_c >/dev/null 2>&1; then createrepo_c --quiet "$dest/x86_64" createrepo_c --quiet "$dest/SRPMS" @@ -143,6 +151,40 @@ build_one_el() { ( cd "$dest" && find . -name '*.rpm' | sort | sed 's|^\./| |' ) } +write_readme() { + local el="$1" + local dest="$2" + local build_host_rpms="$3" + local readme="$dest/README.txt" + local release_date + release_date="$(date -u +'%Y-%m-%d %H:%M:%S UTC')" + + { + echo "SEMS RHEL ${el} release" + echo "=======================" + echo + echo "Release date : ${release_date}" + echo "Git ref : ${REF} (${RESOLVED_SHORT})" + echo "Commit SHA : ${RESOLVED_SHA}" + echo "SEMS version : ${VERSION}" + echo "EL target : ${el}" + echo "Built with : ${ENGINE} via Dockerfile-rhel${el}" + echo "Built on : $(uname -n) ($(uname -sr))" + echo + echo "RPMs in this directory" + echo "----------------------" + ( cd "$dest" && find x86_64 SRPMS -name '*.rpm' 2>/dev/null | sort ) + echo + echo "Build host RPM inventory" + echo "------------------------" + echo "Full list of packages installed in the build container at the" + echo "time these RPMs were produced (output of 'rpm -qa' inside the" + echo "Dockerfile-rhel${el} image):" + echo + echo "$build_host_rpms" + } > "$readme" +} + for el in $EL_VERSIONS; do build_one_el "$el" done From 36467b8820662f4ddbbb02103cc895b366119f1d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 06:23:33 +0000 Subject: [PATCH 4/6] release.sh: build from current working tree, drop argument The script now takes no arguments and builds directly from the current checkout, matching the simpler "check out what you want, then run release.sh" workflow. Git metadata (describe, SHA, dirty flag) is still captured in the per-EL README when the source is a git clone, but is no longer required. --- release.sh | 91 ++++++++++++++++++++++-------------------------------- 1 file changed, 37 insertions(+), 54 deletions(-) diff --git a/release.sh b/release.sh index 2d1b9eb9a..1f086fd39 100755 --- a/release.sh +++ b/release.sh @@ -1,14 +1,13 @@ #!/usr/bin/env bash -# Build RHEL 7/8/9/10 RPMs for SEMS at a specific git ref and lay them out for -# static hosting (e.g. nginx). +# Build RHEL 7/8/9/10 RPMs for SEMS from the current working tree and lay them +# out for static hosting (e.g. nginx). # # Usage: -# cd /path/to/sems-sources # a checked-out sems repository -# ./release.sh # e.g. ./release.sh v2.1.0 +# cd /path/to/sems-sources # a checked-out sems repository +# ./release.sh # writes RPMs to ../rhel # -# is any tag, branch or commit SHA that exists in the local clone. -# The build runs in an isolated `git worktree` so the caller's working tree is -# never modified. Output goes to ../rhel relative to the current repository. +# The build runs directly against the current checkout; commit/tag/stash as +# needed before invoking. Output goes to ../rhel relative to the repository. # # Environment overrides: # EL_VERSIONS Space-separated list of EL majors to build. Default "7 8 9 10". @@ -18,6 +17,7 @@ # Layout produced under $OUT_DIR: # /x86_64/*.rpm # /SRPMS/*.src.rpm +# /README.txt (release metadata + build-host rpm -qa) # /{x86_64,SRPMS}/repodata/ (only if createrepo_c is installed locally) # # The existing pkg/rpm/sems.spec + Dockerfile-rhel are the single source of @@ -26,54 +26,32 @@ set -euo pipefail -if [[ $# -lt 1 || "$1" == "-h" || "$1" == "--help" ]]; then - cat >&2 < - tag, branch, or SHA to build from (e.g. v2.1.0) - -Run 'git fetch --tags' first if the tag isn't in the local clone. -EOF - exit 2 -fi - -REF="$1" -REPO_DIR="$(pwd)" - -if ! git -C "$REPO_DIR" rev-parse --git-dir >/dev/null 2>&1; then - echo "error: run this script from the root of a sems git checkout" >&2 - exit 1 -fi - -# Resolve the ref up front so we fail fast with a clear message. -if ! git -C "$REPO_DIR" rev-parse --verify --quiet "${REF}^{commit}" >/dev/null; then - echo "error: git ref '$REF' not found in $REPO_DIR" >&2 - echo " (try: git fetch --tags origin)" >&2 - exit 1 -fi -RESOLVED_SHA="$(git -C "$REPO_DIR" rev-parse "${REF}^{commit}")" -RESOLVED_SHORT="$(git -C "$REPO_DIR" rev-parse --short "$RESOLVED_SHA")" - -# Build in an isolated worktree so the caller's working tree (and any -# uncommitted changes they have) is untouched. -WORKTREE_DIR="$(mktemp -d -t sems-release-XXXXXX)" -cleanup() { - git -C "$REPO_DIR" worktree remove --force "$WORKTREE_DIR" >/dev/null 2>&1 || true - rm -rf "$WORKTREE_DIR" -} -trap cleanup EXIT - -git -C "$REPO_DIR" worktree add --detach "$WORKTREE_DIR" "$RESOLVED_SHA" >/dev/null - -SRC_DIR="$WORKTREE_DIR" +SRC_DIR="$(pwd)" if [[ ! -f "$SRC_DIR/VERSION" || ! -f "$SRC_DIR/pkg/rpm/sems.spec" ]]; then - echo "error: checkout at $REF does not look like a sems tree" >&2 + echo "error: run this script from the root of a sems checkout" >&2 exit 1 fi VERSION="$(cat "$SRC_DIR/VERSION")" -OUT_DIR="${OUT_DIR:-$(cd "$REPO_DIR/.." && pwd)/rhel}" +OUT_DIR="${OUT_DIR:-$(cd "$SRC_DIR/.." && pwd)/rhel}" EL_VERSIONS="${EL_VERSIONS:-7 8 9 10}" +# Capture git metadata if available, but don't require it - the script builds +# whatever is in the working tree regardless of git state. +GIT_SHA="" +GIT_SHORT="" +GIT_DESCRIBE="" +GIT_DIRTY="" +if git -C "$SRC_DIR" rev-parse --git-dir >/dev/null 2>&1; then + GIT_SHA="$(git -C "$SRC_DIR" rev-parse HEAD 2>/dev/null || true)" + GIT_SHORT="$(git -C "$SRC_DIR" rev-parse --short HEAD 2>/dev/null || true)" + GIT_DESCRIBE="$(git -C "$SRC_DIR" describe --tags --always --dirty 2>/dev/null || true)" + if ! git -C "$SRC_DIR" diff --quiet 2>/dev/null || \ + ! git -C "$SRC_DIR" diff --cached --quiet 2>/dev/null; then + GIT_DIRTY="yes" + fi +fi + ENGINE="${CONTAINER_ENGINE:-}" if [[ -z "$ENGINE" ]]; then if command -v docker >/dev/null 2>&1; then @@ -86,9 +64,9 @@ if [[ -z "$ENGINE" ]]; then fi fi -echo "git ref : $REF ($RESOLVED_SHORT)" echo "sems version : $VERSION" -echo "source tree : $SRC_DIR (worktree)" +echo "source tree : $SRC_DIR" +echo "git state : ${GIT_DESCRIBE:-}${GIT_DIRTY:+ (dirty)}" echo "output dir : $OUT_DIR" echo "el targets : $EL_VERSIONS" echo "engine : $ENGINE" @@ -98,7 +76,8 @@ mkdir -p "$OUT_DIR" build_one_el() { local el="$1" local dockerfile="$SRC_DIR/Dockerfile-rhel${el}" - local image="sems-release-el${el}:${VERSION}-${RESOLVED_SHORT}" + local image_tag="${GIT_SHORT:-$VERSION}" + local image="sems-release-el${el}:${image_tag}" local dest="$OUT_DIR/${el}" local staging cid @@ -164,10 +143,14 @@ write_readme() { echo "=======================" echo echo "Release date : ${release_date}" - echo "Git ref : ${REF} (${RESOLVED_SHORT})" - echo "Commit SHA : ${RESOLVED_SHA}" echo "SEMS version : ${VERSION}" echo "EL target : ${el}" + if [[ -n "$GIT_SHA" ]]; then + echo "Git describe : ${GIT_DESCRIBE}${GIT_DIRTY:+ (working tree had uncommitted changes)}" + echo "Commit SHA : ${GIT_SHA}" + else + echo "Git state : not a git checkout" + fi echo "Built with : ${ENGINE} via Dockerfile-rhel${el}" echo "Built on : $(uname -n) ($(uname -sr))" echo @@ -190,6 +173,6 @@ for el in $EL_VERSIONS; do done echo -echo "Done. Built $REF ($RESOLVED_SHORT) as sems-$VERSION." +echo "Done. Built sems-$VERSION from ${GIT_DESCRIBE:-working tree}." echo "Point nginx's document root (or an alias) at:" echo " $OUT_DIR" From 41703622598b800888f17be13fb7a03915a281e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 07:34:59 +0000 Subject: [PATCH 5/6] release.sh: nest artifacts under // for multi-version hosting Lets several SEMS versions share the same nginx tree (e.g. rhel/9/2.1.0/x86_64 alongside rhel/9/2.2.0/x86_64). Each rebuild only wipes its own (el, version) subtree, so older releases are preserved until explicitly removed. --- release.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/release.sh b/release.sh index 1f086fd39..8c3a32bed 100755 --- a/release.sh +++ b/release.sh @@ -14,11 +14,11 @@ # OUT_DIR Output directory. Default "$(dirname $PWD)/rhel". # CONTAINER_ENGINE "docker" or "podman". Auto-detected. # -# Layout produced under $OUT_DIR: -# /x86_64/*.rpm -# /SRPMS/*.src.rpm -# /README.txt (release metadata + build-host rpm -qa) -# /{x86_64,SRPMS}/repodata/ (only if createrepo_c is installed locally) +# Layout produced under $OUT_DIR (multiple SEMS versions can coexist): +# //x86_64/*.rpm +# //SRPMS/*.src.rpm +# //README.txt (release metadata + build-host rpm -qa) +# //{x86_64,SRPMS}/repodata/ (only if createrepo_c is installed locally) # # The existing pkg/rpm/sems.spec + Dockerfile-rhel are the single source of # truth for the build; this script just orchestrates them and extracts the @@ -78,7 +78,7 @@ build_one_el() { local dockerfile="$SRC_DIR/Dockerfile-rhel${el}" local image_tag="${GIT_SHORT:-$VERSION}" local image="sems-release-el${el}:${image_tag}" - local dest="$OUT_DIR/${el}" + local dest="$OUT_DIR/${el}/${VERSION}" local staging cid if [[ ! -f "$dockerfile" ]]; then @@ -88,11 +88,12 @@ build_one_el() { echo echo "==============================================" - echo " Building RPMs for el${el}" + echo " Building RPMs for el${el} (sems ${VERSION})" echo "==============================================" "$ENGINE" build -t "$image" -f "$dockerfile" "$SRC_DIR" - # Replace any previous artifacts for this EL but leave siblings alone. + # Replace any previous artifacts for this exact (el, version) pair but + # leave other versions and other EL trees alone. rm -rf "$dest" mkdir -p "$dest/x86_64" "$dest/SRPMS" @@ -126,7 +127,7 @@ build_one_el() { echo "note: createrepo_c not installed; skipping repodata for el${el}" fi - echo "el${el} artifacts:" + echo "el${el} ${VERSION} artifacts:" ( cd "$dest" && find . -name '*.rpm' | sort | sed 's|^\./| |' ) } From 13de613307087cdff8aec72d0ff01b6a7a704b84 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Apr 2026 07:36:13 +0000 Subject: [PATCH 6/6] Add .dockerignore to keep build/ and .git/ out of image contexts The release.sh script (and existing build_test.yml CI) feeds the working tree to docker as the build context. A local cmake out-of-source build directory or the .git history can each be hundreds of megabytes, all of which docker otherwise streams to the daemon and embeds in image layers via COPY. Excluding both keeps releases reproducible and faster. --- .dockerignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..aaefe1231 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +# Keep the docker build context lean: skip transient build output and the +# git history, both of which can be hundreds of MB and are never needed by +# any of the Dockerfile-rhel / Dockerfile-debian images. +build/ +.git/