musl libc has weak fork-safety guarantees in multi-threaded processes. When Tokio's runtime is running and we fork() for SNMP operations, musl's internal state can be inconsistent in the child, causing SIGSEGV on every SNMP walk to Ubiquiti devices. glibc handles this via pthread_atfork. - Dockerfile: Alpine → rust:1.93-bookworm build + debian:12-slim runtime - CI: Remove ensure-netsnmp-base/manifest jobs, simplify build deps - Delete Dockerfile.netsnmp and netsnmp-base.yml workflow - Add NULL pointer guards in snmp_helper.c for fork-safety - Add SNMP exception type handling (NoSuchObject/Instance/EndOfMibView) - Add crash reproduction tests
327 lines
10 KiB
YAML
327 lines
10 KiB
YAML
# GitHub Actions CI/CD Configuration for Towerops Agent
|
|
# Builds and publishes Docker image to GitHub Container Registry
|
|
|
|
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- "**"
|
|
tags:
|
|
- "v*"
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: ${{ github.repository }}
|
|
DOCKERHUB_IMAGE: gmcintire/towerops-agent
|
|
CARGO_TERM_COLOR: always
|
|
RUSTFLAGS: -C link-arg=-fuse-ld=lld
|
|
|
|
jobs:
|
|
test:
|
|
name: Test
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Mount Cargo target
|
|
uses: useblacksmith/stickydisk@v1
|
|
with:
|
|
key: ${{ github.repository }}-cargo-target
|
|
path: ./target
|
|
|
|
- name: Mount Cargo registry
|
|
uses: useblacksmith/stickydisk@v1
|
|
with:
|
|
key: ${{ github.repository }}-cargo-registry
|
|
path: ~/.cargo/registry
|
|
|
|
- name: Mount Cargo git
|
|
uses: useblacksmith/stickydisk@v1
|
|
with:
|
|
key: ${{ github.repository }}-cargo-git
|
|
path: ~/.cargo/git
|
|
|
|
- name: Mount Rust toolchain
|
|
uses: useblacksmith/stickydisk@v1
|
|
with:
|
|
key: ${{ github.repository }}-rustup
|
|
path: ~/.rustup
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
toolchain: "1.93"
|
|
components: rustfmt, clippy
|
|
|
|
- name: Cache apt packages
|
|
uses: awalsh128/cache-apt-pkgs-action@latest
|
|
with:
|
|
packages: protobuf-compiler lld libsnmp-dev
|
|
version: 1.0
|
|
|
|
- name: Format check
|
|
run: cargo fmt -- --check
|
|
|
|
- name: Check
|
|
run: cargo check --release
|
|
|
|
- name: Test
|
|
run: cargo test
|
|
|
|
# Temporarily disabled:
|
|
# - name: Clippy
|
|
# run: cargo clippy -- -D warnings
|
|
|
|
build-branch:
|
|
name: Build (Branch)
|
|
needs: [test]
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
if: github.event_name == 'push' && github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/')
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Blacksmith Builder
|
|
uses: useblacksmith/setup-docker-builder@v1
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract branch name
|
|
id: branch
|
|
run: |
|
|
BRANCH_NAME=${GITHUB_REF#refs/heads/}
|
|
# Sanitize branch name for Docker tag (replace / with -)
|
|
SAFE_BRANCH=$(echo "$BRANCH_NAME" | sed 's/\//-/g')
|
|
echo "name=$SAFE_BRANCH" >> $GITHUB_OUTPUT
|
|
|
|
- name: Build and push
|
|
uses: useblacksmith/build-push-action@v2
|
|
with:
|
|
context: .
|
|
push: true
|
|
provenance: false
|
|
build-args: |
|
|
BUILDKIT_INLINE_CACHE=1
|
|
tags: |
|
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.branch.outputs.name }}
|
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
|
|
|
|
# Generate version info for release builds
|
|
version:
|
|
name: Version
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
|
outputs:
|
|
version: ${{ steps.version.outputs.version }}
|
|
short_sha: ${{ steps.version.outputs.short_sha }}
|
|
is_tag: ${{ steps.version.outputs.is_tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Generate version info
|
|
id: version
|
|
run: |
|
|
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
|
TAG=${GITHUB_REF#refs/tags/}
|
|
VERSION=${TAG#v}
|
|
echo "is_tag=true" >> $GITHUB_OUTPUT
|
|
else
|
|
VERSION=$(git describe --tags --always --dirty=-modified | sed 's/^v//')
|
|
echo "is_tag=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
SHORT_SHA=${GITHUB_SHA::7}
|
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
|
|
|
# Build architecture-specific images in parallel (native runners)
|
|
build-release:
|
|
name: Build (${{ matrix.arch }})
|
|
needs: [test, version]
|
|
runs-on: ${{ matrix.runner }}
|
|
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: linux/amd64
|
|
arch: amd64
|
|
runner: blacksmith-4vcpu-ubuntu-2404
|
|
- platform: linux/arm64
|
|
arch: arm64
|
|
runner: blacksmith-4vcpu-ubuntu-2404-arm # Use Blacksmith ARM64 runners
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Blacksmith Builder
|
|
uses: useblacksmith/setup-docker-builder@v1
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Log in to Docker Hub
|
|
uses: docker/login-action@v3
|
|
with:
|
|
username: gmcintire
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
- name: Build and push
|
|
uses: useblacksmith/build-push-action@v2
|
|
with:
|
|
context: .
|
|
platforms: ${{ matrix.platform }}
|
|
push: true
|
|
provenance: false
|
|
build-args: |
|
|
VERSION=${{ needs.version.outputs.version }}
|
|
BUILDKIT_INLINE_CACHE=1
|
|
tags: |
|
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }}-${{ matrix.arch }}
|
|
docker.io/${{ env.DOCKERHUB_IMAGE }}:${{ needs.version.outputs.version }}-${{ matrix.arch }}
|
|
|
|
- name: Extract standalone binary
|
|
run: |
|
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }}-${{ matrix.arch }}"
|
|
CONTAINER=$(docker create $IMAGE)
|
|
docker cp $CONTAINER:/usr/local/bin/towerops-agent ./towerops-agent-linux-${{ matrix.arch }}
|
|
docker rm $CONTAINER
|
|
sha256sum towerops-agent-linux-${{ matrix.arch }} > towerops-agent-linux-${{ matrix.arch }}.sha256
|
|
|
|
- name: Upload binary artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: binary-${{ matrix.arch }}
|
|
path: |
|
|
towerops-agent-linux-${{ matrix.arch }}
|
|
towerops-agent-linux-${{ matrix.arch }}.sha256
|
|
|
|
# Create multi-arch manifest after both builds complete
|
|
manifest:
|
|
name: Create Manifest
|
|
needs: [version, build-release]
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Log in to Docker Hub
|
|
uses: docker/login-action@v3
|
|
with:
|
|
username: gmcintire
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
- name: Create and push manifests
|
|
run: |
|
|
VERSION="${{ needs.version.outputs.version }}"
|
|
SHORT_SHA="${{ needs.version.outputs.short_sha }}"
|
|
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
|
DH_IMAGE="docker.io/${{ env.DOCKERHUB_IMAGE }}"
|
|
|
|
# Create GHCR manifests
|
|
docker buildx imagetools create -t ${IMAGE}:${VERSION} \
|
|
${IMAGE}:${VERSION}-amd64 \
|
|
${IMAGE}:${VERSION}-arm64
|
|
|
|
docker buildx imagetools create -t ${IMAGE}:latest \
|
|
${IMAGE}:${VERSION}-amd64 \
|
|
${IMAGE}:${VERSION}-arm64
|
|
|
|
docker buildx imagetools create -t ${IMAGE}:sha-${SHORT_SHA} \
|
|
${IMAGE}:${VERSION}-amd64 \
|
|
${IMAGE}:${VERSION}-arm64
|
|
|
|
# Create Docker Hub manifests
|
|
docker buildx imagetools create -t ${DH_IMAGE}:${VERSION} \
|
|
${DH_IMAGE}:${VERSION}-amd64 \
|
|
${DH_IMAGE}:${VERSION}-arm64
|
|
|
|
docker buildx imagetools create -t ${DH_IMAGE}:latest \
|
|
${DH_IMAGE}:${VERSION}-amd64 \
|
|
${DH_IMAGE}:${VERSION}-arm64
|
|
|
|
docker buildx imagetools create -t ${DH_IMAGE}:sha-${SHORT_SHA} \
|
|
${DH_IMAGE}:${VERSION}-amd64 \
|
|
${DH_IMAGE}:${VERSION}-arm64
|
|
|
|
- name: Summary
|
|
run: |
|
|
echo "### Released version: ${{ needs.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "Multi-architecture build (amd64, arm64) - built in parallel" >> $GITHUB_STEP_SUMMARY
|
|
echo "Tags pushed to GHCR and Docker Hub:" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`latest\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`${{ needs.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`sha-${{ needs.version.outputs.short_sha }}\`" >> $GITHUB_STEP_SUMMARY
|
|
|
|
# Create GitHub Release with standalone binaries (only on tags)
|
|
release:
|
|
name: GitHub Release
|
|
needs: [version, manifest]
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Download amd64 binary
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: binary-amd64
|
|
|
|
- name: Download arm64 binary
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: binary-arm64
|
|
|
|
- name: Create GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
name: v${{ needs.version.outputs.version }}
|
|
generate_release_notes: true
|
|
files: |
|
|
towerops-agent-linux-amd64
|
|
towerops-agent-linux-amd64.sha256
|
|
towerops-agent-linux-arm64
|
|
towerops-agent-linux-arm64.sha256
|
|
|
|
- name: Notify agents of new release
|
|
if: success()
|
|
run: |
|
|
curl -sf -X POST \
|
|
-H "Authorization: Bearer ${{ secrets.AGENT_WEBHOOK_SECRET }}" \
|
|
https://towerops.net/api/v1/webhooks/agent-release
|
|
continue-on-error: true
|