Add deployment
CI / frontend (push) Failing after 57s
CI / backend (push) Failing after 6m27s

This commit is contained in:
voltsrage
2026-08-05 00:26:20 +08:00
parent 9e88ff6113
commit 2a3ef62a7d
86 changed files with 2320 additions and 174 deletions
+174
View File
@@ -0,0 +1,174 @@
# Phase 36 Step 12 — build images, migrate, deploy on version tags.
# Requires act_runner with docker, curl, ssh, scp, bash and label ubuntu-latest.
#
# Secrets: REGISTRY_USERNAME, REGISTRY_TOKEN, PG_CONNECTION_DDL,
# DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY
# Variables: PROD_API_URL, REGISTRY (optional; defaults below)
name: CD
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
image_tag:
description: "Image tag to deploy (defaults to the pushed tag)"
required: false
env:
REGISTRY: gitea.example.com/vigilcare
jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- name: Resolve tag and registry
id: meta
run: |
if [ -n "${{ vars.REGISTRY }}" ]; then
echo "REGISTRY=${{ vars.REGISTRY }}" >> "$GITHUB_ENV"
fi
if [ -n "${{ inputs.image_tag }}" ]; then
echo "tag=${{ inputs.image_tag }}" >> "$GITHUB_OUTPUT"
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
else
echo "image_tag input is required for workflow_dispatch without a tag" >&2
exit 1
fi
- name: Log in to the Gitea registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" \
| docker login "${REGISTRY%%/*}" \
-u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
- name: Build and push clinical-api
# Context MUST be repo root — ClinicalContracts is a sibling ProjectReference.
run: |
docker build -f VigilCareClinicalAPI/Dockerfile \
-t "${REGISTRY}/clinical-api:${{ steps.meta.outputs.tag }}" \
-t "${REGISTRY}/clinical-api:latest" .
docker push "${REGISTRY}/clinical-api:${{ steps.meta.outputs.tag }}"
docker push "${REGISTRY}/clinical-api:latest"
- name: Build and push ward-gateway
# Same repo-root context as docker-compose.yml's ward-gateway-api service.
run: |
docker build -f VigilCare.WardGateway/Dockerfile \
-t "${REGISTRY}/ward-gateway:${{ steps.meta.outputs.tag }}" \
-t "${REGISTRY}/ward-gateway:latest" .
docker push "${REGISTRY}/ward-gateway:${{ steps.meta.outputs.tag }}"
docker push "${REGISTRY}/ward-gateway:latest"
- name: Build and push dashboard
# Context is vigilcare-dashboard/ — package.json and nginx.conf live there.
run: |
docker build -f vigilcare-dashboard/Dockerfile \
--build-arg VITE_API_URL="${{ vars.PROD_API_URL }}" \
-t "${REGISTRY}/dashboard:${{ steps.meta.outputs.tag }}" \
-t "${REGISTRY}/dashboard:latest" vigilcare-dashboard
docker push "${REGISTRY}/dashboard:${{ steps.meta.outputs.tag }}"
docker push "${REGISTRY}/dashboard:latest"
migrate:
needs: build-and-push
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:8.0
steps:
- uses: actions/checkout@v4
- name: Build migration bundle
run: |
dotnet tool install --global dotnet-ef --version 8.0.4 \
|| dotnet tool update --global dotnet-ef --version 8.0.4
export PATH="$PATH:/root/.dotnet/tools"
bash ./scripts/build-api-migration-bundle.sh
cp -f ./artifacts/migrate-api ./migrate-api
chmod +x ./migrate-api
# Runs while the previous release is still serving traffic, so every
# migration must be backwards-compatible with the outgoing image.
# See Step 6 — expand-then-contract.
- name: Apply migrations
run: ./migrate-api --connection "${{ secrets.PG_CONNECTION_DDL }}"
deploy:
needs: [build-and-push, migrate]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
- name: Copy compose file
run: |
scp -i ~/.ssh/id_ed25519 docker-compose.prod.yml \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/opt/vigilcare/docker-compose.prod.yml"
- name: Deploy
env:
IMAGE_TAG: ${{ needs.build-and-push.outputs.tag }}
run: |
ssh -i ~/.ssh/id_ed25519 \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
IMAGE_TAG="$IMAGE_TAG" bash -euo pipefail <<'EOF'
cd /opt/vigilcare
# Record the currently deployed tag so a rollback has a target.
grep '^IMAGE_TAG=' .env > .env.previous || true
if grep -q '^IMAGE_TAG=' .env; then
sed -i "s|^IMAGE_TAG=.*|IMAGE_TAG=${IMAGE_TAG}|" .env
else
echo "IMAGE_TAG=${IMAGE_TAG}" >> .env
fi
docker compose -f docker-compose.prod.yml --env-file .env pull
docker compose -f docker-compose.prod.yml --env-file .env up -d --remove-orphans
docker image prune -f
EOF
- name: Smoke test
run: |
ssh -i ~/.ssh/id_ed25519 \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" bash -euo pipefail <<'EOF'
for i in $(seq 1 30); do
if curl -fsS http://localhost:5270/health/ready >/dev/null; then
echo "Ready check passed."
curl -fsS http://localhost:5081/health/live >/dev/null && echo "Gateway live."
curl -fsS http://localhost:8080/ >/dev/null && echo "Dashboard serving."
exit 0
fi
sleep 5
done
echo "Ready check never passed — dumping API logs:"
docker compose -f /opt/vigilcare/docker-compose.prod.yml --env-file /opt/vigilcare/.env logs --tail 100 api
exit 1
EOF
- name: Roll back on failure
if: failure()
run: |
ssh -i ~/.ssh/id_ed25519 \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" bash -euo pipefail <<'EOF'
cd /opt/vigilcare
# Restores the previous image tag only. Schema changes are NOT
# reverted — this is why migrations must be backwards-compatible.
if [ -f .env.previous ]; then
PREV=$(cut -d= -f2 .env.previous)
sed -i "s|^IMAGE_TAG=.*|IMAGE_TAG=${PREV}|" .env
docker compose -f docker-compose.prod.yml --env-file .env up -d
echo "Rolled back to ${PREV}"
fi
EOF
+98
View File
@@ -0,0 +1,98 @@
# Phase 36 Step 11 — build and test on every push/PR.
# Fixtures read ConnectionStrings__* / Redis__* / RabbitMq__* / Kafka__* from the
# environment (see ApiFixture / GatewayApiFixture). Dependencies come from
# docker-compose.yml so Kafka, ES, and MinIO match local integration tests —
# ApiFixture starts hosted services that require those brokers.
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start dependency stack
run: |
docker compose up -d postgres redis rabbitmq kafka elasticsearch minio
docker compose --profile ward-gateway up -d ward-gateway-db ward-gateway-redis ward-gateway-rabbitmq
- name: Wait for Postgres (API + gateway)
run: |
for i in $(seq 1 60); do
docker compose exec -T postgres pg_isready -U postgres && break
sleep 2
done
docker compose exec -T postgres pg_isready -U postgres
docker compose exec -T ward-gateway-db pg_isready -U postgres
- name: Create test databases
run: |
docker compose exec -T postgres psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='vigilcare_test'" \
| grep -q 1 \
|| docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE vigilcare_test"
docker compose exec -T ward-gateway-db psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='vigilcare_ward_test'" \
| grep -q 1 \
|| docker compose exec -T ward-gateway-db psql -U postgres -c "CREATE DATABASE vigilcare_ward_test"
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- name: Restore
run: dotnet restore VigilCareClinical.sln
- name: Build
run: dotnet build VigilCareClinical.sln -c Release --no-restore
- name: Test
env:
# Defaults match docker-compose.yml published ports; fixtures fall back to these anyway.
ConnectionStrings__DefaultConnection: "Host=localhost;Port=5436;Database=vigilcare_test;Username=postgres;Password=password"
ConnectionStrings__GatewayDb: "Host=localhost;Port=5437;Database=vigilcare_ward_test;Username=postgres;Password=password"
Redis__ConnectionString: "localhost:6382,defaultDatabase=1,allowAdmin=true"
Gateway__Redis__ConnectionString: "localhost:6383,defaultDatabase=2,allowAdmin=true"
RabbitMq__Host: "localhost"
RabbitMq__Port: "5674"
Gateway__RabbitMq__Port: "5675"
Kafka__BootstrapServers: "localhost:9092"
Kafka__ReplicationFactor: "1"
Elasticsearch__Uri: "http://localhost:9200"
Minio__Endpoint: "localhost:9005"
run: |
dotnet test VigilCareClinical.sln -c Release --no-build \
--logger "trx;LogFileName=test-results.trx" \
--results-directory ./TestResults
- name: Publish test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: ./TestResults
- name: Tear down stack
if: always()
run: docker compose --profile ward-gateway down -v
frontend:
runs-on: ubuntu-latest
container:
image: node:22-alpine
steps:
- uses: actions/checkout@v4
- name: Install
working-directory: vigilcare-dashboard
run: npm ci
- name: Test
working-directory: vigilcare-dashboard
run: npm run test
- name: Build
working-directory: vigilcare-dashboard
run: npm run build