fetch_ml/.forgejo/workflows/build.yml
Jeremie Fraeys 685f79c4a7
ci(deploy): add Forgejo workflows and deployment automation
Add CI/CD pipelines for Forgejo/GitHub Actions:
- build.yml - Main build pipeline with matrix builds
- deploy-staging.yml - Automated staging deployment
- deploy-prod.yml - Production deployment with rollback support
- security-modes-test.yml - Security mode validation tests

Add deployment artifacts:
- docker-compose.staging.yml for staging environment
- ROLLBACK.md with rollback procedures and playbooks

Supports multi-environment deployment workflow with proper
gates between staging and production.
2026-02-26 12:04:23 -05:00

345 lines
11 KiB
YAML

name: Build Pipeline
on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- 'docs/**'
- 'README.md'
- 'CHANGELOG.md'
- '.forgejo/ISSUE_TEMPLATE/**'
- '**/*.md'
concurrency:
group: build-${{ gitea.workflow }}-${{ gitea.ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
packages: write
env:
GO_VERSION: '1.25.0'
ZIG_VERSION: '0.15.2'
RSYNC_VERSION: '3.3.0'
REGISTRY: ghcr.io
IMAGE_NAME: fetchml-worker
jobs:
build-binaries:
name: Build Binaries
runs-on: self-hosted
timeout-minutes: 30
strategy:
matrix:
build_config:
- name: "native"
tags: "native_libs"
cgo_enabled: "1"
build_native: "true"
- name: "cgo-only"
tags: ""
cgo_enabled: "1"
build_native: "false"
- name: "no-cgo"
tags: ""
cgo_enabled: "0"
build_native: "false"
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Set up Go
run: |
REQUIRED_GO="1.25.0"
if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then
echo "Go ${REQUIRED_GO} already installed - skipping download"
else
echo "Installing Go ${REQUIRED_GO}..."
curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf -
export PATH="/usr/local/go/bin:$PATH"
echo "/usr/local/go/bin" >> $GITHUB_PATH
echo "Go ${REQUIRED_GO} installed"
fi
go version
- name: Set up Zig
run: |
ZIG_VERSION="${{ env.ZIG_VERSION }}"
if command -v zig &> /dev/null && zig version | grep -q "${ZIG_VERSION}"; then
echo "Zig ${ZIG_VERSION} already installed - skipping download"
else
echo "Installing Zig ${ZIG_VERSION}..."
ZIG_DIR="/usr/local/zig-${ZIG_VERSION}"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz
sudo mkdir -p "${ZIG_DIR}"
sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz
sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig
elif [[ "$OSTYPE" == "darwin"* ]]; then
curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-macos-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz
sudo mkdir -p "${ZIG_DIR}"
sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz
sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig
fi
rm -f /tmp/zig.tar.xz
echo "Zig ${ZIG_VERSION} installed"
fi
zig version
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y podman build-essential autoconf automake libtool pkg-config musl-tools cmake zlib1g-dev
- name: Build pinned rsync from official source
run: |
make -C cli build-rsync RSYNC_VERSION=${{ env.RSYNC_VERSION }}
- name: Build SQLite for CLI
run: |
make -C cli build-sqlite
- name: Build CLI binary
run: |
cd cli && make tiny
- name: Build Native Libraries
if: matrix.build_config.build_native == 'true'
run: |
echo "Building native C++ libraries..."
make native-build 2>&1 || {
echo "Native build failed!"
exit 1
}
echo "Native libraries built successfully"
- name: Build Go binaries (${{ matrix.build_config.name }})
run: |
echo "Building Go binaries with CGO_ENABLED=${{ matrix.build_config.cgo_enabled }}, tags=${{ matrix.build_config.tags }}"
CGO_ENABLED=${{ matrix.build_config.cgo_enabled }} make build
# Tag the binaries with the build config name
mkdir -p "bin/${{ matrix.build_config.name }}"
cp bin/* "bin/${{ matrix.build_config.name }}/" 2>/dev/null || true
- name: Test binaries
run: |
./bin/worker --help || true
./cli/zig-out/bin/ml --help || true
ls -lh ./cli/zig-out/bin/ml
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: fetch_ml_binaries_${{ matrix.build_config.name }}
path: |
bin/
cli/zig-out/
retention-days: 30
build-docker:
name: Build Docker Images
runs-on: self-hosted
needs: build-binaries
timeout-minutes: 45
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: fetch_ml_binaries_native
path: bin/
- name: Set up Docker
run: |
# Check Docker is available
docker --version || {
echo "Docker not available, using Podman"
sudo apt-get install -y podman
}
- name: Build Docker image
run: |
# Build the Docker image
docker build -f build/docker/simple.Dockerfile -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} .
- name: Generate image digest
run: |
docker inspect --format='{{index .RepoDigests 0}}' ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} > image-digest.txt
cat image-digest.txt
- name: Tag images
run: |
# Tag with commit SHA
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
# If this is a version tag, tag with version
if [[ "${{ gitea.ref }}" == refs/tags/v* ]]; then
VERSION=$(echo "${{ gitea.ref }}" | sed 's/refs\/tags\///')
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}
fi
- name: Container image scan (trivy)
run: |
# Scan the built image for vulnerabilities
trivy image --exit-code 1 --severity CRITICAL ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }} || {
echo "CRITICAL vulnerabilities found in container image"
exit 1
}
- name: Save image digest artifact
uses: actions/upload-artifact@v4
with:
name: image-digest
path: image-digest.txt
retention-days: 30
# Note: In Forgejo, you may need to configure a local registry or use external push
# This section is a placeholder for registry push
- name: Push to registry (optional)
run: |
echo "Image built: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }}"
echo "Note: Registry push requires proper authentication setup in Forgejo"
# docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ gitea.sha }}
# docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
sign-hipaa-config:
name: Sign HIPAA Config
runs-on: self-hosted
needs: build-binaries
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install cosign (if available)
run: |
# Try to install cosign for signing
if command -v cosign &> /dev/null; then
echo "cosign already installed"
else
echo "Installing cosign..."
curl -sSfL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 | sudo tee /usr/local/bin/cosign > /dev/null
sudo chmod +x /usr/local/bin/cosign || {
echo "cosign installation failed - signing will be skipped"
}
fi
cosign version || echo "cosign not available"
- name: Sign HIPAA config (placeholder)
run: |
echo "HIPAA config signing placeholder"
echo "To enable signing, configure COSIGN_KEY secret"
# Check if signing key is available
if [ -n "${{ secrets.COSIGN_KEY }}" ]; then
echo "Signing HIPAA config..."
# cosign sign-blob \
# --key ${{ secrets.COSIGN_KEY }} \
# deployments/configs/worker/docker-hipaa.yaml \
# > deployments/configs/worker/docker-hipaa.yaml.sig
echo "Signing would happen here with real cosign key"
else
echo "COSIGN_KEY not set - skipping HIPAA config signing"
# Create a placeholder signature file for now
echo "UNSIGNED_PLACEHOLDER" > deployments/configs/worker/docker-hipaa.yaml.sig
fi
- name: Upload HIPAA config signature
uses: actions/upload-artifact@v4
with:
name: hipaa-config-signature
path: deployments/configs/worker/docker-hipaa.yaml.sig
retention-days: 30
provenance:
name: Generate SLSA Provenance
runs-on: self-hosted
needs: [build-binaries, build-docker]
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Generate provenance
run: |
echo "Generating SLSA provenance..."
# Create a basic SLSA provenance file
cat > provenance.json << 'EOF'
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.2",
"subject": [
{
"name": "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}",
"digest": {
"sha256": "$(cat artifacts/image-digest/image-digest.txt | cut -d':' -f2 || echo 'unknown')"
}
}
],
"predicate": {
"builder": {
"id": "https://forgejo.example.com/jfraeysd/fetch_ml/.forgejo/workflows/build.yml"
},
"buildType": "https://forgejo.example.com/buildType/docker",
"invocation": {
"configSource": {
"uri": "https://forgejo.example.com/jfraeysd/fetch_ml",
"digest": {
"sha1": "${{ gitea.sha }}"
},
"entryPoint": ".forgejo/workflows/build.yml"
},
"parameters": {},
"environment": {
"gitea_actor": "${{ gitea.actor }}",
"gitea_ref": "${{ gitea.ref }}"
}
},
"metadata": {
"buildInvocationId": "${{ gitea.run_id }}",
"buildStartedOn": "$(date -Iseconds)",
"completeness": {
"parameters": false,
"environment": false,
"materials": false
}
},
"materials": [
{
"uri": "https://forgejo.example.com/jfraeysd/fetch_ml",
"digest": {
"sha1": "${{ gitea.sha }}"
}
}
]
}
}
EOF
cat provenance.json
- name: Upload provenance
uses: actions/upload-artifact@v4
with:
name: slsa-provenance
path: provenance.json
retention-days: 30