Skip to content

feat(analytics): add Google Analytics gtag #53

feat(analytics): add Google Analytics gtag

feat(analytics): add Google Analytics gtag #53

Workflow file for this run

name: Build & Deploy to Cloud
on:
push:
branches: [main]
paths:
- 'apps/**'
- 'infrastructure/helm/**'
- '.github/workflows/deploy.yml'
workflow_dispatch: # Manual trigger (builds all)
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository_owner }}/taskflow
jobs:
# =============================================================================
# Detect which services changed
# =============================================================================
changes:
runs-on: ubuntu-latest
outputs:
api: ${{ steps.filter.outputs.api }}
sso: ${{ steps.filter.outputs.sso }}
mcp: ${{ steps.filter.outputs.mcp }}
notification: ${{ steps.filter.outputs.notification }}
web: ${{ steps.filter.outputs.web }}
helm: ${{ steps.filter.outputs.helm }}
any_service: ${{ steps.filter.outputs.api == 'true' || steps.filter.outputs.sso == 'true' || steps.filter.outputs.mcp == 'true' || steps.filter.outputs.notification == 'true' || steps.filter.outputs.web == 'true' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect changes
uses: dorny/paths-filter@v3
id: filter
with:
filters: |
api:
- 'apps/api/**'
sso:
- 'apps/sso/**'
mcp:
- 'apps/mcp-server/**'
notification:
- 'apps/notification-service/**'
web:
- 'apps/web/**'
helm:
- 'infrastructure/helm/**'
# =============================================================================
# Build Docker images (only changed services)
# =============================================================================
build-api:
needs: changes
if: needs.changes.outputs.api == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/api
tags: |
type=raw,value=${{ github.sha }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: apps/api
file: apps/api/Dockerfile
push: true
platforms: linux/arm64
no-cache: true
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-sso:
needs: changes
if: needs.changes.outputs.sso == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/sso
tags: |
type=raw,value=${{ github.sha }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: apps/sso
file: apps/sso/Dockerfile
push: true
platforms: linux/arm64
no-cache: true
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
NEXT_PUBLIC_BETTER_AUTH_URL=https://sso.${{ vars.DOMAIN }}
NEXT_PUBLIC_CONTINUE_URL=https://${{ vars.DOMAIN }}
NEXT_PUBLIC_APP_NAME=Taskflow SSO
build-mcp:
needs: changes
if: needs.changes.outputs.mcp == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/mcp
tags: |
type=raw,value=${{ github.sha }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: apps/mcp-server
file: apps/mcp-server/Dockerfile
push: true
platforms: linux/arm64
no-cache: true
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-notification:
needs: changes
if: needs.changes.outputs.notification == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/notification
tags: |
type=raw,value=${{ github.sha }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: apps/notification-service
file: apps/notification-service/Dockerfile
push: true
platforms: linux/arm64
no-cache: true
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-web:
needs: changes
if: needs.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/web
tags: |
type=raw,value=${{ github.sha }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: apps/web
file: apps/web/Dockerfile
push: true
platforms: linux/arm64
no-cache: true
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
NEXT_PUBLIC_SSO_URL=https://sso.${{ vars.DOMAIN }}
NEXT_PUBLIC_API_URL=https://api.${{ vars.DOMAIN }}
NEXT_PUBLIC_APP_URL=https://${{ vars.DOMAIN }}
NEXT_PUBLIC_OAUTH_REDIRECT_URI=https://${{ vars.DOMAIN }}/api/auth/callback
NEXT_PUBLIC_CHATKIT_DOMAIN_KEY=${{ secrets.CHATKIT_DOMAIN_KEY }}
# =============================================================================
# Deploy to Kubernetes cluster
# =============================================================================
deploy:
needs: [changes, build-api, build-sso, build-mcp, build-notification, build-web]
# Run if any build ran OR if only helm changed
if: |
always() &&
(needs.changes.outputs.any_service == 'true' || needs.changes.outputs.helm == 'true' || github.event_name == 'workflow_dispatch') &&
!contains(needs.*.result, 'failure')
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Set up Helm
uses: azure/setup-helm@v3
with:
version: 'v3.13.0'
# For Azure AKS
- name: Azure Login
if: ${{ vars.CLOUD_PROVIDER == 'azure' }}
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Get AKS credentials
if: ${{ vars.CLOUD_PROVIDER == 'azure' }}
run: |
az aks get-credentials \
--resource-group ${{ vars.AZURE_RESOURCE_GROUP }} \
--name ${{ vars.AZURE_CLUSTER_NAME }}
# For GKE
- name: Authenticate to GKE
if: ${{ vars.CLOUD_PROVIDER == 'gke' }}
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS }}
- name: Get GKE credentials
if: ${{ vars.CLOUD_PROVIDER == 'gke' }}
uses: google-github-actions/get-gke-credentials@v1
with:
cluster_name: ${{ vars.GKE_CLUSTER_NAME }}
location: ${{ vars.GKE_CLUSTER_ZONE }}
# For any provider with kubeconfig
- name: Set kubeconfig
if: ${{ vars.CLOUD_PROVIDER == 'kubeconfig' }}
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Add Dapr Helm repo
run: |
helm repo add dapr https://dapr.github.io/helm-charts/
helm repo update
- name: Install/Upgrade Dapr
run: |
helm upgrade --install dapr dapr/dapr \
--version=1.15 \
--namespace dapr-system \
--create-namespace \
--set dapr_scheduler.cluster.storageSize=4Gi \
--wait
- name: Create namespace
run: kubectl create namespace taskflow --dry-run=client -o yaml | kubectl apply -f -
- name: Create GHCR pull secret
run: |
kubectl create secret docker-registry ghcr-secret \
--namespace taskflow \
--docker-server=ghcr.io \
--docker-username=${{ github.actor }} \
--docker-password=${{ secrets.GITHUB_TOKEN }} \
--dry-run=client -o yaml | kubectl apply -f -
- name: Deploy with Helm
run: |
# Determine ingress class based on what's installed
INGRESS_CLASS="${{ vars.INGRESS_CLASS }}"
if [ -z "$INGRESS_CLASS" ]; then
INGRESS_CLASS="traefik" # Default to traefik
fi
# Create temporary values file for comma-containing values (avoids Helm --set parsing issues)
cat > /tmp/helm-overrides.yaml << 'ENDOFVALUES'
sso:
env:
ALLOWED_ORIGINS: "https://${{ vars.DOMAIN }},https://sso.${{ vars.DOMAIN }},https://api.${{ vars.DOMAIN }},https://mcp.${{ vars.DOMAIN }}"
api:
env:
CORS_ORIGINS: "https://${{ vars.DOMAIN }},https://sso.${{ vars.DOMAIN }},https://api.${{ vars.DOMAIN }},https://mcp.${{ vars.DOMAIN }}"
ENDOFVALUES
# Build image override args - only set tags for services that were actually built
IMAGE_ARGS=""
# Always set repositories
IMAGE_ARGS="$IMAGE_ARGS --set sso.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/sso"
IMAGE_ARGS="$IMAGE_ARGS --set api.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/api"
IMAGE_ARGS="$IMAGE_ARGS --set web.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/web"
IMAGE_ARGS="$IMAGE_ARGS --set mcpServer.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/mcp"
IMAGE_ARGS="$IMAGE_ARGS --set notificationService.image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/notification"
# Only set tags for services that were built (or all on manual trigger)
if [ "${{ needs.changes.outputs.sso }}" == "true" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
IMAGE_ARGS="$IMAGE_ARGS --set sso.image.tag=${{ github.sha }}"
fi
if [ "${{ needs.changes.outputs.api }}" == "true" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
IMAGE_ARGS="$IMAGE_ARGS --set api.image.tag=${{ github.sha }}"
fi
if [ "${{ needs.changes.outputs.web }}" == "true" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
IMAGE_ARGS="$IMAGE_ARGS --set web.image.tag=${{ github.sha }}"
fi
if [ "${{ needs.changes.outputs.mcp }}" == "true" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
IMAGE_ARGS="$IMAGE_ARGS --set mcpServer.image.tag=${{ github.sha }}"
fi
if [ "${{ needs.changes.outputs.notification }}" == "true" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
IMAGE_ARGS="$IMAGE_ARGS --set notificationService.image.tag=${{ github.sha }}"
fi
helm upgrade --install taskflow ./infrastructure/helm/taskflow \
--namespace taskflow \
--values infrastructure/helm/taskflow/values-cloud.yaml \
--values /tmp/helm-overrides.yaml \
--set global.imagePullSecrets[0].name=ghcr-secret \
$IMAGE_ARGS \
--set managedServices.neon.enabled=true \
--set "managedServices.neon.ssoDatabase=${{ secrets.NEON_SSO_DATABASE_URL }}" \
--set "managedServices.neon.apiDatabase=${{ secrets.NEON_API_DATABASE_URL }}" \
--set "managedServices.neon.chatkitDatabase=${{ secrets.NEON_CHATKIT_DATABASE_URL }}" \
--set "managedServices.neon.notificationDatabase=${{ secrets.NEON_NOTIFICATION_DATABASE_URL }}" \
--set managedServices.upstash.enabled=true \
--set "managedServices.upstash.host=${{ secrets.UPSTASH_REDIS_HOST }}" \
--set "managedServices.upstash.password=${{ secrets.UPSTASH_REDIS_PASSWORD }}" \
--set "managedServices.upstash.restUrl=${{ secrets.REDIS_URL }}" \
--set "managedServices.upstash.restToken=${{ secrets.REDIS_TOKEN }}" \
--set "sso.env.BETTER_AUTH_SECRET=${{ secrets.BETTER_AUTH_SECRET }}" \
--set "sso.env.BETTER_AUTH_URL=https://sso.${{ vars.DOMAIN }}" \
--set "sso.smtp.user=${{ secrets.SMTP_USER }}" \
--set "sso.smtp.password=${{ secrets.SMTP_PASSWORD }}" \
--set "api.openai.apiKey=${{ secrets.OPENAI_API_KEY }}" \
--set notificationService.enabled=true \
--set dapr.enabled=true \
--set "dapr.pubsub.redisHost=${{ secrets.UPSTASH_REDIS_HOST }}" \
--set "dapr.pubsub.redisPassword=${{ secrets.UPSTASH_REDIS_PASSWORD }}" \
--set "global.domain=${{ vars.DOMAIN }}" \
--set "sso.ingress.className=$INGRESS_CLASS" \
--set "sso.ingress.host=sso.${{ vars.DOMAIN }}" \
--set "api.ingress.className=$INGRESS_CLASS" \
--set "api.ingress.host=api.${{ vars.DOMAIN }}" \
--set "mcpServer.ingress.enabled=true" \
--set "mcpServer.ingress.className=$INGRESS_CLASS" \
--set "mcpServer.ingress.host=mcp.${{ vars.DOMAIN }}" \
--set "mcpServer.env.TASKFLOW_SSO_PUBLIC_URL=https://sso.${{ vars.DOMAIN }}" \
--set "mcpServer.env.TASKFLOW_MCP_PUBLIC_URL=https://mcp.${{ vars.DOMAIN }}" \
--set "web.ingress.className=$INGRESS_CLASS" \
--set "web.ingress.host=${{ vars.DOMAIN }}" \
--set "ingress-nginx.enabled=false" \
--wait \
--timeout 10m
- name: Verify deployment
run: |
echo "Checking pod status..."
kubectl get pods -n taskflow
echo ""
echo "Checking services..."
kubectl get svc -n taskflow
echo ""
echo "Checking ingress..."
kubectl get ingress -n taskflow 2>/dev/null || echo "No ingress configured"
- name: Post deployment URLs
run: |
echo "## Deployment Complete! :rocket:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Services Built" >> $GITHUB_STEP_SUMMARY
echo "| Service | Built |" >> $GITHUB_STEP_SUMMARY
echo "|---------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| API | ${{ needs.changes.outputs.api == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| SSO | ${{ needs.changes.outputs.sso == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| MCP | ${{ needs.changes.outputs.mcp == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Notification | ${{ needs.changes.outputs.notification == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Web | ${{ needs.changes.outputs.web == 'true' && '✅' || '⏭️ skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### URLs" >> $GITHUB_STEP_SUMMARY
echo "| Service | URL |" >> $GITHUB_STEP_SUMMARY
echo "|---------|-----|" >> $GITHUB_STEP_SUMMARY
echo "| Web Dashboard | https://${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY
echo "| SSO Platform | https://sso.${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY
echo "| API | https://api.${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY
echo "| MCP Server | https://mcp.${{ vars.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY