name: Prepare Release on: workflow_run: workflows: ["Validators (Pushes and Pull Requests)"] types: [completed] workflow_dispatch: inputs: version: description: 'Release version (auto-generated if empty, e.g. quant_20260711.0.abc1234 for the first deploy that day)' required: false type: string env: DOTNET_VERSION: '10.0.x' concurrency: group: prepare-release-${{ github.event.workflow_run.head_sha || github.sha }} cancel-in-progress: false jobs: upstream-gate: name: "Upstream CI Success Gate" runs-on: ubuntu-latest steps: - name: Check CI Pipeline Status run: | if [ "${{ github.event_name }}" = "workflow_run" ]; then if [ "${{ github.event.workflow_run.conclusion }}" != "success" ]; then echo "❌ ERROR: CI pipeline failed — release preparation blocked" exit 1 fi echo "✓ CI pipeline succeeded — proceeding to release" else echo "ℹ Release triggered manually — skipping upstream CI check" fi build-and-release: name: Build & Create Release if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest timeout-minutes: 30 needs: upstream-gate outputs: version: ${{ steps.metadata.outputs.version }} commit: ${{ steps.metadata.outputs.commit }} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Generate Metadata id: metadata run: | VERSION_INPUT="${{ github.event.inputs.version }}" COMMIT=$(git rev-parse --short HEAD) # Auto-generate version if not provided if [ -z "$VERSION_INPUT" ]; then # Simple, reliable version scheme: timestamp + commit hash # Avoids unreliable Gitea API calls (network failures, timeouts) # Format: vYYYY.MM.DD.HHMMSS.COMMIT TIMESTAMP=$(TZ=Asia/Seoul date +%Y.%m.%d.%H%M%S) VERSION="v${TIMESTAMP}.${COMMIT}" else VERSION="$VERSION_INPUT" fi echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "commit=${COMMIT}" >> $GITHUB_OUTPUT echo "Version: $VERSION" echo "Commit: $COMMIT" - name: Restore run: | dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj - name: Build (Release) run: | dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ -c Release \ --no-restore \ -p:ContinuousIntegrationBuild=true - name: Publish run: | dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ -c Release \ -o ./publish \ --no-restore \ --no-build - name: Write Version Text run: | echo "${{ steps.metadata.outputs.version }}" > ./publish/version.txt - name: Write Production Config run: | mkdir -p ./publish VERSION="${{ steps.metadata.outputs.version }}" python3 -c ' import json import pathlib # NOTE: No ConnectionStrings here on purpose. The real DB # password lives only in /home/kjh2064/.config/quantengine.env # on the production server and is injected via systemd # EnvironmentFile (ConnectionStrings__DefaultConnection), # which overrides this file at runtime. Never bake secrets # into a build artifact that ends up in a Gitea Release. config = { "Logging": { "LogLevel": { "Default": "Information" } }, "AppVersion": "'$VERSION'" } pathlib.Path("./publish/appsettings.Production.json").write_text( json.dumps(config, ensure_ascii=False, indent=2), encoding="utf-8" )' test -s ./publish/appsettings.Production.json || { echo "ERROR: appsettings.Production.json is empty"; exit 1; } echo "✓ Production config created (version: $VERSION)" - name: Package Artifact run: | VERSION="${{ steps.metadata.outputs.version }}" ARTIFACT="quantengine_${VERSION}.tar.gz" tar -czf "$ARTIFACT" -C ./publish . echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT echo "✓ Package: $(du -sh $ARTIFACT | cut -f1)" file "$ARTIFACT" - name: Generate Artifact Checksum run: | VERSION="${{ steps.metadata.outputs.version }}" ARTIFACT="quantengine_${VERSION}.tar.gz" sha256sum "$ARTIFACT" | awk '{print $1}' > "${ARTIFACT}.sha256" echo "✓ Checksum created: ${ARTIFACT}.sha256" cat "${ARTIFACT}.sha256" - name: Generate Release Manifest run: | VERSION="${{ steps.metadata.outputs.version }}" COMMIT="${{ steps.metadata.outputs.commit }}" ARTIFACT="quantengine_${VERSION}.tar.gz" CHECKSUM=$(cat "${ARTIFACT}.sha256") python3 - <