Skip to content

fix[frontend](socai): added default template for empty previous socai… #512

fix[frontend](socai): added default template for empty previous socai…

fix[frontend](socai): added default template for empty previous socai… #512

name: "v11 - Build & Deploy Pipeline"
on:
push:
branches:
- 'release/v11**'
- 'v11'
release:
types: [ released ]
jobs:
setup_deployment:
name: Setup Deployment
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.set-env.outputs.tag }}
environment: ${{ steps.set-env.outputs.environment }}
cm_url: ${{ steps.set-env.outputs.cm_url }}
event_processor_tag: ${{ steps.set-env.outputs.event_processor_tag }}
steps:
- name: Determine Build Environment
id: set-env
run: |
# =====================================================================
# DEV — push to release/v11**
# Version = <branch-base>-dev.N, auto-incremented via CM.
# =====================================================================
if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release/v11') }}; then
ENVIRONMENT="dev"
CM_URL="https://cm.dev.utmstack.com"
echo "Environment: $ENVIRONMENT"
echo "CM URL: $CM_URL"
# Extract version from branch name (e.g. release/v11.2.1 → v11.2.1)
BRANCH_VERSION=$(echo "${{ github.ref }}" | sed 's|refs/heads/release/||')
echo "Branch version: $BRANCH_VERSION"
RESPONSE=$(curl -s "${CM_URL}/api/v1/versions/latest")
LATEST_VERSION=$(echo "$RESPONSE" | jq -r '.version // empty')
echo "Latest version from CM: $LATEST_VERSION"
if [ -n "$LATEST_VERSION" ]; then
LATEST_BASE=$(echo "$LATEST_VERSION" | sed 's/-dev\.[0-9]*$//')
if [ "$BRANCH_VERSION" = "$LATEST_BASE" ]; then
DEV_NUM=$(echo "$LATEST_VERSION" | grep -oP '(?<=-dev\.)\d+')
NEW_DEV_NUM=$((DEV_NUM + 1))
TAG="${BRANCH_VERSION}-dev.${NEW_DEV_NUM}"
echo "Versions match, incrementing: $TAG"
else
TAG="${BRANCH_VERSION}-dev.1"
echo "Versions don't match, starting fresh: $TAG"
fi
else
TAG="${BRANCH_VERSION}-dev.1"
echo "No previous version found, starting fresh: $TAG"
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT
echo "cm_url=$CM_URL" >> $GITHUB_OUTPUT
echo "event_processor_tag=${{ vars.TW_EVENT_PROCESSOR_VERSION_DEV }}" >> $GITHUB_OUTPUT
# =====================================================================
# RC — push to v11
#
# Tag derivation walks two CMs to handle both the normal flow and
# the hotfix flow without divergent code paths:
#
# 1. CM DEV gives us the candidate BASE (strip `-dev.N`).
# 2. CM PROD gives us the latest version already in production.
# 3. If BASE > PROD → use BASE. Normal flow.
# If BASE <= PROD → BASE is already released (hotfix scenario)
# so bump the patch of PROD instead. Avoids overwriting an
# already-shipped tag and matches the roll-forward policy.
# =====================================================================
elif ${{ github.event_name == 'push' && github.ref == 'refs/heads/v11' }}; then
ENVIRONMENT="rc"
CM_URL="https://cm.utmstack.com"
CM_DEV_URL="https://cm.dev.utmstack.com"
echo "Environment: $ENVIRONMENT"
echo "CM URL (target): $CM_URL"
echo "CM URL (source for base): $CM_DEV_URL"
DEV_RESPONSE=$(curl -s "${CM_DEV_URL}/api/v1/versions/latest")
LATEST_DEV_VERSION=$(echo "$DEV_RESPONSE" | jq -r '.version // empty')
echo "Latest dev version from CM DEV: $LATEST_DEV_VERSION"
if [ -z "$LATEST_DEV_VERSION" ]; then
echo "❌ No dev version found in CM DEV — cannot derive RC base. Push a release/v11.x.x branch first."
exit 1
fi
BASE=$(echo "$LATEST_DEV_VERSION" | sed -E 's/-dev\.[0-9]+$//')
echo "BASE derived from CM DEV: $BASE"
if [[ ! "$BASE" =~ ^v11\. ]]; then
echo "❌ Derived BASE '$BASE' is not a v11 release."
exit 1
fi
# Double-check against production to detect the hotfix scenario.
PROD_RESPONSE=$(curl -s "${CM_URL}/api/v1/versions/latest")
PROD_LATEST=$(echo "$PROD_RESPONSE" | jq -r '.version // empty')
echo "Latest production version from CM PROD: ${PROD_LATEST:-<none>}"
if [ -n "$PROD_LATEST" ]; then
# sort -V puts the higher semver last.
HIGHER=$(printf '%s\n%s\n' "$BASE" "$PROD_LATEST" | sort -V | tail -1)
if [ "$HIGHER" = "$BASE" ] && [ "$BASE" != "$PROD_LATEST" ]; then
# BASE is strictly newer than PROD — use it as-is.
TAG="$BASE"
echo "BASE ($BASE) > PROD ($PROD_LATEST) — using BASE as RC tag."
else
# PROD is >= BASE → BASE was already released (hotfix case).
# Bump the patch of PROD.
MAJOR_MINOR=$(echo "$PROD_LATEST" | sed -E 's/^(v[0-9]+\.[0-9]+)\.[0-9]+.*$/\1/')
PATCH=$(echo "$PROD_LATEST" | sed -E 's/^v[0-9]+\.[0-9]+\.([0-9]+).*$/\1/')
NEW_PATCH=$((PATCH + 1))
TAG="${MAJOR_MINOR}.${NEW_PATCH}"
echo "BASE ($BASE) <= PROD ($PROD_LATEST) — hotfix scenario. Bumping patch: $TAG"
fi
else
# CM PROD has no versions yet — use BASE.
TAG="$BASE"
echo "CM PROD is empty — using BASE as RC tag."
fi
echo "RC tag: $TAG"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT
echo "cm_url=$CM_URL" >> $GITHUB_OUTPUT
echo "event_processor_tag=${{ vars.TW_EVENT_PROCESSOR_VERSION_PROD }}" >> $GITHUB_OUTPUT
# =====================================================================
# PRODUCTION — release.released (non-prerelease publish)
#
# Production does NOT rebuild anything. The images, installer, and
# changelog are all artifacts of the RC run. The production trigger
# only needs to tell CM "this version is now available to community
# instances" via a promote endpoint (TODO: define and wire up).
# =====================================================================
elif ${{ github.event_name == 'release' && github.event.action == 'released' }}; then
ENVIRONMENT="production"
CM_URL="https://cm.utmstack.com"
echo "Environment: $ENVIRONMENT"
echo "CM URL: $CM_URL"
TAG="${{ github.event.release.tag_name }}"
echo "Tag from release: $TAG"
if [[ ! "$TAG" =~ ^v11\. ]]; then
echo "⏭️ Skipping: tag '$TAG' is not a v11 release."
exit 0
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT
echo "cm_url=$CM_URL" >> $GITHUB_OUTPUT
echo "event_processor_tag=${{ vars.TW_EVENT_PROCESSOR_VERSION_PROD }}" >> $GITHUB_OUTPUT
fi
build_agent:
name: Build Agent Binaries
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
runs-on: ubuntu-24.04
steps:
- name: Check out code into the right branch
uses: actions/checkout@v4
- name: Build Linux Binaries (amd64)
env:
GOOS: linux
GOARCH: amd64
CGO_ENABLED: 0
run: |
cd ${{ github.workspace }}/agent
go build -o utmstack_agent_service_linux_amd64 -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" .
cd ${{ github.workspace }}/agent/updater
go build -o utmstack_updater_service_linux_amd64 .
- name: Build Linux Binaries (arm64)
env:
GOOS: linux
GOARCH: arm64
CGO_ENABLED: 0
run: |
cd ${{ github.workspace }}/agent
go build -o utmstack_agent_service_linux_arm64 -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" .
cd ${{ github.workspace }}/agent/updater
go build -o utmstack_updater_service_linux_arm64 .
- name: Build Windows Binaries (amd64)
env:
GOOS: windows
GOARCH: amd64
CGO_ENABLED: 0
run: |
cd ${{ github.workspace }}/agent
go build -o utmstack_agent_service_windows_amd64.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" .
cd ${{ github.workspace }}/agent/updater
go build -o utmstack_updater_service_windows_amd64.exe .
- name: Build Windows Binaries (arm64)
env:
GOOS: windows
GOARCH: arm64
CGO_ENABLED: 0
run: |
cd ${{ github.workspace }}/agent
go build -o utmstack_agent_service_windows_arm64.exe -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" .
cd ${{ github.workspace }}/agent/updater
go build -o utmstack_updater_service_windows_arm64.exe .
- name: Build macOS Binaries (arm64)
env:
GOOS: darwin
GOARCH: arm64
CGO_ENABLED: 0
run: |
cd ${{ github.workspace }}/agent
go build -o utmstack_agent_service_darwin_arm64 -v -ldflags "-X 'github.com/utmstack/UTMStack/agent/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" .
cd ${{ github.workspace }}/agent/updater
go build -o utmstack_updater_service_darwin_arm64 .
- name: Upload Linux binaries as artifact
uses: actions/upload-artifact@v4
with:
name: agents-linux
path: |
agent/utmstack_agent_service_linux_amd64
agent/utmstack_agent_service_linux_arm64
agent/updater/utmstack_updater_service_linux_amd64
agent/updater/utmstack_updater_service_linux_arm64
retention-days: 1
- name: Upload unsigned Windows binaries as artifact
uses: actions/upload-artifact@v4
with:
name: agents-windows-unsigned
path: |
agent/utmstack_agent_service_windows_amd64.exe
agent/utmstack_agent_service_windows_arm64.exe
agent/updater/utmstack_updater_service_windows_amd64.exe
agent/updater/utmstack_updater_service_windows_arm64.exe
retention-days: 1
- name: Upload unsigned macOS binaries as artifact
uses: actions/upload-artifact@v4
with:
name: agents-darwin-unsigned
path: |
agent/utmstack_agent_service_darwin_arm64
agent/updater/utmstack_updater_service_darwin_arm64
retention-days: 1
sign_agent_windows:
name: Sign Windows Agent Binaries
needs: [build_agent, setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
uses: ./.github/workflows/reusable-sign-agent.yml
with:
os: windows
artifact_name: agents-windows-unsigned
signed_artifact_name: agents-windows-signed
gcp_project_id: ${{ vars.GCP_PROJECT_PROD }}
kms_location: ${{ vars.KMS_KEYRING_LOCATION }}
kms_keyring: ${{ vars.KMS_KEYRING_NAME }}
kms_key: ${{ vars.KMS_KEY_NAME }}
binaries: |
utmstack_agent_service_windows_amd64.exe
utmstack_agent_service_windows_arm64.exe
updater/utmstack_updater_service_windows_amd64.exe
updater/utmstack_updater_service_windows_arm64.exe
secrets: inherit
sign_agent_macos:
name: Sign macOS Agent Binaries
needs: [build_agent, setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
uses: ./.github/workflows/reusable-sign-agent.yml
with:
os: macos
artifact_name: agents-darwin-unsigned
signed_artifact_name: agents-darwin-signed
binaries: |
utmstack_agent_service_darwin_arm64
updater/utmstack_updater_service_darwin_arm64
secrets: inherit
build_utmstack_collector:
name: Build UTMStack Collector
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
runs-on: ubuntu-24.04
steps:
- name: Check out code into the right branch
uses: actions/checkout@v4
- name: Build UTMStack Collectors
run: |
echo "Building UTMStack Collector..."
cd ${{ github.workspace }}/utmstack-collector
GOOS=linux GOARCH=amd64 go build -o utmstack_collector -v -ldflags "-X 'github.com/utmstack/UTMStack/utmstack-collector/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" .
echo "Building UTMStack AS400 Collector..."
cd ${{ github.workspace }}/as400
GOOS=linux GOARCH=amd64 go build -o utmstack_as400_collector_service -v -ldflags "-X 'github.com/utmstack/UTMStack/as400/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" .
cd ${{ github.workspace }}/as400/updater
GOOS=linux GOARCH=amd64 go build -o utmstack_as400_updater_service -v .
- name: Upload collector binary as artifact
uses: actions/upload-artifact@v4
with:
name: utmstack-collectors
path: |
${{ github.workspace }}/utmstack-collector/utmstack_collector
${{ github.workspace }}/as400/utmstack_as400_collector_service
${{ github.workspace }}/as400/updater/utmstack_as400_updater_service
retention-days: 1
build_agent_manager:
name: Build Agent Manager Microservice
needs: [sign_agent_windows, sign_agent_macos, build_utmstack_collector, setup_deployment]
if: ${{ always() && needs.sign_agent_windows.result == 'success' && needs.sign_agent_macos.result == 'success' && needs.build_utmstack_collector.result == 'success' && needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
runs-on: ubuntu-24.04
steps:
- name: Check out code into the right branch
uses: actions/checkout@v4
- name: Download Linux agents from artifact
uses: actions/download-artifact@v4
with:
name: agents-linux
path: ${{ github.workspace }}/agent
- name: Download signed Windows agents from artifact
uses: actions/download-artifact@v4
with:
name: agents-windows-signed
path: ${{ github.workspace }}/agent
- name: Download UTMStack Collectors from artifacts
uses: actions/download-artifact@v4
with:
name: utmstack-collectors
path: ${{ github.workspace }}/utmstack-collector
- name: Download signed macOS agents from artifact
uses: actions/download-artifact@v4
with:
name: agents-darwin-signed
path: ${{ github.workspace }}/agent-darwin
- name: Prepare dependencies for Agent Manager Image
run: |
cd ${{ github.workspace }}/agent-manager
GOOS=linux GOARCH=amd64 go build -o agent-manager -v .
mkdir -p ./dependencies/collector
cp "${{ github.workspace }}/utmstack-collector/utmstack-collector/utmstack_collector" ./dependencies/collector/
cp "${{ github.workspace }}/utmstack-collector/version.json" ./dependencies/collector/
mkdir -p ./dependencies/collector/as400
curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/as400-collector.jar" -o ./dependencies/collector/as400/as400-collector.jar
cp "${{ github.workspace }}/as400/version.json" ./dependencies/collector/as400/
cp "${{ github.workspace }}/utmstack-collector/as400/utmstack_as400_collector_service" ./dependencies/collector/as400/
cp "${{ github.workspace }}/utmstack-collector/as400/updater/utmstack_as400_updater_service" ./dependencies/collector/as400/
mkdir -p ./dependencies/agent/
# Linux agents
cp "${{ github.workspace }}/agent/utmstack_agent_service_linux_amd64" ./dependencies/agent/
cp "${{ github.workspace }}/agent/utmstack_agent_service_linux_arm64" ./dependencies/agent/
cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_amd64" ./dependencies/agent/
cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_arm64" ./dependencies/agent/
# Windows agents
cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_amd64.exe" ./dependencies/agent/
cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_arm64.exe" ./dependencies/agent/
cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_amd64.exe" ./dependencies/agent/
cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_arm64.exe" ./dependencies/agent/
# macOS agents (signed and notarized)
cp "${{ github.workspace }}/agent-darwin/utmstack_agent_service_darwin_arm64" ./dependencies/agent/
cp "${{ github.workspace }}/agent-darwin/updater/utmstack_updater_service_darwin_arm64" ./dependencies/agent/
curl -sSL "https://storage.googleapis.com/utmstack-updates/agent_updates/release/macos-agent/latest/utmstack-collector-mac" -o ./dependencies/agent/utmstack-collector-mac
# TODO: Remove legacy binary names after all agents have migrated to new naming convention
# Legacy names for backwards compatibility with existing agents
cp "${{ github.workspace }}/agent/utmstack_agent_service_linux_amd64" ./dependencies/agent/utmstack_agent_service
cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_linux_amd64" ./dependencies/agent/utmstack_updater_service
cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_amd64.exe" ./dependencies/agent/utmstack_agent_service.exe
cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_amd64.exe" ./dependencies/agent/utmstack_updater_service.exe
cp "${{ github.workspace }}/agent/utmstack_agent_service_windows_arm64.exe" ./dependencies/agent/utmstack_agent_service_arm64.exe
cp "${{ github.workspace }}/agent/updater/utmstack_updater_service_windows_arm64.exe" ./dependencies/agent/utmstack_updater_service_arm64.exe
cp "${{ github.workspace }}/agent/version.json" ./dependencies/agent/
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: utmstack
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push the Agent Manager Image
uses: docker/build-push-action@v6
with:
context: ./agent-manager
push: true
provenance: false
tags: ghcr.io/utmstack/utmstack/agent-manager:${{ needs.setup_deployment.outputs.tag }}
build_event_processor:
name: Build Event Processor Microservice
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
runs-on: ubuntu-24.04
steps:
- name: Check out code into the right branch
uses: actions/checkout@v4
- name: Build Plugins
env:
GOOS: linux
GOARCH: amd64
run: |
cd ${{ github.workspace }}/plugins/alerts; go build -o com.utmstack.alerts.plugin -v .
cd ${{ github.workspace }}/plugins/aws; go build -o com.utmstack.aws.plugin -v .
cd ${{ github.workspace }}/plugins/azure; go build -o com.utmstack.azure.plugin -v .
cd ${{ github.workspace }}/plugins/bitdefender; go build -o com.utmstack.bitdefender.plugin -v .
cd ${{ github.workspace }}/plugins/config; go build -o com.utmstack.config.plugin -v .
cd ${{ github.workspace }}/plugins/events; go build -o com.utmstack.events.plugin -v .
cd ${{ github.workspace }}/plugins/gcp; go build -o com.utmstack.gcp.plugin -v .
cd ${{ github.workspace }}/plugins/geolocation; go build -o com.utmstack.geolocation.plugin -v .
cd ${{ github.workspace }}/plugins/inputs; go build -o com.utmstack.inputs.plugin -v .
cd ${{ github.workspace }}/plugins/o365; go build -o com.utmstack.o365.plugin -v .
cd ${{ github.workspace }}/plugins/sophos; go build -o com.utmstack.sophos.plugin -v .
cd ${{ github.workspace }}/plugins/stats; go build -o com.utmstack.stats.plugin -v .
cd ${{ github.workspace }}/plugins/soc-ai; go build -o com.utmstack.soc-ai.plugin -v .
cd ${{ github.workspace }}/plugins/modules-config; go build -o com.utmstack.modules-config.plugin -v .
cd ${{ github.workspace }}/plugins/crowdstrike; go build -o com.utmstack.crowdstrike.plugin -v .
cd ${{ github.workspace }}/plugins/feeds; go build -o com.utmstack.feeds.plugin -v .
- name: Prepare Dependencies for Event Processor Image
run: |
mkdir -p ./geolocation
curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/geolocation/asn-blocks-v4.csv" -o ./geolocation/asn-blocks-v4.csv
curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/geolocation/asn-blocks-v6.csv" -o ./geolocation/asn-blocks-v6.csv
curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/geolocation/blocks-v4.csv" -o ./geolocation/blocks-v4.csv
curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/geolocation/blocks-v6.csv" -o ./geolocation/blocks-v6.csv
curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/geolocation/locations-en.csv" -o ./geolocation/locations-en.csv
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: utmstack
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push the Event Processor Image
uses: docker/build-push-action@v6
with:
context: .
file: ./event_processor.Dockerfile
push: true
provenance: false
tags: ghcr.io/utmstack/utmstack/eventprocessor:${{ needs.setup_deployment.outputs.tag }}
build-args: |
BASE_IMAGE=ghcr.io/threatwinds/eventprocessor/base:${{ needs.setup_deployment.outputs.event_processor_tag }}
build_backend:
name: Build Backend Microservice
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
uses: ./.github/workflows/reusable-java.yml
with:
image_name: backend
tag: ${{ needs.setup_deployment.outputs.tag }}
java_version: '17'
use_tag_as_version: true
maven_profile: 'prod'
maven_goals: 'clean package'
copy_filters_and_rules: true
build_frontend:
name: Build Frontend Microservice
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
uses: ./.github/workflows/reusable-node.yml
with:
image_name: frontend
tag: ${{ needs.setup_deployment.outputs.tag }}
build_user_auditor:
name: Build User-Auditor Microservice
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
uses: ./.github/workflows/reusable-java.yml
with:
image_name: user-auditor
tag: ${{ needs.setup_deployment.outputs.tag }}
java_version: '11'
use_version_file: false
maven_goals: 'clean install -U'
build_web_pdf:
name: Build Web-PDF Microservice
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
uses: ./.github/workflows/reusable-java.yml
with:
image_name: web-pdf
tag: ${{ needs.setup_deployment.outputs.tag }}
java_version: '11'
use_version_file: false
maven_goals: 'clean install -U'
all_builds_complete:
name: All Builds Complete
needs: [
build_agent_manager,
build_event_processor,
build_backend,
build_frontend,
build_user_auditor,
build_web_pdf
]
if: ${{ always() && needs.build_agent_manager.result == 'success' && needs.build_event_processor.result == 'success' && needs.build_backend.result == 'success' && needs.build_frontend.result == 'success' && needs.build_user_auditor.result == 'success' && needs.build_web_pdf.result == 'success' }}
runs-on: ubuntu-24.04
steps:
- run: echo "✅ All builds completed successfully."
# AI changelog runs only on RC. Production reuses the same release notes
# because the GitHub Release was already created during RC.
generate_changelog:
name: Generate Changelog
needs: [all_builds_complete, setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment == 'rc' }}
uses: ./.github/workflows/generate-changelog.yml
with:
current_tag: ${{ needs.setup_deployment.outputs.tag }}
secrets:
THREATWINDS_API_KEY: ${{ secrets.THREATWINDS_API_KEY }}
THREATWINDS_API_SECRET: ${{ secrets.THREATWINDS_API_SECRET }}
# Installer build runs only on RC. The resulting binary is uploaded to the
# GitHub Release as a prerelease asset. When the release is later promoted
# to non-prerelease (which triggers production), the installer is already
# there — no rebuild needed.
build_installer_release:
name: Build & Upload Installer
needs: [generate_changelog, setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment == 'rc' }}
uses: ./.github/workflows/installer-release.yml
with:
version: ${{ needs.setup_deployment.outputs.tag }}
version_major: v11
environment: rc
prerelease: true
changelog: ${{ needs.generate_changelog.outputs.changelog }}
secrets:
API_SECRET: ${{ secrets.API_SECRET }}
CM_ENCRYPT_SALT: ${{ secrets.CM_ENCRYPT_SALT }}
CM_SIGN_PUBLIC_KEY: ${{ secrets.CM_SIGN_PUBLIC_KEY }}
deploy_installer_dev:
name: Deploy Installer (Dev)
needs: [all_builds_complete, setup_deployment]
if: ${{ always() && needs.all_builds_complete.result == 'success' && needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment == 'dev' }}
uses: ./.github/workflows/installer-release.yml
with:
version: ${{ needs.setup_deployment.outputs.tag }}
version_major: v11
environment: dev
secrets:
API_SECRET: ${{ secrets.API_SECRET }}
CM_ENCRYPT_SALT: ${{ secrets.CM_ENCRYPT_SALT }}
CM_SIGN_PUBLIC_KEY: ${{ secrets.CM_SIGN_PUBLIC_KEY }}
# Publish a new version to CM. Runs for dev and rc — production does NOT
# re-publish because the rc run already registered v11.x.x in CM PROD.
publish_new_version:
name: Publish New Version to Customer Manager
needs: [all_builds_complete, generate_changelog, setup_deployment]
if: ${{ always() && needs.all_builds_complete.result == 'success' && needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
runs-on: ubuntu-24.04
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish version
env:
CHANGELOG_CONTENT: ${{ needs.generate_changelog.outputs.changelog }}
ENVIRONMENT: ${{ needs.setup_deployment.outputs.environment }}
TAG: ${{ needs.setup_deployment.outputs.tag }}
CM_URL: ${{ needs.setup_deployment.outputs.cm_url }}
run: |
# Use AI changelog for rc / production, generic for dev.
if [ "$ENVIRONMENT" != "dev" ] && [ -n "$CHANGELOG_CONTENT" ]; then
changelog="$CHANGELOG_CONTENT"
else
changelog="Development build $TAG - Internal testing release"
fi
echo "Environment: $ENVIRONMENT"
echo "CM URL: $CM_URL"
echo "Tag: $TAG"
# Select CM_SERVICE_ACCOUNT based on environment
if [ "$ENVIRONMENT" = "dev" ]; then
cmAuth=$(echo '${{ secrets.CM_SERVICE_ACCOUNT_DEV }}' | jq -r '.')
else
cmAuth=$(echo '${{ secrets.CM_SERVICE_ACCOUNT_PROD }}' | jq -r '.')
fi
id=$(echo "$cmAuth" | jq -r '.id')
key=$(echo "$cmAuth" | jq -r '.key')
body=$(jq -n \
--arg version "$TAG" \
--arg changelog "$changelog" \
'{version: $version, changelog: $changelog}'
)
response=$(curl -s -X POST "${CM_URL}/api/v1/versions/register" \
-H "Content-Type: application/json" \
-H "id: $id" \
-H "key: $key" \
-d "$body")
echo "Response: $response"
# Schedule the freshly-published version. For dev, this targets the dev
# instance list; for rc, the RC instance list. Production does NOT use this
# job — promotion to community lives in `promote_to_community` below.
schedule:
name: Schedule release to our instances
needs: [publish_new_version, setup_deployment]
if: ${{ always() && needs.publish_new_version.result == 'success' && needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment != 'production' }}
runs-on: ubuntu-24.04
env:
ENVIRONMENT: ${{ needs.setup_deployment.outputs.environment }}
TAG: ${{ needs.setup_deployment.outputs.tag }}
CM_URL: ${{ needs.setup_deployment.outputs.cm_url }}
steps:
- name: Schedule updates
run: |
echo "🔍 Environment: $ENVIRONMENT"
echo "🔍 Version: $TAG"
echo "🔍 CM URL: $CM_URL"
# Select instance IDs and auth based on environment
if [ "$ENVIRONMENT" = "dev" ]; then
instance_ids="${{ vars.SCHEDULE_INSTANCES_DEV }}"
auth_json='${{ secrets.CM_SERVICE_ACCOUNT_DEV }}'
else
# rc uses prod variables
instance_ids="${{ vars.SCHEDULE_INSTANCES_PROD }}"
auth_json='${{ secrets.CM_SERVICE_ACCOUNT_PROD }}'
fi
# Extract id and key from auth JSON
auth_id=$(echo "$auth_json" | jq -r '.id')
auth_key=$(echo "$auth_json" | jq -r '.key')
# Parse IDs (handle single ID or comma-separated IDs)
IFS=',' read -ra ID_ARRAY <<< "$instance_ids"
# Iterate over each instance ID
for instance_id in "${ID_ARRAY[@]}"; do
instance_id=$(echo "$instance_id" | xargs)
echo "📅 Scheduling release for instance: $instance_id"
response=$(curl -s -w "\n%{http_code}" -X POST "${CM_URL}/api/v1/updates" \
-H "Content-Type: application/json" \
-H "id: $auth_id" \
-H "key: $auth_key" \
-d "{\"instances_ids\": [\"$instance_id\"], \"version\": \"$TAG\"}")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "✅ Successfully scheduled for instance: $instance_id"
else
echo "❌ Failed to schedule for instance: $instance_id (HTTP $http_code)"
echo "Response: $body"
exit 1
fi
done
echo "✅ Scheduled release for all instances with version $TAG"
promote_to_community:
name: Promote to Community
needs: [setup_deployment]
if: ${{ needs.setup_deployment.outputs.tag != '' && needs.setup_deployment.outputs.environment == 'production' }}
runs-on: ubuntu-24.04
env:
TAG: ${{ needs.setup_deployment.outputs.tag }}
CM_URL: ${{ needs.setup_deployment.outputs.cm_url }}
steps:
- name: Schedule update for all community instances
run: |
echo "🚀 Promoting $TAG to community"
echo " CM URL: $CM_URL"
cmAuth=$(echo '${{ secrets.CM_SERVICE_ACCOUNT_PROD }}' | jq -r '.')
auth_id=$(echo "$cmAuth" | jq -r '.id')
auth_key=$(echo "$cmAuth" | jq -r '.key')
body=$(jq -n \
--arg version "$TAG" \
'{version: $version, edition: "community"}')
response=$(curl -sS -w "\n%{http_code}" -X POST "${CM_URL}/api/v1/updates" \
-H "Content-Type: application/json" \
-H "id: $auth_id" \
-H "key: $auth_key" \
-d "$body")
http_code=$(echo "$response" | tail -n1)
payload=$(echo "$response" | sed '$d')
echo "HTTP $http_code"
echo "Response: $payload"
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "✅ Community broadcast scheduled for $TAG"
else
echo "❌ Community broadcast failed (HTTP $http_code)"
exit 1
fi