Some checks failed
		
		
	
	Build Test Deploy / authority-container (push) Has been cancelled
				
			Build Test Deploy / docs (push) Has been cancelled
				
			Build Test Deploy / deploy (push) Has been cancelled
				
			Build Test Deploy / build-test (push) Has been cancelled
				
			Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
			
				
	
	
		
			342 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
| # .gitea/workflows/build-test-deploy.yml
 | ||
| # Unified CI/CD workflow for git.stella-ops.org (Feedser monorepo)
 | ||
| 
 | ||
| name: Build Test Deploy
 | ||
| 
 | ||
| on:
 | ||
|   push:
 | ||
|     branches: [ main ]
 | ||
|     paths:
 | ||
|       - 'src/**'
 | ||
|       - 'docs/**'
 | ||
|       - 'scripts/**'
 | ||
|       - 'Directory.Build.props'
 | ||
|       - 'Directory.Build.targets'
 | ||
|       - 'global.json'
 | ||
|       - '.gitea/workflows/**'
 | ||
|   pull_request:
 | ||
|     branches: [ main, develop ]
 | ||
|     paths:
 | ||
|       - 'src/**'
 | ||
|       - 'docs/**'
 | ||
|       - 'scripts/**'
 | ||
|       - '.gitea/workflows/**'
 | ||
|   workflow_dispatch:
 | ||
|     inputs:
 | ||
|       force_deploy:
 | ||
|         description: 'Ignore branch checks and run the deploy stage'
 | ||
|         required: false
 | ||
|         default: 'false'
 | ||
|         type: boolean
 | ||
| 
 | ||
| env:
 | ||
|   DOTNET_VERSION: '10.0.100-rc.1.25451.107'
 | ||
|   BUILD_CONFIGURATION: Release
 | ||
|   CI_CACHE_ROOT: /data/.cache/stella-ops/feedser
 | ||
|   RUNNER_TOOL_CACHE: /toolcache
 | ||
| 
 | ||
| jobs:
 | ||
|   build-test:
 | ||
|     runs-on: ubuntu-22.04
 | ||
|     environment: ${{ github.event_name == 'pull_request' && 'preview' || 'staging' }}
 | ||
|     env:
 | ||
|       PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/webservice
 | ||
|       AUTHORITY_PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/authority
 | ||
|       TEST_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results
 | ||
|     steps:
 | ||
|       - name: Checkout repository
 | ||
|         uses: actions/checkout@v4
 | ||
|         with:
 | ||
|           fetch-depth: 0
 | ||
| 
 | ||
|       - name: Setup .NET ${{ env.DOTNET_VERSION }}
 | ||
|         uses: actions/setup-dotnet@v4
 | ||
|         with:
 | ||
|           dotnet-version: ${{ env.DOTNET_VERSION }}
 | ||
|           include-prerelease: true
 | ||
| 
 | ||
|       - name: Restore dependencies
 | ||
|         run: dotnet restore src/StellaOps.Feedser.sln
 | ||
| 
 | ||
|       - name: Build solution (warnings as errors)
 | ||
|         run: dotnet build src/StellaOps.Feedser.sln --configuration $BUILD_CONFIGURATION --no-restore -warnaserror
 | ||
| 
 | ||
|       - name: Run unit and integration tests
 | ||
|         run: |
 | ||
|           mkdir -p "$TEST_RESULTS_DIR"
 | ||
|           dotnet test src/StellaOps.Feedser.sln \
 | ||
|             --configuration $BUILD_CONFIGURATION \
 | ||
|             --no-build \
 | ||
|             --logger "trx;LogFileName=stellaops-feedser-tests.trx" \
 | ||
|             --results-directory "$TEST_RESULTS_DIR"
 | ||
| 
 | ||
|       - name: Publish Feedser web service
 | ||
|         run: |
 | ||
|           mkdir -p "$PUBLISH_DIR"
 | ||
|           dotnet publish src/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj \
 | ||
|             --configuration $BUILD_CONFIGURATION \
 | ||
|             --no-build \
 | ||
|             --output "$PUBLISH_DIR"
 | ||
| 
 | ||
|       - name: Upload published artifacts
 | ||
|         uses: actions/upload-artifact@v4
 | ||
