From 52e1932bdec58676a7fe95598c8c808a7a950116 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 17 Mar 2026 22:02:52 +0000 Subject: [PATCH] feat: multi-architecture builds (x86_64 + arm64) - build-rpm.sh: --arch flag for targeting x86_64 or arm64, --all for both Uses bun cross-compile with --target=bun-linux-x64/arm64 - build-bastion.sh: --arch flag for Docker platform targeting - release.sh: builds both architectures by default - CI: builds + publishes RPM/DEB for both architectures Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/ci.yml | 124 ++++++++++++---------- bastion/scripts/build-bastion.sh | 65 +++++++++++- bastion/scripts/build-rpm.sh | 175 +++++++++++++++++++++++++++---- bastion/scripts/release.sh | 4 +- 4 files changed, 288 insertions(+), 80 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index f6459fc..7474bff 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: - name: Run tests run: pnpm test:run - # -- Build & package --------------------------------------- + # -- Build & package (both architectures) ------------------- build: runs-on: ubuntu-latest @@ -112,28 +112,38 @@ jobs: curl -sL -o /tmp/nfpm.tar.gz "https://github.com/goreleaser/nfpm/releases/download/v2.45.0/nfpm_2.45.0_Linux_x86_64.tar.gz" tar xzf /tmp/nfpm.tar.gz -C /usr/local/bin nfpm - - name: Bundle standalone binary + - name: Bundle x86_64 binary run: | mkdir -p dist - bun build src/cli/src/index.ts --compile --outfile dist/lab + bun build src/cli/src/index.ts --compile --target=bun-linux-x64 --outfile dist/lab-x86_64 - - name: Package RPM - run: nfpm pkg --packager rpm --target dist/ + - name: Bundle arm64 binary + run: | + bun build src/cli/src/index.ts --compile --target=bun-linux-arm64 --outfile dist/lab-arm64 - - name: Package DEB - run: nfpm pkg --packager deb --target dist/ + - name: Package x86_64 RPM + DEB + run: | + sed -e 's|^arch:.*|arch: amd64|' -e 's|src: ./dist/lab$|src: ./dist/lab-x86_64|' nfpm.yaml > /tmp/nfpm-x86_64.yaml + nfpm pkg --config /tmp/nfpm-x86_64.yaml --packager rpm --target dist/ + nfpm pkg --config /tmp/nfpm-x86_64.yaml --packager deb --target dist/ - - name: Upload RPM artifact + - name: Package arm64 RPM + DEB + run: | + sed -e 's|^arch:.*|arch: arm64|' -e 's|src: ./dist/lab$|src: ./dist/lab-arm64|' nfpm.yaml > /tmp/nfpm-arm64.yaml + nfpm pkg --config /tmp/nfpm-arm64.yaml --packager rpm --target dist/ + nfpm pkg --config /tmp/nfpm-arm64.yaml --packager deb --target dist/ + + - name: Upload RPM artifacts uses: actions/upload-artifact@v3 with: - name: rpm-package + name: rpm-packages path: bastion/dist/lab-*.rpm retention-days: 7 - - name: Upload DEB artifact + - name: Upload DEB artifacts uses: actions/upload-artifact@v3 with: - name: deb-package + name: deb-packages path: bastion/dist/lab*.deb retention-days: 7 @@ -149,45 +159,48 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Download RPM artifact + - name: Download RPM artifacts uses: actions/download-artifact@v3 with: - name: rpm-package + name: rpm-packages path: bastion/dist/ - name: Install rpm tools run: sudo apt-get update && sudo apt-get install -y rpm - - name: Publish RPM to Gitea + - name: Publish RPMs to Gitea env: GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }} GITEA_URL: http://${{ env.GITEA_REGISTRY }} GITEA_OWNER: ${{ env.GITEA_OWNER }} GITEA_REPO: lab run: | - RPM_FILE=$(ls dist/lab-*.rpm | head -1) - RPM_VERSION=$(rpm -qp --queryformat '%{VERSION}-%{RELEASE}' "$RPM_FILE") - echo "Publishing $RPM_FILE (version $RPM_VERSION)..." + for RPM_FILE in dist/lab-*.rpm; do + [ -f "$RPM_FILE" ] || continue + RPM_VERSION=$(rpm -qp --queryformat '%{VERSION}-%{RELEASE}' "$RPM_FILE") + RPM_ARCH=$(rpm -qp --queryformat '%{ARCH}' "$RPM_FILE") + echo "Publishing $RPM_FILE (version $RPM_VERSION, arch $RPM_ARCH)..." - # Delete existing version if present - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - "${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}") - - if [ "$HTTP_CODE" = "200" ]; then - echo "Version exists, replacing..." - curl -s -o /dev/null -X DELETE \ + # Delete existing version if present + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: token ${GITEA_TOKEN}" \ - "${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}" - fi + "${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}") - # Upload - curl --fail -X PUT \ - -H "Authorization: token ${GITEA_TOKEN}" \ - --upload-file "$RPM_FILE" \ - "${GITEA_URL}/api/packages/${GITEA_OWNER}/rpm/upload" + if [ "$HTTP_CODE" = "200" ]; then + echo "Version exists, replacing..." + curl -s -o /dev/null -X DELETE \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}" + fi - echo "Published successfully!" + # Upload + curl --fail -X PUT \ + -H "Authorization: token ${GITEA_TOKEN}" \ + --upload-file "$RPM_FILE" \ + "${GITEA_URL}/api/packages/${GITEA_OWNER}/rpm/upload" + + echo "Published $RPM_FILE successfully!" + done # Link package to repo source scripts/link-package.sh @@ -203,41 +216,44 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Download DEB artifact + - name: Download DEB artifacts uses: actions/download-artifact@v3 with: - name: deb-package + name: deb-packages path: bastion/dist/ - - name: Publish DEB to Gitea + - name: Publish DEBs to Gitea env: GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }} GITEA_URL: http://${{ env.GITEA_REGISTRY }} GITEA_OWNER: ${{ env.GITEA_OWNER }} GITEA_REPO: lab run: | - DEB_FILE=$(ls dist/lab*.deb | head -1) - DEB_VERSION=$(dpkg-deb --field "$DEB_FILE" Version) - echo "Publishing $DEB_FILE (version $DEB_VERSION)..." - # Publish to each supported distribution DISTRIBUTIONS="trixie forky noble plucky" - for DIST in $DISTRIBUTIONS; do - echo " -> $DIST..." - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ - -X PUT \ - -H "Authorization: token ${GITEA_TOKEN}" \ - --upload-file "$DEB_FILE" \ - "${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/pool/${DIST}/main/upload") + for DEB_FILE in dist/lab*.deb; do + [ -f "$DEB_FILE" ] || continue + DEB_VERSION=$(dpkg-deb --field "$DEB_FILE" Version) + DEB_ARCH=$(dpkg-deb --field "$DEB_FILE" Architecture) + echo "Publishing $DEB_FILE (version $DEB_VERSION, arch $DEB_ARCH)..." - if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then - echo " Published to $DIST" - elif [ "$HTTP_CODE" = "409" ]; then - echo " Already exists in $DIST (skipping)" - else - echo " WARNING: Upload to $DIST returned HTTP $HTTP_CODE" - fi + for DIST in $DISTRIBUTIONS; do + echo " -> $DIST..." + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -X PUT \ + -H "Authorization: token ${GITEA_TOKEN}" \ + --upload-file "$DEB_FILE" \ + "${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/pool/${DIST}/main/upload") + + if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then + echo " Published to $DIST" + elif [ "$HTTP_CODE" = "409" ]; then + echo " Already exists in $DIST (skipping)" + else + echo " WARNING: Upload to $DIST returned HTTP $HTTP_CODE" + fi + done done echo "Published successfully!" diff --git a/bastion/scripts/build-bastion.sh b/bastion/scripts/build-bastion.sh index 5ebd181..11d1159 100755 --- a/bastion/scripts/build-bastion.sh +++ b/bastion/scripts/build-bastion.sh @@ -11,14 +11,72 @@ if [ -f .env ]; then set -a; source .env; set +a fi +# ── Argument parsing ─────────────────────────────────────────────── +TARGET_ARCH="" + +usage() { + cat < Building bastion image (tag: $TAG)..." -podman build -t "$IMAGE:$TAG" -f stack/Dockerfile . +# ── Resolve target platform ─────────────────────────────────────── +detect_host_arch() { + local machine + machine="$(uname -m)" + case "$machine" in + x86_64) echo "x86_64" ;; + aarch64) echo "arm64" ;; + arm64) echo "arm64" ;; + *) echo "$machine" ;; + esac +} + +docker_platform_for() { + case "$1" in + x86_64) echo "linux/amd64" ;; + arm64) echo "linux/arm64" ;; + esac +} + +ARCH="${TARGET_ARCH:-$(detect_host_arch)}" +PLATFORM="$(docker_platform_for "$ARCH")" + +echo "==> Building bastion image (tag: $TAG, platform: $PLATFORM)..." +podman build --platform "$PLATFORM" -t "$IMAGE:$TAG" -f stack/Dockerfile . echo "==> Tagging as $REGISTRY/michal/$IMAGE:$TAG..." podman tag "$IMAGE:$TAG" "$REGISTRY/michal/$IMAGE:$TAG" @@ -41,3 +99,4 @@ fi echo "==> Done!" echo " Image: $REGISTRY/michal/$IMAGE:$TAG" +echo " Platform: $PLATFORM" diff --git a/bastion/scripts/build-rpm.sh b/bastion/scripts/build-rpm.sh index 8308398..8b14bd7 100755 --- a/bastion/scripts/build-rpm.sh +++ b/bastion/scripts/build-rpm.sh @@ -13,9 +13,147 @@ fi # Ensure tools are on PATH export PATH="$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH" -echo "==> Running unit tests..." -pnpm test:run -echo "" +# ── Argument parsing ─────────────────────────────────────────────── +BUILD_ALL=false +TARGET_ARCH="" +SKIP_TESTS=false + +usage() { + cat < Bundling standalone binary for ${arch}..." + bun build src/cli/src/index.ts --compile --target="${bun_target}" --outfile "${binary_name}" + + echo "==> Packaging RPM (${arch})..." + # Create a temporary nfpm config with the correct arch and binary path + local tmpconfig + tmpconfig="$(mktemp /tmp/nfpm-XXXXXX.yaml)" + sed -e "s|^arch:.*|arch: ${nfpm_arch}|" \ + -e "s|src: ./dist/lab$|src: ./${binary_name}|" \ + nfpm.yaml > "$tmpconfig" + + nfpm pkg --config "$tmpconfig" --packager rpm --target dist/ + rm -f "$tmpconfig" + + local rpm_arch + rpm_arch="$(rpm_arch_for "$arch")" + RPM_FILE=$(ls dist/lab-*.${rpm_arch}.rpm 2>/dev/null | head -1) + echo "==> Built: $RPM_FILE" + echo " Size: $(du -h "$RPM_FILE" | cut -f1)" + + echo "" + echo "==> Packaging DEB (${arch})..." + local deb_arch + deb_arch="$(deb_arch_for "$arch")" + + tmpconfig="$(mktemp /tmp/nfpm-XXXXXX.yaml)" + sed -e "s|^arch:.*|arch: ${nfpm_arch}|" \ + -e "s|src: ./dist/lab$|src: ./${binary_name}|" \ + nfpm.yaml > "$tmpconfig" + + nfpm pkg --config "$tmpconfig" --packager deb --target dist/ + rm -f "$tmpconfig" + + DEB_FILE=$(ls dist/lab_*_${deb_arch}.deb 2>/dev/null | head -1) + echo "==> Built: $DEB_FILE" + echo " Size: $(du -h "$DEB_FILE" | cut -f1)" +} + +# ── Main ────────────────────────────────────────────────────────── + +if [ "$SKIP_TESTS" = false ]; then + echo "==> Running unit tests..." + pnpm test:run + echo "" +fi echo "==> Building TypeScript..." pnpm build @@ -23,25 +161,20 @@ pnpm build echo "==> Generating shell completions..." pnpm completions:generate -echo "==> Bundling standalone binary..." mkdir -p dist -rm -f dist/lab dist/lab-*.rpm dist/lab*.deb +rm -f dist/lab dist/lab-x86_64 dist/lab-arm64 dist/lab-*.rpm dist/lab*.deb -bun build src/cli/src/index.ts --compile --outfile dist/lab - -echo "==> Packaging RPM..." -nfpm pkg --packager rpm --target dist/ - -RPM_FILE=$(ls dist/lab-*.rpm 2>/dev/null | head -1) -echo "==> Built: $RPM_FILE" -echo " Size: $(du -h "$RPM_FILE" | cut -f1)" -rpm -qpi "$RPM_FILE" +if [ "$BUILD_ALL" = true ]; then + build_arch "x86_64" + build_arch "arm64" +elif [ -n "$TARGET_ARCH" ]; then + build_arch "$TARGET_ARCH" +else + # Default to host architecture + HOST_ARCH="$(detect_host_arch)" + build_arch "$HOST_ARCH" +fi echo "" -echo "==> Packaging DEB..." -rm -f dist/lab*.deb -nfpm pkg --packager deb --target dist/ - -DEB_FILE=$(ls dist/lab*.deb 2>/dev/null | head -1) -echo "==> Built: $DEB_FILE" -echo " Size: $(du -h "$DEB_FILE" | cut -f1)" +echo "==> Build complete. Artifacts in dist/:" +ls -lh dist/lab* 2>/dev/null || echo " (none)" diff --git a/bastion/scripts/release.sh b/bastion/scripts/release.sh index 9836e92..f2ae711 100755 --- a/bastion/scripts/release.sh +++ b/bastion/scripts/release.sh @@ -13,8 +13,8 @@ fi echo "=== lab-bastion release ===" echo "" -# 1. Build binaries & packages -bash scripts/build-rpm.sh +# 1. Build binaries & packages (both architectures) +bash scripts/build-rpm.sh --all echo ""