Files
rust-package-builder/scripts/build-linux-release.sh
T
2026-10-19 22:39:09 +02:00

230 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
source /opt/rust-package-builder/scripts/lib.sh
: "${SHA:?SHA required}"
: "${SRC:?SRC required}"
: "${PKG:?PKG required}"
: "${OUT:?OUT required}"
: "${VERSION:?VERSION required}"
: "${BINARY_NAME:?BINARY_NAME required}"
: "${PACKAGE_NAME:?PACKAGE_NAME required}"
REPOSITORY="$DATA_DIR/repository"
APTLY_CONF="$DATA_DIR/state/aptly.conf"
BRANCH_RELEASE_ID="$(date -u '+%Y%m%dT%H%M%SZ')-${SHORT_SHA}-linux"
RELEASE_DIR="$REPOSITORY/releases/$BRANCH_RELEASE_ID"
PUBLISHED=0
cleanup_linux_release() {
if [[ "$PUBLISHED" != "1" && -d "$RELEASE_DIR" ]]; then
rm -rf "$RELEASE_DIR"
fi
}
trap cleanup_linux_release EXIT
REQUESTED_PACKAGE_RELEASE=$PACKAGE_RELEASE
if [[ "$REQUESTED_PACKAGE_RELEASE" == "auto" ]]; then
LAST_LINUX_COMMIT=""
LAST_VERSION=""
LAST_PACKAGE_RELEASE=0
[[ -r "$DATA_DIR/state/last_linux_commit" ]] && LAST_LINUX_COMMIT=$(cat "$DATA_DIR/state/last_linux_commit")
[[ -r "$DATA_DIR/state/last_version" ]] && LAST_VERSION=$(cat "$DATA_DIR/state/last_version")
[[ -r "$DATA_DIR/state/last_package_release" ]] && LAST_PACKAGE_RELEASE=$(cat "$DATA_DIR/state/last_package_release")
CURRENT_LINUX_PACKAGING_REVISION=""
if [[ -r "$REPOSITORY/current/LINUX-BUILD-INFO.txt" ]]; then
CURRENT_LINUX_PACKAGING_REVISION=$(sed -n 's/^packaging_revision=//p' "$REPOSITORY/current/LINUX-BUILD-INFO.txt" | head -n1)
fi
if [[ "$LAST_LINUX_COMMIT" == "$SHA" \
&& "$LAST_VERSION" == "$VERSION" \
&& "$CURRENT_LINUX_PACKAGING_REVISION" == "$PACKAGING_REVISION" \
&& "$LAST_PACKAGE_RELEASE" =~ ^[1-9][0-9]*$ ]]; then
PACKAGE_RELEASE=$LAST_PACKAGE_RELEASE
log "Linux repair build: reusing package release $PACKAGE_RELEASE"
elif [[ "$LAST_VERSION" == "$VERSION" && "$LAST_PACKAGE_RELEASE" =~ ^[0-9]+$ ]]; then
PACKAGE_RELEASE=$((LAST_PACKAGE_RELEASE + 1))
else
PACKAGE_RELEASE=1
fi
else
PACKAGE_RELEASE=$REQUESTED_PACKAGE_RELEASE
fi
export PACKAGE_RELEASE
cd "$SRC"
LINUX_RUST_TOOLCHAIN=$(/opt/rust-package-builder/scripts/ensure-rust-toolchain.sh "$SRC" linux)
log "Linux branch: version $VERSION release $PACKAGE_RELEASE"
TEST_MODE="${RUN_LINUX_TESTS:-compile}"
case "$TEST_MODE" in
0|off|false|no)
log "Linux cargo tests skipped"
;;
1|compile|check|no-run)
log "Linux cargo tests compile check started (--no-run)"
if [[ -f Cargo.lock ]]; then
cargo "+$LINUX_RUST_TOOLCHAIN" test --release --locked --no-run
else
cargo "+$LINUX_RUST_TOOLCHAIN" test --release --no-run
fi
log "Linux cargo tests compile check completed"
;;
full|run)
log "Linux full cargo tests started"
if [[ -f Cargo.lock ]]; then
cargo "+$LINUX_RUST_TOOLCHAIN" test --release --locked
else
cargo "+$LINUX_RUST_TOOLCHAIN" test --release
fi
log "Linux full cargo tests completed"
;;
*)
fatal "invalid RUN_LINUX_TESTS=$TEST_MODE (use: 0, 1/compile, or full)"
;;
esac
log "Linux cargo build started"
if [[ -f Cargo.lock ]]; then
cargo "+$LINUX_RUST_TOOLCHAIN" build --release --locked
else
cargo "+$LINUX_RUST_TOOLCHAIN" build --release
fi
log "Linux cargo build completed"
BINARY_PATH="$CARGO_TARGET_DIR/release/$BINARY_NAME"
[[ -x "$BINARY_PATH" ]] || fatal "built binary not found: $BINARY_PATH"
MAX_GLIBC_FOUND=$(objdump -T "$BINARY_PATH" 2>/dev/null | grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sed 's/^GLIBC_//' | sort -V | tail -n1 || true)
if [[ -n "$MAX_GLIBC_FOUND" ]]; then
if [[ "$(printf '%s\n%s\n' "$MAX_GLIBC_VERSION" "$MAX_GLIBC_FOUND" | sort -V | tail -n1)" != "$MAX_GLIBC_VERSION" ]]; then
fatal "binary requires GLIBC_$MAX_GLIBC_FOUND, target maximum is GLIBC_$MAX_GLIBC_VERSION"
fi
log "glibc compatibility check: <= $MAX_GLIBC_FOUND"
fi
# The Go agent is packaged ONLY for Linux. A failure aborts the Linux branch
# before either APT or RPM metadata is published; OPNsense/FreeBSD are separate.
AGENT_SRC="$SRC/docker-agent"
[[ -f "$AGENT_SRC/go.mod" ]] || fatal "RustProxy source is missing docker-agent/go.mod"
AGENT_BINARY="$OUT/rustproxy-docker-agent"
AGENT_PKG="$WORK/package-agent"
log "Linux Docker Agent tests started"
(cd "$AGENT_SRC" && GOWORK=off CGO_ENABLED=0 go test ./...)
log "Linux Docker Agent build started"
(cd "$AGENT_SRC" && GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w -X main.version=$VERSION" -o "$AGENT_BINARY" .)
[[ -x "$AGENT_BINARY" ]] || fatal "built Go agent not found: $AGENT_BINARY"
/opt/rust-package-builder/scripts/prepare-package.sh "$SRC" "$PKG" "$BINARY_PATH"
/opt/rust-package-builder/scripts/prepare-agent-package.sh "$SRC" "$AGENT_PKG" "$AGENT_BINARY"
DEB_FILE="$OUT/${PACKAGE_NAME}_${VERSION}-${PACKAGE_RELEASE}_amd64.deb"
RPM_FILE="$OUT/${PACKAGE_NAME}-${VERSION}-${PACKAGE_RELEASE}.x86_64.rpm"
AGENT_DEB_FILE="$OUT/rustproxy-docker-agent_${VERSION}-${PACKAGE_RELEASE}_amd64.deb"
AGENT_RPM_FILE="$OUT/rustproxy-docker-agent-${VERSION}-${PACKAGE_RELEASE}.x86_64.rpm"
log "creating RustProxy and Docker Agent deb packages"
nfpm package --config "$PKG/nfpm-deb.yaml" --packager deb --target "$DEB_FILE"
nfpm package --config "$AGENT_PKG/nfpm-deb.yaml" --packager deb --target "$AGENT_DEB_FILE"
log "creating RustProxy and Docker Agent rpm packages"
nfpm package --config "$PKG/nfpm-rpm.yaml" --packager rpm --target "$RPM_FILE"
nfpm package --config "$AGENT_PKG/nfpm-rpm.yaml" --packager rpm --target "$AGENT_RPM_FILE"
for deb in "$DEB_FILE" "$AGENT_DEB_FILE"; do
ar t "$deb" | grep '^_gpgorigin$' >/dev/null || fatal "DEB package is not signed: $deb"
done
for rpm_file in "$RPM_FILE" "$AGENT_RPM_FILE"; do
rpm -qpi "$rpm_file" | awk -F: '/^Signature/ && $0 !~ /\(none\)/ {ok=1} END{exit !ok}' \
|| fatal "RPM package is not signed: $rpm_file"
done
log "Linux package signatures verified"
cp -f "$DEB_FILE" "$AGENT_DEB_FILE" "$DATA_DIR/packages/deb/"
cp -f "$RPM_FILE" "$AGENT_RPM_FILE" "$DATA_DIR/packages/rpm/x86_64/"
log "updating aptly internal repository"
aptly -config="$APTLY_CONF" repo add -force-replace package-builder "$DEB_FILE" "$AGENT_DEB_FILE" >/dev/null
SNAPSHOT="linux-${VERSION}-${SHORT_SHA}-$(date +%s)"
aptly -config="$APTLY_CONF" snapshot create "$SNAPSHOT" from repo package-builder >/dev/null
APTLY_SIGN_ARGS=(-batch -gpg-key="$GPG_KEY_ID")
if [[ -r "$GPG_PASSPHRASE_FILE" ]]; then
APTLY_SIGN_ARGS+=(-passphrase-file="$GPG_PASSPHRASE_FILE")
fi
if aptly -config="$APTLY_CONF" publish list -raw | awk -v d="$REPO_DISTRIBUTION" '$2 == d {found=1} END{exit !found}'; then
aptly -config="$APTLY_CONF" publish switch -force-overwrite "${APTLY_SIGN_ARGS[@]}" "$REPO_DISTRIBUTION" . "$SNAPSHOT" >/dev/null
else
aptly -config="$APTLY_CONF" publish snapshot -force-overwrite "${APTLY_SIGN_ARGS[@]}" \
-distribution="$REPO_DISTRIBUTION" -component="$REPO_COMPONENT" -architectures=amd64 "$SNAPSHOT" >/dev/null
fi
APT_PUBLIC="$DATA_DIR/aptly/public"
[[ -f "$APT_PUBLIC/dists/$REPO_DISTRIBUTION/InRelease" ]] || fatal "aptly did not create InRelease"
[[ -f "$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release.gpg" ]] || fatal "aptly did not create Release.gpg"
gpg --batch --verify "$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release.gpg" "$APT_PUBLIC/dists/$REPO_DISTRIBUTION/Release" >/dev/null 2>&1 \
|| fatal "APT Release signature verification failed"
mkdir -p "$RELEASE_DIR/apt" "$RELEASE_DIR/rpm/x86_64" "$RELEASE_DIR/opnsense" "$RELEASE_DIR/freebsd"
CURRENT_TARGET=""
if [[ -L "$REPOSITORY/current" ]]; then
CURRENT_LINK=$(readlink "$REPOSITORY/current")
CURRENT_TARGET="$REPOSITORY/$CURRENT_LINK"
fi
log "staging Linux repository snapshot"
if [[ -n "$CURRENT_TARGET" ]]; then
for component in opnsense freebsd; do
if [[ -d "$CURRENT_TARGET/$component" ]]; then
rsync -a --delete --link-dest="$CURRENT_TARGET/$component"/ "$CURRENT_TARGET/$component"/ "$RELEASE_DIR/$component"/
fi
done
fi
rsync -a --delete "$APT_PUBLIC"/ "$RELEASE_DIR/apt"/
rsync -a --delete "$DATA_DIR/packages/rpm"/ "$RELEASE_DIR/rpm"/
rm -rf "$RELEASE_DIR/rpm/x86_64/repodata"
createrepo_c --update "$RELEASE_DIR/rpm/x86_64" >/dev/null
GPG_SIGN_ARGS=(--batch --yes --pinentry-mode loopback)
if [[ -r "$GPG_PASSPHRASE_FILE" ]]; then
GPG_SIGN_ARGS+=(--passphrase-file "$GPG_PASSPHRASE_FILE")
fi
gpg "${GPG_SIGN_ARGS[@]}" --armor --detach-sign --local-user "$GPG_KEY_ID" \
--output "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml.asc" "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml"
gpg --batch --verify "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml.asc" "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" >/dev/null 2>&1 \
|| fatal "RPM metadata signature verification failed"
gpg --batch --armor --export "$GPG_KEY_ID" > "$RELEASE_DIR/repo-key.gpg"
[[ -s "$RELEASE_DIR/repo-key.gpg" ]] || fatal "public GPG key export failed"
/opt/rust-package-builder/scripts/generate-index.sh "$RELEASE_DIR/index.html" "$PACKAGE_NAME"
cat > "$RELEASE_DIR/LINUX-BUILD-INFO.txt" <<INFO
app_version=$VERSION
package_release=$PACKAGE_RELEASE
packaging_revision=$PACKAGING_REVISION
linux_packages=$PACKAGE_NAME,rustproxy-docker-agent
source_url=$SOURCE_REPO
source_ref=$SOURCE_BRANCH
source_commit=$SHA
build_reason=$BUILD_REASON
INFO
[[ -s "$RELEASE_DIR/index.html" ]]
[[ -f "$RELEASE_DIR/apt/dists/$REPO_DISTRIBUTION/InRelease" ]]
[[ -f "$RELEASE_DIR/rpm/x86_64/repodata/repomd.xml" ]]
log "publishing Linux repository branch"
rm -f "$REPOSITORY/current.new"
ln -s "releases/$BRANCH_RELEASE_ID" "$REPOSITORY/current.new"
mv -Tf "$REPOSITORY/current.new" "$REPOSITORY/current"
PUBLISHED=1
printf '%s\n' "$SHA" | atomic_write "$DATA_DIR/state/last_linux_commit"
printf '%s\n' "$VERSION" | atomic_write "$DATA_DIR/state/last_version"
printf '%s\n' "$PACKAGE_RELEASE" | atomic_write "$DATA_DIR/state/last_package_release"
printf '%s\n' "$BRANCH_RELEASE_ID" | atomic_write "$DATA_DIR/state/last_linux_release"
printf '%s\n' "$BRANCH_RELEASE_ID" | atomic_write "$DATA_DIR/state/last_release"
log "Linux publication completed: version=$VERSION release=$PACKAGE_RELEASE commit=$SHA"