4b29fafcff
User caught this directly: it's already 2026-07-12 in Korea, but Run #2008's release was tagged quant_20260711.3.7150737 -- the wrong date. Confirmed: UTC was still 2026-07-11 16:2x when KST was already 2026-07-12 01:2x (9-hour offset). prepare-release.yml computed TODAY via `TZ=UTC date +%Y%m%d`, which is only "correct" if the team operates on UTC -- but this project's production server logs, deployment cadence, and team are all Korea Standard Time. Any release cut between midnight and 9am KST would silently tag itself with yesterday's date. Fixed by using `TZ=Asia/Seoul date +%Y%m%d` instead.
204 lines
7.7 KiB
YAML
204 lines
7.7 KiB
YAML
name: Prepare Release
|
|
|
|
on:
|
|
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'
|
|
|
|
jobs:
|
|
build-and-release:
|
|
name: Build & Create Release
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
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
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: |
|
|
VERSION_INPUT="${{ github.event.inputs.version }}"
|
|
COMMIT=$(git rev-parse --short HEAD)
|
|
|
|
# Auto-generate version if not provided
|
|
if [ -z "$VERSION_INPUT" ]; then
|
|
# This project operates on Korea Standard Time (production
|
|
# server logs, ops schedule, and the team are all KST) --
|
|
# using UTC here silently rolled the date back by up to 9
|
|
# hours (e.g. 2026-07-12 01:xx KST is still 2026-07-11 16:xx
|
|
# UTC), so a release cut right after midnight KST would tag
|
|
# itself with yesterday's date.
|
|
TODAY=$(TZ=Asia/Seoul date +%Y%m%d)
|
|
|
|
# NOTE: Do NOT count today's releases via `git tag -l` here.
|
|
# actions/checkout@v4 defaults to a shallow, single-branch
|
|
# clone that does not fetch any tags, so every job container
|
|
# sees zero local tags regardless of how many releases exist
|
|
# -- this is exactly why every release tonight came out as
|
|
# "quant_20260711.1.*" (three of them: b7591fb, 6ab270f,
|
|
# e49922e, all claiming to be deploy #1). Query the actual
|
|
# Gitea Releases API instead, which reflects real state.
|
|
# Sequence number resets to 0 on each new date -- the first
|
|
# release of a day is quant_YYYYMMDD.0.hash, the second .1, etc.
|
|
RELEASES_TODAY=$(curl -sf --connect-timeout 10 --max-time 30 \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
"https://gitea.taxbaik.com/api/v1/repos/${{ github.repository }}/tags?limit=50" \
|
|
| jq -r --arg prefix "quant_${TODAY}." '[.[] | select(.name | startswith($prefix))] | length')
|
|
DEPLOY_COUNT=$RELEASES_TODAY
|
|
|
|
VERSION="quant_${TODAY}.${DEPLOY_COUNT}.${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 Production Config
|
|
run: |
|
|
mkdir -p ./publish
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (no secrets included)"
|
|
|
|
- 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: Create Git Tag
|
|
run: |
|
|
VERSION="${{ steps.metadata.outputs.version }}"
|
|
COMMIT="${{ steps.metadata.outputs.commit }}"
|
|
|
|
git config user.name "Gitea Actions"
|
|
git config user.email "actions@gitea.local"
|
|
|
|
git tag -a "$VERSION" -m "Release $VERSION (commit: $COMMIT)" HEAD
|
|
echo "✓ Local tag created: $VERSION"
|
|
|
|
git push origin "$VERSION"
|
|
echo "✓ Tag pushed: $VERSION"
|
|
|
|
- name: Create Gitea Release
|
|
env:
|
|
VERSION: ${{ steps.metadata.outputs.version }}
|
|
COMMIT: ${{ steps.metadata.outputs.commit }}
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: |
|
|
ARTIFACT="quantengine_${VERSION}.tar.gz"
|
|
API="https://gitea.taxbaik.com/api/v1"
|
|
REPO="kjh2064/QuantEngineByItz"
|
|
|
|
test -s "$ARTIFACT" || { echo "ERROR: artifact missing: $ARTIFACT"; exit 1; }
|
|
|
|
echo "Creating release $VERSION via Gitea API..."
|
|
RELEASE_JSON=$(curl -sf -X POST \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"tag_name\":\"${VERSION}\",\"name\":\"Release ${VERSION}\",\"body\":\"Release Version: ${VERSION} | Commit: ${COMMIT}\",\"target_commitish\":\"main\"}" \
|
|
"${API}/repos/${REPO}/releases")
|
|
|
|
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
|
|
|
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
|
echo "ERROR: Failed to create release"
|
|
echo "$RELEASE_JSON"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✓ Release created: $VERSION (id: $RELEASE_ID)"
|
|
|
|
echo "Uploading artifact..."
|
|
curl -sf -X POST \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H "Content-Type: multipart/form-data" \
|
|
-F "attachment=@${ARTIFACT}" \
|
|
"${API}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${ARTIFACT}" \
|
|
-o /dev/null
|
|
|
|
echo "✓ Artifact attached: $ARTIFACT"
|
|
|
|
notification:
|
|
name: Release Notification
|
|
runs-on: ubuntu-latest
|
|
if: always()
|
|
needs: build-and-release
|
|
|
|
steps:
|
|
- name: Notify Release Ready
|
|
if: needs.build-and-release.result == 'success'
|
|
run: |
|
|
echo "════════════════════════════════════════"
|
|
echo "✅ Release Ready for Deployment"
|
|
echo "════════════════════════════════════════"
|
|
echo "Version: ${{ needs.build-and-release.outputs.version }}"
|
|
echo "Commit: ${{ needs.build-and-release.outputs.commit }}"
|
|
echo ""
|
|
echo "Next: Use deploy-prod.yml to deploy this release"
|
|
echo "════════════════════════════════════════"
|