207 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
# .gitea/workflows/promote.yml
 | 
						||
# Manual promotion workflow to copy staged artefacts to production
 | 
						||
 | 
						||
name: Promote Feedser (Manual)
 | 
						||
 | 
						||
on:
 | 
						||
  workflow_dispatch:
 | 
						||
    inputs:
 | 
						||
      include_docs:
 | 
						||
        description: 'Also promote the generated documentation bundle'
 | 
						||
        required: false
 | 
						||
        default: 'true'
 | 
						||
        type: boolean
 | 
						||
      tag:
 | 
						||
        description: 'Optional build identifier to record in the summary'
 | 
						||
        required: false
 | 
						||
        default: 'latest'
 | 
						||
        type: string
 | 
						||
 | 
						||
jobs:
 | 
						||
  promote:
 | 
						||
    runs-on: ubuntu-22.04
 | 
						||
    environment: production
 | 
						||
    steps:
 | 
						||
      - name: Checkout repository
 | 
						||
        uses: actions/checkout@v4
 | 
						||
 | 
						||
      - name: Resolve staging credentials
 | 
						||
        id: staging
 | 
						||
        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
 | 
						||
          if [ -z "$path" ]; then missing+=("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 staging configuration: ${missing[*]}"
 | 
						||
            exit 1
 | 
						||
          fi
 | 
						||
 | 
						||
          key_file="$RUNNER_TEMP/staging_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: Resolve production credentials
 | 
						||
        id: production
 | 
						||
        run: |
 | 
						||
          missing=()
 | 
						||
 | 
						||
          host="${{ secrets.PRODUCTION_DEPLOYMENT_HOST }}"
 | 
						||
          if [ -z "$host" ]; then host="${{ vars.PRODUCTION_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+=("PRODUCTION_DEPLOYMENT_HOST"); fi
 | 
						||
 | 
						||
          user="${{ secrets.PRODUCTION_DEPLOYMENT_USERNAME }}"
 | 
						||
          if [ -z "$user" ]; then user="${{ vars.PRODUCTION_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+=("PRODUCTION_DEPLOYMENT_USERNAME"); fi
 | 
						||
 | 
						||
          path="${{ secrets.PRODUCTION_DEPLOYMENT_PATH }}"
 | 
						||
          if [ -z "$path" ]; then path="${{ vars.PRODUCTION_DEPLOYMENT_PATH }}"; fi
 | 
						||
          if [ -z "$path" ]; then missing+=("PRODUCTION_DEPLOYMENT_PATH")
 | 
						||
          fi
 | 
						||
 | 
						||
          docs_path="${{ secrets.PRODUCTION_DOCS_PATH }}"
 | 
						||
          if [ -z "$docs_path" ]; then docs_path="${{ vars.PRODUCTION_DOCS_PATH }}"; fi
 | 
						||
 | 
						||
          key="${{ secrets.PRODUCTION_DEPLOYMENT_KEY }}"
 | 
						||
          if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
 | 
						||
          if [ -z "$key" ]; then key="${{ vars.PRODUCTION_DEPLOYMENT_KEY }}"; fi
 | 
						||
          if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
 | 
						||
          if [ -z "$key" ]; then missing+=("PRODUCTION_DEPLOYMENT_KEY"); fi
 | 
						||
 | 
						||
          if [ ${#missing[@]} -gt 0 ]; then
 | 
						||
            echo "❌ Missing production configuration: ${missing[*]}"
 | 
						||
            exit 1
 | 
						||
          fi
 | 
						||
 | 
						||
          key_file="$RUNNER_TEMP/production_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: Install rsync
 | 
						||
        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: Fetch staging artefacts
 | 
						||
        id: fetch
 | 
						||
        run: |
 | 
						||
          staging_root="${{ runner.temp }}/staging"
 | 
						||
          mkdir -p "$staging_root/service" "$staging_root/docs"
 | 
						||
 | 
						||
          echo "📥 Copying service bundle from staging"
 | 
						||
          rsync -az --delete \
 | 
						||
            -e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
 | 
						||
            "${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs.path }}/" \
 | 
						||
            "$staging_root/service/"
 | 
						||
 | 
						||
          if [ "${{ github.event.inputs.include_docs }}" = "true" ] && [ -n "${{ steps.staging.outputs['docs-path'] }}" ]; then
 | 
						||
            echo "📥 Copying documentation bundle from staging"
 | 
						||
            rsync -az --delete \
 | 
						||
              -e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
 | 
						||
              "${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs['docs-path'] }}/" \
 | 
						||
              "$staging_root/docs/"
 | 
						||
          else
 | 
						||
            echo "ℹ️ Documentation promotion skipped"
 | 
						||
          fi
 | 
						||
 | 
						||
          echo "service-dir=$staging_root/service" >> $GITHUB_OUTPUT
 | 
						||
          echo "docs-dir=$staging_root/docs" >> $GITHUB_OUTPUT
 | 
						||
 | 
						||
      - name: Backup production service content
 | 
						||
        run: |
 | 
						||
          ssh -o StrictHostKeyChecking=no -i "${{ steps.production.outputs['key-file'] }}" \
 | 
						||
            "${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}" \
 | 
						||
            "set -e; TARGET='${{ steps.production.outputs.path }}'; \
 | 
						||
             if [ -d \"$TARGET\" ]; then \
 | 
						||
               parent=\$(dirname \"$TARGET\"); \
 | 
						||
               base=\$(basename \"$TARGET\"); \
 | 
						||
               backup=\"\$parent/\${base}.backup.\$(date +%Y%m%d_%H%M%S)\"; \
 | 
						||
               mkdir -p \"\$backup\"; \
 | 
						||
               rsync -a --delete \"$TARGET/\" \"\$backup/\"; \
 | 
						||
               ls -dt \"\$parent/\${base}.backup.*\" 2>/dev/null | tail -n +6 | xargs rm -rf || true; \
 | 
						||
               echo 'Backup created at ' \"\$backup\"; \
 | 
						||
             else \
 | 
						||
               echo 'Production service path missing; skipping backup'; \
 | 
						||
             fi"
 | 
						||
 | 
						||
      - name: Publish service to production
 | 
						||
        run: |
 | 
						||
          rsync -az --delete \
 | 
						||
            -e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
 | 
						||
            "${{ steps.fetch.outputs['service-dir'] }}/" \
 | 
						||
            "${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs.path }}/"
 | 
						||
 | 
						||
      - name: Promote documentation bundle
 | 
						||
        if: github.event.inputs.include_docs == 'true' && steps.production.outputs['docs-path'] != ''
 | 
						||
        run: |
 | 
						||
          rsync -az --delete \
 | 
						||
            -e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
 | 
						||
            "${{ steps.fetch.outputs['docs-dir'] }}/" \
 | 
						||
            "${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs['docs-path'] }}/"
 | 
						||
 | 
						||
      - name: Promotion summary
 | 
						||
        run: |
 | 
						||
          echo "✅ Promotion completed"
 | 
						||
          echo "   Tag: ${{ github.event.inputs.tag }}"
 | 
						||
          echo "   Service: ${{ steps.staging.outputs.host }} → ${{ steps.production.outputs.host }}"
 | 
						||
          if [ "${{ github.event.inputs.include_docs }}" = "true" ]; then
 | 
						||
            echo "   Docs: included"
 | 
						||
          else
 | 
						||
            echo "   Docs: skipped"
 | 
						||
          fi
 |