|         with:
 | ||
|           name: feedser-publish
 | ||
|           path: ${{ env.PUBLISH_DIR }}
 | ||
|           if-no-files-found: error
 | ||
|           retention-days: 7
 | ||
| 
 | ||
|       - name: Restore Authority solution
 | ||
|         run: dotnet restore src/StellaOps.Authority/StellaOps.Authority.sln
 | ||
| 
 | ||
|       - name: Build Authority solution
 | ||
|         run: dotnet build src/StellaOps.Authority/StellaOps.Authority.sln --configuration $BUILD_CONFIGURATION --no-restore -warnaserror
 | ||
| 
 | ||
|       - name: Run Authority tests
 | ||
|         run: |
 | ||
|           dotnet test src/StellaOps.Configuration.Tests/StellaOps.Configuration.Tests.csproj \
 | ||
|             --configuration $BUILD_CONFIGURATION \
 | ||
|             --no-build \
 | ||
|             --logger "trx;LogFileName=stellaops-authority-tests.trx" \
 | ||
|             --results-directory "$TEST_RESULTS_DIR"
 | ||
| 
 | ||
|       - name: Publish Authority web service
 | ||
|         run: |
 | ||
|           mkdir -p "$AUTHORITY_PUBLISH_DIR"
 | ||
|           dotnet publish src/StellaOps.Authority/StellaOps.Authority/StellaOps.Authority.csproj \
 | ||
|             --configuration $BUILD_CONFIGURATION \
 | ||
|             --no-build \
 | ||
|             --output "$AUTHORITY_PUBLISH_DIR"
 | ||
| 
 | ||
|       - name: Upload Authority artifacts
 | ||
|         uses: actions/upload-artifact@v4
 | ||
|         with:
 | ||
|           name: authority-publish
 | ||
|           path: ${{ env.AUTHORITY_PUBLISH_DIR }}
 | ||
|           if-no-files-found: error
 | ||
|           retention-days: 7
 | ||
| 
 | ||
|       - name: Upload test results
 | ||
|         if: always()
 | ||
|         uses: actions/upload-artifact@v4
 | ||
|         with:
 | ||
|           name: feedser-test-results
 | ||
|           path: ${{ env.TEST_RESULTS_DIR }}
 | ||
|           if-no-files-found: ignore
 | ||
|           retention-days: 7
 | ||
| 
 | ||
|   authority-container:
 | ||
|     runs-on: ubuntu-22.04
 | ||
|     needs: build-test
 | ||
|     steps:
 | ||
|       - name: Checkout repository
 | ||
|         uses: actions/checkout@v4
 | ||
| 
 | ||
|       - name: Validate Authority compose file
 | ||
|         run: docker compose -f ops/authority/docker-compose.authority.yaml config
 | ||
| 
 | ||
|       - name: Build Authority container image
 | ||
|         run: docker build -f ops/authority/Dockerfile -t stellaops-authority:ci .
 | ||
| 
 | ||
|   docs:
 | ||
|     runs-on: ubuntu-22.04
 | ||
|     env:
 | ||
|       DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-site
 | ||
|     steps:
 | ||
|       - name: Checkout repository
 | ||
|         uses: actions/checkout@v4
 | ||
| 
 | ||
|       - name: Setup Python
 | ||
|         uses: actions/setup-python@v5
 | ||
|         with:
 | ||
|           python-version: '3.11'
 | ||
| 
 | ||
|       - name: Install documentation dependencies
 | ||
|         run: |
 | ||
|           python -m pip install --upgrade pip
 | ||
|           python -m pip install markdown pygments
 | ||
| 
 | ||
|       - name: Render documentation bundle
 | ||
|         run: |
 | ||
|           python scripts/render_docs.py --source docs --output "$DOCS_OUTPUT_DIR" --clean
 | ||
| 
 | ||
|       - name: Upload documentation artifact
 | ||
|         uses: actions/upload-artifact@v4
 | ||
|         with:
 | ||
|           name: feedser-docs-site
 | ||
|           path: ${{ env.DOCS_OUTPUT_DIR }}
 | ||
|           if-no-files-found: error
 | ||
|           retention-days: 7
 | ||
| 
 | ||
|   deploy:
 | ||
