Skip to content

Add BookStack analytics dashboard with user and page title tracking #15

Add BookStack analytics dashboard with user and page title tracking

Add BookStack analytics dashboard with user and page title tracking #15

name: Deploy BookStack Agent (Cloud Run)
on:
push:
branches: [main]
paths:
- 'bookstack_agent/**'
- 'src/aieng_bot/bookstack/**'
- '.github/workflows/deploy-bookstack-agent.yml'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
PROJECT_ID: coderd
GAR_LOCATION: us-central1
REPOSITORY: catalog
REGION: us-central1
jobs:
# -------------------------------------------------------------------------
# Job 1 — FastAPI backend
# -------------------------------------------------------------------------
deploy-api:
name: Deploy FastAPI backend
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
env:
SERVICE_NAME: bookstack-agent-api
steps:
- uses: actions/checkout@v6
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/share/boost "$AGENT_TOOLSDIRECTORY"
docker system prune -af --volumes
- uses: docker/setup-buildx-action@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v3
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev --quiet
- name: Ensure Artifact Registry repository exists
run: |
gcloud artifacts repositories describe ${{ env.REPOSITORY }} \
--location=${{ env.GAR_LOCATION }} 2>/dev/null || \
gcloud artifacts repositories create ${{ env.REPOSITORY }} \
--repository-format=docker \
--location=${{ env.GAR_LOCATION }} \
--description="Docker repository for Catalog services"
- name: Build and push API image
id: build
run: |
IMAGE="${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE_NAME }}"
docker build \
-f bookstack_agent/api/Dockerfile \
-t "${IMAGE}:${{ github.sha }}" \
-t "${IMAGE}:latest" \
--cache-from type=gha,scope=${{ env.SERVICE_NAME }} \
--cache-to type=gha,mode=max,scope=${{ env.SERVICE_NAME }} \
.
docker push "${IMAGE}:${{ github.sha }}"
docker push "${IMAGE}:latest"
echo "image=${IMAGE}:${{ github.sha }}" >> $GITHUB_OUTPUT
- name: Deploy API to Cloud Run
id: deploy
run: |
gcloud run deploy ${{ env.SERVICE_NAME }} \
--image ${{ steps.build.outputs.image }} \
--region ${{ env.REGION }} \
--platform managed \
--allow-unauthenticated \
--memory=1Gi \
--cpu=1 \
--timeout=300s \
--max-instances=10 \
--min-instances=1 \
--concurrency=80 \
--port=8080 \
--set-env-vars="ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }},BOOKSTACK_URL=https://bookstack.vectorinstitute.ai,BOOKSTACK_TOKEN_ID=${{ secrets.BOOKSTACK_TOKEN_ID }},BOOKSTACK_TOKEN_SECRET=${{ secrets.BOOKSTACK_TOKEN_SECRET }}" \
--update-labels="deployed-by=github-actions,commit=${{ github.sha }},service=${{ env.SERVICE_NAME }}" \
--quiet
API_URL=$(gcloud run services describe ${{ env.SERVICE_NAME }} \
--region ${{ env.REGION }} --format 'value(status.url)')
echo "url=$API_URL" >> $GITHUB_OUTPUT
echo "✓ API deployed at $API_URL"
- name: Health-check API
run: |
URL="${{ steps.deploy.outputs.url }}/api/health"
for i in $(seq 1 20); do
if curl -sf --max-time 10 "$URL" > /dev/null; then
echo "✓ API healthy"; exit 0
fi
echo "Attempt $i/20…"; sleep 5
done
echo "✗ API health check failed"; exit 1
outputs:
api_url: ${{ steps.deploy.outputs.url }}
# -------------------------------------------------------------------------
# Job 2 — Next.js frontend
# -------------------------------------------------------------------------
deploy-ui:
name: Deploy Next.js frontend
runs-on: ubuntu-latest
needs: deploy-api
permissions:
contents: read
id-token: write
env:
SERVICE_NAME: bookstack-agent-ui
steps:
- uses: actions/checkout@v6
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/share/boost "$AGENT_TOOLSDIRECTORY"
docker system prune -af --volumes
- uses: docker/setup-buildx-action@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v3
- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev --quiet
- name: Build and push UI image
id: build
run: |
IMAGE="${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE_NAME }}"
docker build \
-f bookstack_agent/ui/Dockerfile \
-t "${IMAGE}:${{ github.sha }}" \
-t "${IMAGE}:latest" \
--build-arg NEXT_PUBLIC_APP_URL="https://bookstack.vectorinstitute.ai" \
--build-arg NEXT_PUBLIC_GOOGLE_CLIENT_ID="${{ secrets.GOOGLE_CLIENT_ID }}" \
--cache-from type=gha,scope=${{ env.SERVICE_NAME }} \
--cache-to type=gha,mode=max,scope=${{ env.SERVICE_NAME }} \
bookstack_agent/ui/
docker push "${IMAGE}:${{ github.sha }}"
docker push "${IMAGE}:latest"
echo "image=${IMAGE}:${{ github.sha }}" >> $GITHUB_OUTPUT
- name: Deploy UI to Cloud Run
id: deploy
run: |
gcloud run deploy ${{ env.SERVICE_NAME }} \
--image ${{ steps.build.outputs.image }} \
--region ${{ env.REGION }} \
--platform managed \
--allow-unauthenticated \
--memory=512Mi \
--cpu=1 \
--timeout=60s \
--max-instances=10 \
--min-instances=0 \
--concurrency=80 \
--port=8080 \
--set-env-vars="NODE_ENV=production,NEXT_PUBLIC_APP_URL=https://bookstack.vectorinstitute.ai,BOOKSTACK_API_URL=${{ needs.deploy-api.outputs.api_url }},NEXT_PUBLIC_GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }},GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }},SESSION_SECRET=${{ secrets.SESSION_SECRET }},REDIRECT_URI=https://bookstack.vectorinstitute.ai/aieng-bot/api/auth/callback,ALLOWED_DOMAINS=vectorinstitute.ai" \
--update-labels="deployed-by=github-actions,commit=${{ github.sha }},service=${{ env.SERVICE_NAME }}" \
--quiet
UI_URL=$(gcloud run services describe ${{ env.SERVICE_NAME }} \
--region ${{ env.REGION }} --format 'value(status.url)')
echo "url=$UI_URL" >> $GITHUB_OUTPUT
echo "✓ UI deployed at $UI_URL"
- name: Verify UI
run: |
URL="${{ steps.deploy.outputs.url }}/aieng-bot"
for i in $(seq 1 20); do
if curl -sf --max-time 10 "$URL" > /dev/null; then
echo "✓ UI responding"; exit 0
fi
echo "Attempt $i/20…"; sleep 5
done
echo "✗ UI health check failed"; exit 1
- name: Deployment summary
run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
## 🚀 BookStack Agent Deployed
| Service | URL |
|---------|-----|
| FastAPI API | ${{ needs.deploy-api.outputs.api_url }} |
| Next.js UI | ${{ steps.deploy.outputs.url }} |
**Commit:** `${{ github.sha }}`
### Load Balancer setup for bookstack.vectorinstitute.ai
The load balancer must route:
- `bookstack.vectorinstitute.ai/aieng-bot/*` → `bookstack-agent-ui` Cloud Run service
- `bookstack.vectorinstitute.ai/*` (default) → BookStack VM backend
Run once to wire up the UI service:
```bash
# Create serverless NEG for the UI Cloud Run service
gcloud compute network-endpoint-groups create bookstack-agent-ui-neg \
--region=us-central1 \
--network-endpoint-type=serverless \
--cloud-run-service=bookstack-agent-ui
# Create backend service
gcloud compute backend-services create bookstack-agent-ui-backend \
--global \
--load-balancing-scheme=EXTERNAL_MANAGED
gcloud compute backend-services add-backend bookstack-agent-ui-backend \
--global \
--network-endpoint-group=bookstack-agent-ui-neg \
--network-endpoint-group-region=us-central1
# Add path rule to the existing bookstack.vectorinstitute.ai URL map
# (replace URLMAP_NAME with the actual name)
gcloud compute url-maps import URLMAP_NAME --global << YAML
pathMatchers:
- name: bookstack-paths
defaultService: BOOKSTACK_VM_BACKEND
pathRules:
- paths: ["/aieng-bot", "/aieng-bot/*"]
service: bookstack-agent-ui-backend
YAML
```
EOF