Skip to content

Building self-hosted: api, worker, dashboard, webhook, ws #49

Building self-hosted: api, worker, dashboard, webhook, ws

Building self-hosted: api, worker, dashboard, webhook, ws #49

name: Prepare Self-hosted Release
run-name: >
Building self-hosted${{ github.event.inputs.nightly == 'true' && ' (nightly)' || '' }}:
${{
github.event.inputs.build_api == 'true' && 'api, ' || ''
}}${{
github.event.inputs.build_worker == 'true' && 'worker, ' || ''
}}${{
github.event.inputs.build_dashboard == 'true' && 'dashboard, ' || ''
}}${{
github.event.inputs.build_webhook == 'true' && 'webhook, ' || ''
}}${{
github.event.inputs.build_ws == 'true' && 'ws' || ''
}}
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
inputs:
nightly:
description: "Build as nightly release"
required: false
default: "false"
type: choice
options:
- "true"
- "false"
build_api:
description: "Build API service"
required: false
type: boolean
default: true
build_worker:
description: "Build Worker service"
required: false
type: boolean
default: true
build_dashboard:
description: "Build Dashboard service"
required: false
type: boolean
default: true
build_webhook:
description: "Build Webhook service"
required: false
type: boolean
default: true
build_ws:
description: "Build WebSocket service"
required: false
type: boolean
default: true
permissions:
contents: write
packages: write
deployments: write
id-token: write
jobs:
setup_matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Validate Selected Services
if: github.event_name == 'workflow_dispatch'
run: |
if [ "${{ github.event.inputs.build_api }}" != "true" ] && \
[ "${{ github.event.inputs.build_worker }}" != "true" ] && \
[ "${{ github.event.inputs.build_dashboard }}" != "true" ] && \
[ "${{ github.event.inputs.build_webhook }}" != "true" ] && \
[ "${{ github.event.inputs.build_ws }}" != "true" ]; then
echo "Error: At least one service must be selected for building."
exit 1
fi
- name: Generate Build Matrix
id: set-matrix
run: |
# If triggered by tag push, build all services
if [ "${{ github.event_name }}" == "push" ]; then
matrix='["novu/api","novu/worker","novu/dashboard","novu/webhook","novu/ws"]'
else
# If triggered by workflow_dispatch, build only selected services
services=()
if [ "${{ github.event.inputs.build_api }}" == "true" ]; then
services+=("\"novu/api\"")
fi
if [ "${{ github.event.inputs.build_worker }}" == "true" ]; then
services+=("\"novu/worker\"")
fi
if [ "${{ github.event.inputs.build_dashboard }}" == "true" ]; then
services+=("\"novu/dashboard\"")
fi
if [ "${{ github.event.inputs.build_webhook }}" == "true" ]; then
services+=("\"novu/webhook\"")
fi
if [ "${{ github.event.inputs.build_ws }}" == "true" ]; then
services+=("\"novu/ws\"")
fi
matrix="[$(IFS=','; echo "${services[*]}")]"
fi
echo "matrix=$matrix" >> $GITHUB_OUTPUT
echo "Building services: $matrix"
build_docker:
needs: setup_matrix
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
name: ${{ fromJson(needs.setup_matrix.outputs.matrix) }}
steps:
- name: Git Checkout
uses: actions/checkout@v5
- name: Variables
shell: bash
run: |
service=${{ matrix.name }}
SERVICE_NAME=$(basename "${service//-/-}")
SERVICE_COMMON_NAME=$(echo "$SERVICE_NAME" | sed 's/-ee$//')
# Determine version based on nightly flag
IS_NIGHTLY=${{ github.event.inputs.nightly == 'true' }}
if [ "$IS_NIGHTLY" == "true" ]; then
DATE=$(date +'%Y%m%d')
SHORT_SHA=$(git rev-parse --short HEAD)
LATEST_VERSION="nightly-${DATE}-${SHORT_SHA}"
echo "IS_NIGHTLY=true" >> $GITHUB_ENV
else
LATEST_VERSION=$(jq -r '.version' apps/api/package.json)
echo "IS_NIGHTLY=false" >> $GITHUB_ENV
fi
echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_ENV
echo "SERVICE_NAME=$SERVICE_NAME" >> $GITHUB_ENV
echo "SERVICE_COMMON_NAME=$SERVICE_COMMON_NAME" >> $GITHUB_ENV
echo "REGISTRY_OWNER=novuhq" >> $GITHUB_ENV
echo "This is the service name: $SERVICE_NAME and release version: $LATEST_VERSION"
- name: Install pnpm
uses: pnpm/action-setup@v3
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20.19.0
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup Docker
uses: crazy-max/ghaction-setup-docker@v2
with:
version: v24.0.6
daemon-config: |
{
"features": {
"containerd-snapshotter": true
}
}
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64,linux/arm64
- name: Set Up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: "image=moby/buildkit:v0.13.1"
- uses: ./.github/actions/free-space
name: Extend space in Action Container
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build ${{ env.SERVICE_NAME }} Community Docker Image
shell: bash
env:
DOCKER_BUILD_ARGUMENTS: >
--cache-from type=registry,ref=ghcr.io/${{ env.REGISTRY_OWNER }}/cache:build-cache-${{ env.SERVICE_NAME }}-community
--cache-to type=registry,ref=ghcr.io/${{ env.REGISTRY_OWNER }}/cache:build-cache-${{ env.SERVICE_NAME }}-community,mode=max
--platform=linux/amd64,linux/arm64 --provenance=false
--output=type=image,name=ghcr.io/${{ env.REGISTRY_OWNER }}/${{ env.SERVICE_NAME }},push-by-digest=true,name-canonical=true
run: |
cp scripts/dotenvcreate.mjs apps/$SERVICE_COMMON_NAME/src/dotenvcreate.mjs
cd apps/$SERVICE_COMMON_NAME
if [ "${{ env.SERVICE_NAME }}" == "worker" ]; then
cd src/ && echo -e "\nIS_SELF_HOSTED=true\nOS_TELEMETRY_URL=\"${{ secrets.OS_TELEMETRY_URL }}\"" >> .example.env && cd ..
elif [ "${{ env.SERVICE_NAME }}" == "dashboard" ]; then
echo -e "\nVITE_SELF_HOSTED=true" >> .env
fi
# Switch from PM2 cluster mode to single node process for open source builds
if [[ "${{ env.SERVICE_NAME }}" =~ ^(api|worker|webhook|ws)$ ]]; then
echo "Switching ${{ env.SERVICE_NAME }} from PM2 cluster mode to single node process for open source"
sed -i.bak 's/pm2-runtime start dist\/main\.js -i max/node dist\/main.js/g' Dockerfile && rm -f Dockerfile.bak
fi
pnpm run docker:build
docker images
- name: Check for EE files
id: check-ee-files
run: |
patterns=(
'./node_modules/@novu/ee-**/dist/index.js'
'./node_modules/@taskforcesh/bullmq-pro' # Add more patterns as needed
)
for pattern in "${patterns[@]}"; do
if docker run --rm entrypoint sh novu-$SERVICE_COMMON_NAME -c "ls $pattern 2>/dev/null"; then
echo "::error::'$pattern' files were detected in ${{ matrix.name }}."
exit 1
fi
done
echo "No matching EE files found in the Docker image ${{ matrix.name }}"
- name: Tag and Push docker image
shell: bash
run: |
docker tag novu-$SERVICE_COMMON_NAME ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:${{ env.LATEST_VERSION }}
docker push ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:${{ env.LATEST_VERSION }}
if [ "${{ env.IS_NIGHTLY }}" == "true" ]; then
docker tag novu-$SERVICE_COMMON_NAME ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:nightly
docker push ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:nightly
else
docker tag novu-$SERVICE_COMMON_NAME ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:latest
docker push ghcr.io/$REGISTRY_OWNER/${{ matrix.name }}:latest
fi