|     runs-on: ubuntu-22.04
 | ||
|     needs: [build-test, docs]
 | ||
|     if: >-
 | ||
|       needs.build-test.result == 'success' &&
 | ||
|       needs.docs.result == 'success' &&
 | ||
|       (
 | ||
|         (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
 | ||
|         github.event_name == 'workflow_dispatch'
 | ||
|       )
 | ||
|     environment: staging
 | ||
|     steps:
 | ||
|       - name: Checkout repository
 | ||
|         uses: actions/checkout@v4
 | ||
|         with:
 | ||
|           sparse-checkout: |
 | ||
|             scripts
 | ||
|             .gitea/workflows
 | ||
|           sparse-checkout-cone-mode: true
 | ||
| 
 | ||
|       - name: Check if deployment should proceed
 | ||
|         id: check-deploy
 | ||
|         run: |
 | ||
|           if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
 | ||
|             if [ "${{ github.event.inputs.force_deploy }}" = "true" ]; then
 | ||
|               echo "should-deploy=true" >> $GITHUB_OUTPUT
 | ||
|               echo "✅ Manual deployment requested"
 | ||
|             else
 | ||
|               echo "should-deploy=false" >> $GITHUB_OUTPUT
 | ||
|               echo "ℹ️ Manual dispatch without force_deploy=true — skipping"
 | ||
|             fi
 | ||
|           elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
 | ||
|             echo "should-deploy=true" >> $GITHUB_OUTPUT
 | ||
|             echo "✅ Deploying latest main branch build"
 | ||
|           else
 | ||
|             echo "should-deploy=false" >> $GITHUB_OUTPUT
 | ||
|             echo "ℹ️ Deployment restricted to main branch"
 | ||
|           fi
 | ||
| 
 | ||
|       - name: Resolve deployment credentials
 | ||
|         id: params
 | ||
|         if: steps.check-deploy.outputs.should-deploy == 'true'
 | ||
|         run: |
 | ||
|           missing=()
 | ||
| 
 | ||
|           host="${{ secrets.STAGING_DEPLOYMENT_HOST }}"
 | ||
|           if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi
 | ||
|           if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
 | ||
|           if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
 | ||
|           if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi
 | ||
| 
 | ||
|           user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}"
 | ||
|           if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi
 | ||
|           if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
 | ||
|           if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
 | ||
|           if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi
 | ||
| 
 | ||
|           path="${{ secrets.STAGING_DEPLOYMENT_PATH }}"
 | ||
|           if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi
 | ||
| 
 | ||
|           docs_path="${{ secrets.STAGING_DOCS_PATH }}"
 | ||
|           if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi
 | ||
| 
 | ||
|           key="${{ secrets.STAGING_DEPLOYMENT_KEY }}"
 | ||
|           if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
 | ||
|           if [ -z "$key" ]; then key="${{ vars.STAGING_DEPLOYMENT_KEY }}"; fi
 | ||
|           if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
 | ||
|           if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi
 | ||
| 
 | ||
