name: release # Triggers on semver tag push (v0.1.0, v0.1.1, etc.). Builds release # artifacts for x86_64 and arm64, then publishes them on a GitHub # Release matching the tag. # # Maintainer flow: # git tag v0.1.0 # git push origin v0.1.0 # → CI builds + publishes release with skeletonkey-x86_64 + skeletonkey-arm64 on: push: tags: ['v*.*.*'] workflow_dispatch: # allow manual re-runs permissions: contents: write # needed by softprops/action-gh-release jobs: build: strategy: fail-fast: false matrix: include: - target: x86_64 cc: gcc apt: build-essential - target: arm64 cc: aarch64-linux-gnu-gcc apt: build-essential gcc-aarch64-linux-gnu libc6-dev-arm64-cross linux-libc-dev-arm64-cross name: build (${{ matrix.target }}) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: install build deps run: | sudo apt-get update -qq sudo apt-get install -y --no-install-recommends ${{ matrix.apt }} linux-libc-dev - name: build env: CC: ${{ matrix.cc }} run: | make file skeletonkey ls -la skeletonkey - name: rename + checksum run: | mv skeletonkey skeletonkey-${{ matrix.target }} sha256sum skeletonkey-${{ matrix.target }} > skeletonkey-${{ matrix.target }}.sha256 - uses: actions/upload-artifact@v4 with: name: skeletonkey-${{ matrix.target }} path: | skeletonkey-${{ matrix.target }} skeletonkey-${{ matrix.target }}.sha256 # Portable static-musl x86_64 build. Runs in Alpine (native musl + # linux-headers) so the resulting binary works on every libc — # glibc 2.x of any version, musl, etc. This is what install.sh # fetches by default for x86_64 hosts (the dynamic binary above # hits a glibc-version ceiling on older distros like Debian 12 / # RHEL 8). build-static-x86_64: runs-on: ubuntu-latest name: build (x86_64-static / musl) container: image: alpine:latest steps: - uses: actions/checkout@v4 - name: install build deps run: apk add --no-cache build-base linux-headers tar - name: build static (musl) run: | # MSG_COPY is a Linux-only SysV msg flag that glibc defines # but musl does not — netfilter_xtcompat needs it. Define # the kernel constant explicitly. (Kernel: include/uapi/ # linux/msg.h: MSG_COPY = 040000) make CFLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-pointer-arith -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -DMSG_COPY=040000" LDFLAGS=-static file skeletonkey ls -la skeletonkey - name: rename + checksum run: | mv skeletonkey skeletonkey-x86_64-static sha256sum skeletonkey-x86_64-static > skeletonkey-x86_64-static.sha256 - uses: actions/upload-artifact@v4 with: name: skeletonkey-x86_64-static path: | skeletonkey-x86_64-static skeletonkey-x86_64-static.sha256 # Portable static-musl arm64 build. Cross-compile from the x86_64 # runner using dockcross/linux-arm64-musl — a Debian-based cross # toolchain image that ships aarch64-linux-musl-gcc with a clean # musl sysroot + Linux uapi headers. Avoids the two prior failure # modes: # (1) Alpine on arm64: actions/checkout JS bundle requires glibc- # compatible Node, which GitHub doesn't inject on arm64. # (2) musl-tools on ubuntu-24.04-arm: musl-gcc + Ubuntu's # /usr/include collide (glibc stdio.h vs musl stdio.h → # __gnuc_va_list / __time64_t conflicts). # dockcross runs glibc Debian (so checkout works), invokes a # bundled aarch64-linux-musl-gcc whose sysroot has its own # consistent musl + linux-uapi tree. build-static-arm64: runs-on: ubuntu-latest name: build (arm64-static / musl) steps: - uses: actions/checkout@v4 - name: run dockcross arm64-musl build run: | # Fetch the dockcross wrapper script (handles UID/GID, # volume mounts, env passing). Image already has # aarch64-linux-musl-gcc on PATH. docker run --rm dockcross/linux-arm64-musl > ./dockcross chmod +x ./dockcross ./dockcross bash -c ' make CC=aarch64-linux-musl-gcc \ CFLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-pointer-arith -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -DMSG_COPY=040000" \ LDFLAGS=-static ' file skeletonkey ls -la skeletonkey - name: rename + checksum run: | mv skeletonkey skeletonkey-arm64-static sha256sum skeletonkey-arm64-static > skeletonkey-arm64-static.sha256 - uses: actions/upload-artifact@v4 with: name: skeletonkey-arm64-static path: | skeletonkey-arm64-static skeletonkey-arm64-static.sha256 release: needs: [build, build-static-x86_64, build-static-arm64] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: path: dist - name: flatten artifacts run: | find dist -type f -exec mv {} . \; ls -la skeletonkey-* - name: collect release notes id: notes run: | tag="${GITHUB_REF#refs/tags/}" echo "tag=$tag" >> "$GITHUB_OUTPUT" # Prefer the hand-written release notes if present (richer # per-release context); otherwise fall back to an auto-generated # stub with install instructions + pointers to docs. if [ -f docs/RELEASE_NOTES.md ]; then cp docs/RELEASE_NOTES.md release-notes.md else { echo "## SKELETONKEY $tag" echo echo "Pre-built binaries for x86_64 (dynamic + static-musl) and arm64." echo "Checksums alongside each artifact." echo echo "### Install" echo '```bash' echo "curl -sSL https://github.com/${GITHUB_REPOSITORY}/releases/download/${tag}/install.sh | sh" echo "skeletonkey --version" echo '```' echo echo "See [\`CVES.md\`](https://github.com/${GITHUB_REPOSITORY}/blob/${tag}/CVES.md) for the CVE inventory." echo "See [\`docs/RELEASE_NOTES.md\`](https://github.com/${GITHUB_REPOSITORY}/blob/${tag}/docs/RELEASE_NOTES.md) for per-release detail." } > release-notes.md fi - name: publish release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.notes.outputs.tag }} name: SKELETONKEY ${{ steps.notes.outputs.tag }} body_path: release-notes.md files: | skeletonkey-x86_64 skeletonkey-x86_64.sha256 skeletonkey-x86_64-static skeletonkey-x86_64-static.sha256 skeletonkey-arm64 skeletonkey-arm64.sha256 skeletonkey-arm64-static skeletonkey-arm64-static.sha256 install.sh fail_on_unmatched_files: false # install.sh may not exist at first tag