name: Release on: push: tags: - 'v*' env: SERVER_HOST: git.dolgolyov-family.by REGISTRY: git.dolgolyov-family.by/alexei.dolgolyov/tiny-forge jobs: # ─────────────────────────────────────────────────────────────────────── # Gate the release on a passing test suite. A tagged release must never # ship code that fails `go vet` / `go test`. # ─────────────────────────────────────────────────────────────────────── test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.25' cache-dependency-path: go.sum - name: Vet Go code run: go vet ./... - name: Run Go tests run: go test ./internal/... -count=1 # ─────────────────────────────────────────────────────────────────────── # Build + push the image FIRST. If this fails, no release is created # (create-release depends on it) — so we never leave an orphan release # pointing at a tag with no published image. # ─────────────────────────────────────────────────────────────────────── build-docker: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Compute tags id: meta run: | TAG="${{ gitea.ref_name }}" VERSION="${TAG#v}" echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT" # Detect pre-release (alpha/beta/rc) — these do NOT get :latest. if echo "$TAG" | grep -qE '(alpha|beta|rc)'; then echo "is_pre=true" >> "$GITHUB_OUTPUT" else echo "is_pre=false" >> "$GITHUB_OUTPUT" fi - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.SERVER_HOST }} username: ${{ gitea.actor }} password: ${{ secrets.DEPLOY_TOKEN }} - name: Build and push image uses: docker/build-push-action@v5 with: context: . push: true tags: | ${{ env.REGISTRY }}:${{ steps.meta.outputs.tag }} ${{ env.REGISTRY }}:${{ steps.meta.outputs.version }} ${{ env.REGISTRY }}:sha-${{ gitea.sha }} ${{ steps.meta.outputs.is_pre == 'false' && format('{0}:latest', env.REGISTRY) || '' }} cache-from: type=registry,ref=${{ env.REGISTRY }}:buildcache cache-to: type=registry,ref=${{ env.REGISTRY }}:buildcache,mode=max - name: Trigger redeploy webhook if: steps.meta.outputs.is_pre == 'false' continue-on-error: true run: | if [ -n "${{ secrets.DOCKER_REDEPLOY_WEBHOOK_URL }}" ]; then echo "Triggering redeploy webhook..." curl -sf -X POST "${{ secrets.DOCKER_REDEPLOY_WEBHOOK_URL }}" \ --max-time 30 || echo "::warning::Redeploy webhook failed" else echo "DOCKER_REDEPLOY_WEBHOOK_URL not set — skipping auto-deploy" fi # ─────────────────────────────────────────────────────────────────────── # Create the Gitea release LAST — body = RELEASE_NOTES.md + auto-changelog. # ─────────────────────────────────────────────────────────────────────── create-release: needs: build-docker runs-on: ubuntu-latest steps: - name: Checkout (full history for changelog) uses: actions/checkout@v4 with: fetch-depth: 0 - name: Generate changelog id: changelog run: | PREV_TAG=$(git tag --sort=-v:refname | head -2 | tail -1) if [ -z "$PREV_TAG" ] || [ "$PREV_TAG" = "${{ gitea.ref_name }}" ]; then git log --oneline --no-decorate -n 20 > /tmp/changelog.txt else git log --oneline --no-decorate "${PREV_TAG}..HEAD" > /tmp/changelog.txt fi - name: Create Gitea release env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} run: | TAG="${{ gitea.ref_name }}" VERSION="${TAG#v}" BASE_URL="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}" # Detect pre-release (alpha/beta/rc) IS_PRE="false" if echo "$TAG" | grep -qE '(alpha|beta|rc)'; then IS_PRE="true" fi # Read release notes if present if [ -f RELEASE_NOTES.md ]; then export RELEASE_NOTES=$(cat RELEASE_NOTES.md) echo "Found RELEASE_NOTES.md" else export RELEASE_NOTES="" echo "No RELEASE_NOTES.md found — release body = changelog only" fi # Build release body (notes + changelog) via Python to avoid shell # escaping and CLI length limits. export TAG VERSION IS_PRE python3 <<'PY' import json, os notes = os.environ.get('RELEASE_NOTES', '') changelog = open('/tmp/changelog.txt').read().strip() sections = [] if notes.strip(): sections.append(notes.strip()) if changelog: sections.append('## Changelog\n\n' + changelog) payload = { 'tag_name': os.environ['TAG'], 'name': os.environ['VERSION'], 'body': '\n\n'.join(sections), 'draft': False, 'prerelease': os.environ['IS_PRE'] == 'true', } with open('/tmp/release-payload.json', 'w') as f: json.dump(payload, f) PY HTTP=$(curl -s -o /tmp/release-resp.json -w "%{http_code}" \ -X POST "$BASE_URL/releases" \ -H "Authorization: token $DEPLOY_TOKEN" \ -H "Content-Type: application/json" \ --data-binary @/tmp/release-payload.json) echo "POST /releases → HTTP $HTTP" if [ "$HTTP" = "201" ]; then RELEASE_ID=$(python3 -c "import json; print(json.load(open('/tmp/release-resp.json'))['id'])") echo "Created release $RELEASE_ID for $TAG" elif [ "$HTTP" = "409" ] || grep -q "already exists" /tmp/release-resp.json; then echo "::warning::Release already exists for tag $TAG — reusing" else echo "::error::Failed to create release for $TAG (HTTP $HTTP)" head -c 2000 /tmp/release-resp.json; echo exit 1 fi