|           if [ ${#missing[@]} -gt 0 ]; then
 | ||
|             echo "❌ Missing deployment configuration: ${missing[*]}"
 | ||
|             exit 1
 | ||
|           fi
 | ||
| 
 | ||
|           key_file="$RUNNER_TEMP/staging_deploy_key"
 | ||
|           printf '%s\n' "$key" > "$key_file"
 | ||
|           chmod 600 "$key_file"
 | ||
| 
 | ||
|           echo "host=$host" >> $GITHUB_OUTPUT
 | ||
|           echo "user=$user" >> $GITHUB_OUTPUT
 | ||
|           echo "path=$path" >> $GITHUB_OUTPUT
 | ||
|           echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
 | ||
|           echo "key-file=$key_file" >> $GITHUB_OUTPUT
 | ||
| 
 | ||
|       - name: Download service artifact
 | ||
|         if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs.path != ''
 | ||
|         uses: actions/download-artifact@v4
 | ||
|         with:
 | ||
|           name: feedser-publish
 | ||
|           path: artifacts/service
 | ||
| 
 | ||
|       - name: Download documentation artifact
 | ||
|         if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs['docs-path'] != ''
 | ||
|         uses: actions/download-artifact@v4
 | ||
|         with:
 | ||
|           name: feedser-docs-site
 | ||
|           path: artifacts/docs
 | ||
| 
 | ||
|       - name: Install rsync
 | ||
|         if: steps.check-deploy.outputs.should-deploy == 'true'
 | ||
|         run: |
 | ||
|           if command -v rsync >/dev/null 2>&1; then
 | ||
|             exit 0
 | ||
|           fi
 | ||
|           CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt"
 | ||
|           mkdir -p "$CACHE_DIR"
 | ||
|           KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)"
 | ||
|           DEB_DIR="$CACHE_DIR/$KEY"
 | ||
|           mkdir -p "$DEB_DIR"
 | ||
|           if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then
 | ||
|             apt-get update
 | ||
|             apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb
 | ||
|           else
 | ||
|             apt-get update
 | ||
|             apt-get download rsync libpopt0
 | ||
|             mv rsync*.deb libpopt0*.deb "$DEB_DIR"/
 | ||
|             dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y
 | ||
|           fi
 | ||
| 
 | ||
|       - name: Deploy service bundle
 | ||
|         if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs.path != ''
 | ||
|         env:
 | ||
|           HOST: ${{ steps.params.outputs.host }}
 | ||
|           USER: ${{ steps.params.outputs.user }}
 | ||
|           TARGET: ${{ steps.params.outputs.path }}
 | ||
|           KEY_FILE: ${{ steps.params.outputs['key-file'] }}
 | ||
|         run: |
 | ||
|           SERVICE_DIR="artifacts/service/feedser-publish"
 | ||
|           if [ ! -d "$SERVICE_DIR" ]; then
 | ||
|             echo "❌ Service artifact directory missing ($SERVICE_DIR)"
 | ||
|             exit 1
 | ||
|           fi
 | ||
|           echo "🚀 Deploying Feedser web service to $HOST:$TARGET"
 | ||
|           rsync -az --delete \
 | ||
|             -e "ssh -i $KEY_FILE -o StrictHostKeyChecking=no" \
 | ||
|             "$SERVICE_DIR"/ \
 | ||
|             "$USER@$HOST:$TARGET/"
 | ||
| 
 | ||
|       - name: Deploy documentation bundle
 | ||
|         if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs['docs-path'] != ''
 | ||
|         env:
 | ||
|           HOST: ${{ steps.params.outputs.host }}
 | ||
|           USER: ${{ steps.params.outputs.user }}
 | ||
|           DOCS_TARGET: ${{ steps.params.outputs['docs-path'] }}
 | ||
|           KEY_FILE: ${{ steps.params.outputs['key-file'] }}
 | ||
|         run: |
 | ||
|           DOCS_DIR="artifacts/docs/feedser-docs-site"
 | ||
|           if [ ! -d "$DOCS_DIR" ]; then
 | ||
|             echo "❌ Documentation artifact directory missing ($DOCS_DIR)"
 | ||
|             exit 1
 | ||
|           fi
 | ||
|           echo "📚 Deploying documentation bundle to $HOST:$DOCS_TARGET"
 | ||
|           rsync -az --delete \
 | ||
|             -e "ssh -i $KEY_FILE -o StrictHostKeyChecking=no" \
 | ||
|             "$DOCS_DIR"/ \
 | ||
|             "$USER@$HOST:$DOCS_TARGET/"
 | ||
| 
 | ||
|       - name: Deployment summary
 | ||
|         if: steps.check-deploy.outputs.should-deploy == 'true'
 | ||
|         run: |
 | ||
|           echo "✅ Deployment completed"
 | ||
|           echo "   Host: ${{ steps.params.outputs.host }}"
 | ||
|           echo "   Service path: ${{ steps.params.outputs.path || '(skipped)' }}"
 | ||
|           echo "   Docs path: ${{ steps.params.outputs['docs-path'] || '(skipped)' }}"
 | ||
| 
 | ||
|       - name: Deployment skipped summary
 | ||
|         if: steps.check-deploy.outputs.should-deploy != 'true'
 | ||
|         run: |
 | ||
|           echo "ℹ️ Deployment stage skipped"
 | ||
|           echo "   Event: ${{ github.event_name }}"
 | ||
|           echo "   Ref: ${{ github.ref }}"
 |