# CD Pipeline: Auto-deploy to Mac Mini on push to main # # Requires a self-hosted GitHub Actions runner on the Mac Mini. # Setup: see docs/MAC_MINI_RUNNER_SETUP.md # # Flow: # Push → main : Detects changed services → rebuilds & restarts only those containers # # The runner executes directly on the Mac Mini, so it has access to # Docker, docker-compose, and the local project directory. name: CD Mac Mini on: push: branches: - main workflow_dispatch: inputs: service: description: 'Service to deploy (or "all" for everything)' required: false default: 'all' type: choice options: - all - matrix-web - mana-core-auth - chat-backend - chat-web - todo-backend - todo-web - calendar-backend - calendar-web - clock-backend - clock-web - contacts-backend - contacts-web - matrix-mana-bot concurrency: group: cd-macmini cancel-in-progress: false # Don't cancel in-progress deploys env: PROJECT_DIR: /Users/mana/projects/manacore-monorepo COMPOSE_FILE: docker-compose.macmini.yml ENV_FILE: .env.macmini PATH: /usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin jobs: # =========================================== # Detect what changed # =========================================== detect-changes: name: Detect Changes runs-on: self-hosted if: github.event_name == 'push' outputs: matrix-web: ${{ steps.changes.outputs.matrix-web }} mana-core-auth: ${{ steps.changes.outputs.mana-core-auth }} chat-backend: ${{ steps.changes.outputs.chat-backend }} chat-web: ${{ steps.changes.outputs.chat-web }} todo-backend: ${{ steps.changes.outputs.todo-backend }} todo-web: ${{ steps.changes.outputs.todo-web }} calendar-backend: ${{ steps.changes.outputs.calendar-backend }} calendar-web: ${{ steps.changes.outputs.calendar-web }} clock-backend: ${{ steps.changes.outputs.clock-backend }} clock-web: ${{ steps.changes.outputs.clock-web }} contacts-backend: ${{ steps.changes.outputs.contacts-backend }} contacts-web: ${{ steps.changes.outputs.contacts-web }} matrix-mana-bot: ${{ steps.changes.outputs.matrix-mana-bot }} any-changes: ${{ steps.changes.outputs.any-changes }} steps: - name: Check for changes id: changes run: | cd "${{ env.PROJECT_DIR }}" # Get changed files between previous and current commit CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "") # Shared packages trigger rebuilds for all services that use them SHARED_CHANGED="false" if echo "$CHANGED" | grep -qE "^packages/(shared-ui|shared-theme|shared-icons|shared-tailwind|shared-auth|shared-branding|shared-i18n|shared-utils|shared-types)/"; then SHARED_CHANGED="true" fi check_changes() { local name=$1 shift local result="false" for path in "$@"; do if echo "$CHANGED" | grep -q "^$path"; then result="true" break fi done # Shared package changes trigger rebuild if [ "$SHARED_CHANGED" == "true" ]; then result="true" fi echo "$name=$result" >> $GITHUB_OUTPUT echo " $name: $result" } echo "Changed files:" echo "$CHANGED" | head -20 echo "" echo "Shared packages changed: $SHARED_CHANGED" echo "" check_changes "matrix-web" "apps/matrix/apps/web/" "apps/matrix/packages/" check_changes "mana-core-auth" "services/mana-core-auth/" check_changes "chat-backend" "apps/chat/apps/backend/" "apps/chat/packages/" check_changes "chat-web" "apps/chat/apps/web/" "apps/chat/packages/" check_changes "todo-backend" "apps/todo/apps/backend/" "apps/todo/packages/" check_changes "todo-web" "apps/todo/apps/web/" "apps/todo/packages/" check_changes "calendar-backend" "apps/calendar/apps/backend/" "apps/calendar/packages/" check_changes "calendar-web" "apps/calendar/apps/web/" "apps/calendar/packages/" check_changes "clock-backend" "apps/clock/apps/backend/" "apps/clock/packages/" check_changes "clock-web" "apps/clock/apps/web/" "apps/clock/packages/" check_changes "contacts-backend" "apps/contacts/apps/backend/" "apps/contacts/packages/" check_changes "contacts-web" "apps/contacts/apps/web/" "apps/contacts/packages/" check_changes "matrix-mana-bot" "services/matrix-mana-bot/" "packages/matrix-bot-common/" # Check if anything needs deploying ANY="false" for svc in matrix-web mana-core-auth chat-backend chat-web todo-backend todo-web calendar-backend calendar-web clock-backend clock-web contacts-backend contacts-web matrix-mana-bot; do val=$(grep "^$svc=" $GITHUB_OUTPUT | tail -1 | cut -d= -f2) if [ "$val" == "true" ]; then ANY="true" break fi done echo "any-changes=$ANY" >> $GITHUB_OUTPUT # =========================================== # Deploy changed services # =========================================== deploy: name: Deploy runs-on: self-hosted needs: [detect-changes] if: | always() && (needs.detect-changes.result == 'success' && needs.detect-changes.outputs.any-changes == 'true') || github.event_name == 'workflow_dispatch' steps: - name: Pull latest code run: | cd "${{ env.PROJECT_DIR }}" git pull origin main - name: Ensure env vars exist run: | cd "${{ env.PROJECT_DIR }}" # Add CALENDAR_ENCRYPTION_KEY if not present if ! grep -q "CALENDAR_ENCRYPTION_KEY" "${{ env.ENV_FILE }}" 2>/dev/null; then echo "CALENDAR_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "${{ env.ENV_FILE }}" echo "Added CALENDAR_ENCRYPTION_KEY to ${{ env.ENV_FILE }}" fi - name: Determine services to deploy id: services run: | cd "${{ env.PROJECT_DIR }}" SERVICES="" if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then INPUT="${{ inputs.service }}" if [ "$INPUT" == "all" ]; then echo "Manual deploy: all services" echo "deploy-all=true" >> $GITHUB_OUTPUT exit 0 else SERVICES="$INPUT" fi else # Build list from detected changes if [ "${{ needs.detect-changes.outputs.matrix-web }}" == "true" ]; then SERVICES="$SERVICES matrix-web"; fi if [ "${{ needs.detect-changes.outputs.mana-core-auth }}" == "true" ]; then SERVICES="$SERVICES mana-auth"; fi if [ "${{ needs.detect-changes.outputs.chat-backend }}" == "true" ]; then SERVICES="$SERVICES chat-backend"; fi if [ "${{ needs.detect-changes.outputs.chat-web }}" == "true" ]; then SERVICES="$SERVICES chat-web"; fi if [ "${{ needs.detect-changes.outputs.todo-backend }}" == "true" ]; then SERVICES="$SERVICES todo-backend"; fi if [ "${{ needs.detect-changes.outputs.todo-web }}" == "true" ]; then SERVICES="$SERVICES todo-web"; fi if [ "${{ needs.detect-changes.outputs.calendar-backend }}" == "true" ]; then SERVICES="$SERVICES calendar-backend"; fi if [ "${{ needs.detect-changes.outputs.calendar-web }}" == "true" ]; then SERVICES="$SERVICES calendar-web"; fi if [ "${{ needs.detect-changes.outputs.clock-backend }}" == "true" ]; then SERVICES="$SERVICES clock-backend"; fi if [ "${{ needs.detect-changes.outputs.clock-web }}" == "true" ]; then SERVICES="$SERVICES clock-web"; fi if [ "${{ needs.detect-changes.outputs.contacts-backend }}" == "true" ]; then SERVICES="$SERVICES contacts-backend"; fi if [ "${{ needs.detect-changes.outputs.contacts-web }}" == "true" ]; then SERVICES="$SERVICES contacts-web"; fi if [ "${{ needs.detect-changes.outputs.matrix-mana-bot }}" == "true" ]; then SERVICES="$SERVICES matrix-mana-bot"; fi fi echo "services=$SERVICES" >> $GITHUB_OUTPUT echo "deploy-all=false" >> $GITHUB_OUTPUT echo "Services to deploy: $SERVICES" - name: Deploy all services if: steps.services.outputs.deploy-all == 'true' run: | cd "${{ env.PROJECT_DIR }}" echo "=== Rebuilding and restarting ALL services ===" docker compose -f "${{ env.COMPOSE_FILE }}" --env-file "${{ env.ENV_FILE }}" up -d --build echo "=== Waiting for services to start ===" sleep 15 docker compose -f "${{ env.COMPOSE_FILE }}" ps - name: Deploy changed services if: steps.services.outputs.deploy-all == 'false' && steps.services.outputs.services != '' run: | cd "${{ env.PROJECT_DIR }}" SERVICES="${{ steps.services.outputs.services }}" echo "=== Rebuilding: $SERVICES ===" docker compose -f "${{ env.COMPOSE_FILE }}" --env-file "${{ env.ENV_FILE }}" up -d --build --no-deps $SERVICES echo "=== Waiting for services to start ===" sleep 10 - name: Health checks run: | cd "${{ env.PROJECT_DIR }}" check_health() { local name=$1 local url=$2 local status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo "000") if [ "$status" == "200" ]; then echo " ✓ $name: OK" else echo " ✗ $name: FAILED (HTTP $status)" fi } echo "=== Health Checks ===" check_health "Auth API" "http://localhost:3001/health" check_health "Matrix Web" "http://localhost:5180/health" check_health "Chat Backend" "http://localhost:3030/health" check_health "Chat Web" "http://localhost:5010/health" check_health "Todo Backend" "http://localhost:3031/health" check_health "Todo Web" "http://localhost:5011/health" check_health "Calendar Backend" "http://localhost:3032/health" check_health "Calendar Web" "http://localhost:5012/health" check_health "Clock Backend" "http://localhost:3033/health" check_health "Clock Web" "http://localhost:5013/health" - name: Summary run: | echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.services.outputs.deploy-all }}" == "true" ]; then echo "**Services:** All" >> $GITHUB_STEP_SUMMARY else echo "**Services:** ${{ steps.services.outputs.services }}" >> $GITHUB_STEP_SUMMARY fi