Files
Claude-Code-Monitor/deployments/ci/github-actions/deploy.yaml
T
nntrivi2001 57dc91585d feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
2026-07-30 14:39:03 +07:00

336 lines
12 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ─────────────────────────────────────────────────────────────────────────────
# GitHub Actions Deploy Pipeline Claude Code Agent Monitor
#
# Triggers on version tags and manual dispatch. Deploys to staging
# automatically and to production after manual approval.
# ─────────────────────────────────────────────────────────────────────────────
name: Deploy
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options:
- staging
- production
image_tag:
description: "Image tag to deploy (default: latest from main)"
required: false
type: string
permissions:
contents: read
packages: read
id-token: write # For OIDC cloud auth
concurrency:
group: deploy-${{ github.event.inputs.environment || 'staging' }}
cancel-in-progress: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/agent-monitor
MCP_IMAGE_NAME: ${{ github.repository }}/agent-monitor-mcp
HELM_CHART_PATH: deployments/helm/agent-monitor
jobs:
# ── Resolve image tag ──────────────────────────────────────────────────
prepare:
name: Prepare Deployment
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Resolve image tag
id: resolve
run: |
if [[ -n "${{ github.event.inputs.image_tag }}" ]]; then
TAG="${{ github.event.inputs.image_tag }}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
TAG="${{ github.ref_name }}"
else
TAG="sha-$(git rev-parse --short HEAD)"
fi
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
echo "Resolved image tag: ${TAG}"
# ── Build (if triggered by tag) ────────────────────────────────────────
build:
name: Build Images
runs-on: ubuntu-latest
needs: prepare
if: github.ref_type == 'tag'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & push app image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.image-tag }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
- name: Build & push MCP image
uses: docker/build-push-action@v5
with:
context: .
file: ./mcp/Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }}:${{ needs.prepare.outputs.image-tag }}
${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
# ── Deploy to Staging ──────────────────────────────────────────────────
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [prepare, build]
if: |
always() &&
needs.prepare.result == 'success' &&
(needs.build.result == 'success' || needs.build.result == 'skipped') &&
(github.event.inputs.environment == 'staging' || github.event.inputs.environment == '' || github.ref_type == 'tag')
environment:
name: staging
url: https://staging.agent-monitor.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_STAGING }}
aws-region: ${{ vars.AWS_REGION || 'us-west-2' }}
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: "v1.29.0"
- name: Setup Helm
uses: azure/setup-helm@v4
with:
version: "v3.14.0"
- name: Update kubeconfig
run: |
aws eks update-kubeconfig \
--region ${{ vars.AWS_REGION || 'us-west-2' }} \
--name ${{ vars.EKS_CLUSTER_STAGING || 'agent-monitor-staging' }}
- name: Deploy to staging via Helm
run: |
helm upgrade --install agent-monitor ${{ env.HELM_CHART_PATH }} \
--namespace agent-monitor-staging \
--create-namespace \
--set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \
--set image.tag=${{ needs.prepare.outputs.image-tag }} \
--set mcp.image.repository=${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }} \
--set mcp.image.tag=${{ needs.prepare.outputs.image-tag }} \
--set environment=staging \
--set ingress.host=staging.agent-monitor.example.com \
--values ${{ env.HELM_CHART_PATH }}/values-staging.yaml \
--wait \
--atomic \
--timeout 600s
- name: Health check
run: |
echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=agent-monitor \
-n agent-monitor-staging \
--timeout=300s
# Port forward and check health
kubectl port-forward svc/agent-monitor 14820:4820 -n agent-monitor-staging &
PF_PID=$!
sleep 5
for i in $(seq 1 10); do
if curl -sf http://localhost:14820/api/health | grep -q '"status":"ok"'; then
echo "✔ Health check passed"
kill $PF_PID 2>/dev/null || true
exit 0
fi
echo "Attempt $i/10..."
sleep 5
done
kill $PF_PID 2>/dev/null || true
echo "✖ Health check failed"
exit 1
- name: Notify Slack (staging)
if: always()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "${{ job.status == 'success' && '✅' || '❌' }} Staging deployment ${{ job.status }}: `${{ needs.prepare.outputs.image-tag }}`",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ job.status == 'success' && ':white_check_mark:' || ':x:' }} *Staging Deployment ${{ job.status }}*\n*Image:* `${{ needs.prepare.outputs.image-tag }}`\n*Commit:* `${{ github.sha }}`\n*Actor:* ${{ github.actor }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
# ── Deploy to Production ───────────────────────────────────────────────
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [prepare, deploy-staging]
if: |
always() &&
needs.prepare.result == 'success' &&
needs.deploy-staging.result == 'success' &&
(github.event.inputs.environment == 'production' || github.ref_type == 'tag')
environment:
name: production
url: https://agent-monitor.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_PRODUCTION }}
aws-region: ${{ vars.AWS_REGION || 'us-west-2' }}
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: "v1.29.0"
- name: Setup Helm
uses: azure/setup-helm@v4
with:
version: "v3.14.0"
- name: Update kubeconfig
run: |
aws eks update-kubeconfig \
--region ${{ vars.AWS_REGION || 'us-west-2' }} \
--name ${{ vars.EKS_CLUSTER_PRODUCTION || 'agent-monitor-production' }}
- name: Create database backup
run: |
chmod +x deployments/scripts/db-backup.sh
# Find a running pod to backup from
POD=$(kubectl get pods -n agent-monitor-production \
-l app.kubernetes.io/name=agent-monitor \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "")
if [[ -n "$POD" ]]; then
echo "Backing up database from pod: $POD"
kubectl exec "$POD" -n agent-monitor-production -- \
sh -c "cp /app/data/dashboard.db /tmp/pre-deploy-backup.db 2>/dev/null || true"
echo "Pre-deploy backup created"
else
echo "⚠ No running pods found skipping backup"
fi
- name: Deploy to production via Helm
run: |
helm upgrade --install agent-monitor ${{ env.HELM_CHART_PATH }} \
--namespace agent-monitor-production \
--create-namespace \
--set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \
--set image.tag=${{ needs.prepare.outputs.image-tag }} \
--set mcp.image.repository=${{ env.REGISTRY }}/${{ env.MCP_IMAGE_NAME }} \
--set mcp.image.tag=${{ needs.prepare.outputs.image-tag }} \
--set environment=production \
--set ingress.host=agent-monitor.example.com \
--values ${{ env.HELM_CHART_PATH }}/values-production.yaml \
--wait \
--atomic \
--timeout 600s
- name: Health check
run: |
echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=agent-monitor \
-n agent-monitor-production \
--timeout=300s
kubectl port-forward svc/agent-monitor 14820:4820 -n agent-monitor-production &
PF_PID=$!
sleep 5
for i in $(seq 1 15); do
if curl -sf http://localhost:14820/api/health | grep -q '"status":"ok"'; then
echo "✔ Production health check passed"
kill $PF_PID 2>/dev/null || true
exit 0
fi
echo "Attempt $i/15..."
sleep 5
done
kill $PF_PID 2>/dev/null || true
echo "✖ Production health check failed!"
exit 1
- name: Notify Slack (production)
if: always()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "${{ job.status == 'success' && '🚀' || '🚨' }} Production deployment ${{ job.status }}: `${{ needs.prepare.outputs.image-tag }}`",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ job.status == 'success' && ':rocket:' || ':rotating_light:' }} *Production Deployment ${{ job.status }}*\n*Image:* `${{ needs.prepare.outputs.image-tag }}`\n*Version:* `${{ needs.prepare.outputs.version }}`\n*Commit:* `${{ github.sha }}`\n*Actor:* ${{ github.actor }}\n*Workflow:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK