name: wine-csp-build on: push: branches: [main, develop] paths: - 'src/__Tools/WineCspService/**' - 'ops/wine-csp/**' - 'third_party/forks/AlexMAS.GostCryptography/**' - '.gitea/workflows/wine-csp-build.yml' pull_request: paths: - 'src/__Tools/WineCspService/**' - 'ops/wine-csp/**' - 'third_party/forks/AlexMAS.GostCryptography/**' workflow_dispatch: inputs: push: description: "Push to registry" required: false default: "false" version: description: "Version tag (e.g., 2025.10.0-edge)" required: false default: "2025.10.0-edge" skip_tests: description: "Skip integration tests" required: false default: "false" env: IMAGE_NAME: registry.stella-ops.org/stellaops/wine-csp DOCKERFILE: ops/wine-csp/Dockerfile # Wine CSP only supports linux/amd64 (Wine ARM64 has compatibility issues with Windows x64 apps) PLATFORMS: linux/amd64 PYTHON_VERSION: "3.11" jobs: # =========================================================================== # Job 1: Build Docker Image # =========================================================================== build: name: Build Wine CSP Image runs-on: ubuntu-latest permissions: contents: read packages: write outputs: image_tag: ${{ steps.version.outputs.tag }} image_digest: ${{ steps.build.outputs.digest }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: install: true - name: Set version tag id: version run: | if [[ -n "${{ github.event.inputs.version }}" ]]; then echo "tag=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then echo "tag=2025.10.0-edge" >> $GITHUB_OUTPUT else echo "tag=pr-${{ github.event.pull_request.number || github.sha }}" >> $GITHUB_OUTPUT fi - name: Docker metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_NAME }} tags: | type=raw,value=${{ steps.version.outputs.tag }} type=sha,format=short - name: Build image id: build uses: docker/build-push-action@v6 with: context: . file: ${{ env.DOCKERFILE }} platforms: ${{ env.PLATFORMS }} push: false load: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - name: Save image for testing run: | mkdir -p /tmp/images docker save "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" | gzip > /tmp/images/wine-csp.tar.gz - name: Upload image artifact uses: actions/upload-artifact@v4 with: name: wine-csp-image path: /tmp/images/wine-csp.tar.gz retention-days: 1 # =========================================================================== # Job 2: Integration Tests # =========================================================================== test: name: Integration Tests runs-on: ubuntu-latest needs: build if: ${{ github.event.inputs.skip_tests != 'true' }} steps: - name: Checkout uses: actions/checkout@v4 - name: Download image artifact uses: actions/download-artifact@v4 with: name: wine-csp-image path: /tmp/images - name: Load Docker image run: | gunzip -c /tmp/images/wine-csp.tar.gz | docker load - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install test dependencies run: | pip install -r ops/wine-csp/tests/requirements.txt - name: Start Wine CSP container id: container run: | echo "Starting Wine CSP container..." docker run -d --name wine-csp-test \ -e WINE_CSP_MODE=limited \ -e WINE_CSP_LOG_LEVEL=Debug \ -p 5099:5099 \ "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" echo "container_id=$(docker ps -q -f name=wine-csp-test)" >> $GITHUB_OUTPUT - name: Wait for service startup run: | echo "Waiting for Wine CSP service to be ready (up to 120s)..." for i in $(seq 1 24); do if curl -sf http://127.0.0.1:5099/health > /dev/null 2>&1; then echo "Service ready after $((i * 5))s" exit 0 fi echo "Waiting... ($((i * 5))s elapsed)" sleep 5 done echo "Service failed to start!" docker logs wine-csp-test exit 1 - name: Run integration tests (pytest) id: pytest run: | mkdir -p test-results export WINE_CSP_URL=http://127.0.0.1:5099 pytest ops/wine-csp/tests/test_wine_csp.py \ -v \ --tb=short \ --junitxml=test-results/junit.xml \ --timeout=60 \ -x \ 2>&1 | tee test-results/pytest-output.txt - name: Run shell integration tests if: always() run: | chmod +x ops/wine-csp/tests/run-tests.sh ops/wine-csp/tests/run-tests.sh \ --url http://127.0.0.1:5099 \ --ci \ --verbose || true - name: Collect container logs if: always() run: | docker logs wine-csp-test > test-results/container.log 2>&1 || true - name: Stop container if: always() run: | docker stop wine-csp-test || true docker rm wine-csp-test || true - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: wine-csp-test-results path: test-results/ - name: Publish test results uses: mikepenz/action-junit-report@v4 if: always() with: report_paths: 'test-results/junit.xml' check_name: 'Wine CSP Integration Tests' fail_on_failure: true # =========================================================================== # Job 3: Security Scan # =========================================================================== security: name: Security Scan runs-on: ubuntu-latest needs: build permissions: security-events: write steps: - name: Checkout uses: actions/checkout@v4 - name: Download image artifact uses: actions/download-artifact@v4 with: name: wine-csp-image path: /tmp/images - name: Load Docker image run: | gunzip -c /tmp/images/wine-csp.tar.gz | docker load - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: image-ref: "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' ignore-unfixed: true - name: Upload Trivy scan results uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: 'trivy-results.sarif' - name: Run Trivy for JSON report uses: aquasecurity/trivy-action@master with: image-ref: "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" format: 'json' output: 'trivy-results.json' severity: 'CRITICAL,HIGH,MEDIUM' - name: Upload Trivy JSON report uses: actions/upload-artifact@v4 with: name: wine-csp-security-scan path: trivy-results.json # =========================================================================== # Job 4: Generate SBOM # =========================================================================== sbom: name: Generate SBOM runs-on: ubuntu-latest needs: build steps: - name: Download image artifact uses: actions/download-artifact@v4 with: name: wine-csp-image path: /tmp/images - name: Load Docker image run: | gunzip -c /tmp/images/wine-csp.tar.gz | docker load - name: Install syft uses: anchore/sbom-action/download-syft@v0 - name: Generate SBOM (SPDX) run: | mkdir -p out/sbom syft "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" \ -o spdx-json=out/sbom/wine-csp.spdx.json - name: Generate SBOM (CycloneDX) run: | syft "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" \ -o cyclonedx-json=out/sbom/wine-csp.cdx.json - name: Upload SBOM artifacts uses: actions/upload-artifact@v4 with: name: wine-csp-sbom-${{ needs.build.outputs.image_tag }} path: out/sbom/ # =========================================================================== # Job 5: Publish (only on main branch or manual trigger) # =========================================================================== publish: name: Publish Image runs-on: ubuntu-latest needs: [build, test, security] if: ${{ (github.event.inputs.push == 'true' || (github.event_name == 'push' && github.ref == 'refs/heads/main')) && needs.test.result == 'success' }} permissions: contents: read packages: write id-token: write steps: - name: Download image artifact uses: actions/download-artifact@v4 with: name: wine-csp-image path: /tmp/images - name: Load Docker image run: | gunzip -c /tmp/images/wine-csp.tar.gz | docker load - name: Install cosign uses: sigstore/cosign-installer@v3.7.0 - name: Login to registry uses: docker/login-action@v3 with: registry: registry.stella-ops.org username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Push to registry run: | docker push "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" # Also tag as latest if on main if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then docker tag "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" "${{ env.IMAGE_NAME }}:latest" docker push "${{ env.IMAGE_NAME }}:latest" fi - name: Sign image with cosign env: COSIGN_EXPERIMENTAL: "1" run: | cosign sign --yes "${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}" || echo "Signing skipped (no OIDC available)" - name: Create release summary run: | echo "## Wine CSP Image Published" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Image:** \`${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**WARNING:** This image is for TEST VECTOR GENERATION ONLY." >> $GITHUB_STEP_SUMMARY # =========================================================================== # Job 6: Air-Gap Bundle # =========================================================================== airgap: name: Air-Gap Bundle runs-on: ubuntu-latest needs: [build, test] if: ${{ needs.test.result == 'success' }} steps: - name: Download image artifact uses: actions/download-artifact@v4 with: name: wine-csp-image path: /tmp/images - name: Create air-gap bundle run: | mkdir -p out/bundles # Copy the image tarball cp /tmp/images/wine-csp.tar.gz out/bundles/wine-csp-${{ needs.build.outputs.image_tag }}.tar.gz # Generate bundle manifest cat > out/bundles/wine-csp-${{ needs.build.outputs.image_tag }}.manifest.json < SHA256SUMS echo "Air-gap bundle contents:" ls -lh - name: Upload air-gap bundle uses: actions/upload-artifact@v4 with: name: wine-csp-bundle-${{ needs.build.outputs.image_tag }} path: out/bundles/ # =========================================================================== # Job 7: Test Summary # =========================================================================== summary: name: Test Summary runs-on: ubuntu-latest needs: [build, test, security, sbom] if: always() steps: - name: Download test results uses: actions/download-artifact@v4 with: name: wine-csp-test-results path: test-results/ continue-on-error: true - name: Create summary run: | echo "## Wine CSP Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Stage | Status |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Build | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Tests | ${{ needs.test.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Security | ${{ needs.security.result }} |" >> $GITHUB_STEP_SUMMARY echo "| SBOM | ${{ needs.sbom.result }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Image Tag:** \`${{ needs.build.outputs.image_tag }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "---" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**SECURITY WARNING:** Wine CSP is for TEST VECTOR GENERATION ONLY." >> $GITHUB_STEP_SUMMARY