name: Build and Publish Docker Image on: pull_request: branches: - main push: branches: - main jobs: build-and-validate: name: Build and Validate runs-on: ubuntu steps: - name: Checkout uses: actions/checkout@v4 - name: Validate PR Title if: github.event_name == 'pull_request' run: | PR_TITLE="${{ github.event.pull_request.title }}" echo "PR Title: $PR_TITLE" # Check if PR title starts with valid prefix if [[ "$PR_TITLE" =~ ^(Release|Feature|Hotfix|Bugfix)/ ]]; then echo "✅ PR title is valid: $PR_TITLE" else echo "❌ PR title must start with one of: Release/, Feature/, Hotfix/, or Bugfix/" echo "Current title: $PR_TITLE" exit 1 fi - name: Build Docker Image run: | docker build --build-arg VERSION=test --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -t threejs-test:test . - name: Validate Image run: | docker run --rm -d --name test-container -p 8080:80 threejs-test:test sleep 2 curl -f http://localhost:8080 || exit 1 docker stop test-container - name: Job Summary if: success() run: | SUMMARY_FILE="${FORGEJO_STEP_SUMMARY}" cat >> "$SUMMARY_FILE" << 'EOF' ## ✅ Build and Validation Complete - ✅ Docker image built successfully - ✅ Image validated (container started and HTTP check passed) The image is ready for deployment. EOF publish: name: Publish to Registry runs-on: ubuntu needs: build-and-validate if: github.event_name == 'push' steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for tags token: ${{ secrets.FORGEBOT_ACCESS_TOKEN }} - name: Login to Registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.jusemon.com -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin - name: Determine Version id: version run: | # Get latest version tag LATEST_TAG=$(git describe --tags --match 'v*.*.*' --abbrev=0 2>/dev/null || echo "v0.0.0") LATEST_VERSION="${LATEST_TAG#v}" # Parse version components IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_VERSION" MAJOR=${MAJOR:-0} MINOR=${MINOR:-0} PATCH=${PATCH:-0} # Get PR title from event or merge commit message if [[ "${{ github.event_name }}" == "pull_request" ]]; then PR_TITLE="${{ github.event.pull_request.title }}" echo "PR Title: $PR_TITLE" else # For push events, check merge commit message # Merge commits often have format: "Merge pull request #X from branch" or "PR Title (#X)" COMMIT_MSG=$(git log -1 --pretty=format:"%s" HEAD) echo "Commit message: $COMMIT_MSG" # Try to extract PR title from merge commit # Look for patterns like "Release/...", "Feature/...", etc. in the commit message if echo "$COMMIT_MSG" | grep -qE "^(Release|Feature|Hotfix|Bugfix)/"; then # If commit message itself starts with prefix, use it PR_TITLE=$(echo "$COMMIT_MSG" | grep -oE "^(Release|Feature|Hotfix|Bugfix)/[^[:space:]]*" | head -1) elif echo "$COMMIT_MSG" | grep -qE "(Release|Feature|Hotfix|Bugfix)/"; then # Extract prefix pattern from anywhere in the message PR_TITLE=$(echo "$COMMIT_MSG" | grep -oE "(Release|Feature|Hotfix|Bugfix)/[^[:space:]]*" | head -1) else # Check if it's a merge commit and try to get the original PR title # For Forgejo, merge commits might reference the PR PR_TITLE="$COMMIT_MSG" echo "⚠️ Could not extract PR title prefix from commit message, using full message" fi echo "Extracted PR title: $PR_TITLE" fi # Determine version type from PR title prefix if [[ "$PR_TITLE" =~ ^Release/ ]]; then VERSION_TYPE="release" echo "Detected: RELEASE - incrementing major version" elif [[ "$PR_TITLE" =~ ^Feature/ ]]; then VERSION_TYPE="feature" echo "Detected: FEATURE - incrementing minor version" elif [[ "$PR_TITLE" =~ ^(Hotfix|Bugfix)/ ]]; then VERSION_TYPE="patch" echo "Detected: HOTFIX/BUGFIX - incrementing patch version" else echo "⚠️ Warning: PR title does not match expected pattern (Release/, Feature/, Hotfix/, or Bugfix/)" echo "Defaulting to FEATURE (minor version increment)" VERSION_TYPE="feature" fi # Increment version based on type if [[ "$LATEST_TAG" == "v0.0.0" ]]; then # First version NEW_VERSION="0.1.0" elif [[ "$VERSION_TYPE" == "release" ]]; then # Release: increment major version and reset minor/patch (0.1.5 -> 1.0.0) MAJOR=$((MAJOR + 1)) MINOR=0 PATCH=0 NEW_VERSION="$MAJOR.$MINOR.$PATCH" elif [[ "$VERSION_TYPE" == "feature" ]]; then # Feature: increment minor version and reset patch (0.1.5 -> 0.2.0) MINOR=$((MINOR + 1)) PATCH=0 NEW_VERSION="$MAJOR.$MINOR.$PATCH" else # Hotfix/Bugfix: increment patch version (0.1.5 -> 0.1.6) PATCH=$((PATCH + 1)) NEW_VERSION="$MAJOR.$MINOR.$PATCH" fi echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT echo "Latest tag: $LATEST_TAG" echo "Version type: $VERSION_TYPE" echo "New version: $NEW_VERSION" echo "Current VERSION file: $(cat VERSION 2>/dev/null || echo 'not found')" - name: Build Docker Image run: | VERSION="${{ steps.version.outputs.version }}" BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") IMAGE_NAME="git.jusemon.com/jusemon/threejs-test:$VERSION" docker build --build-arg VERSION="$VERSION" --build-arg BUILD_DATE="$BUILD_DATE" -t "$IMAGE_NAME" . echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV - name: Push Docker Image run: | IMAGE_NAME="git.jusemon.com/jusemon/threejs-test:${{ steps.version.outputs.version }}" docker push "$IMAGE_NAME" # Also tag as 'latest' for main branch docker tag "$IMAGE_NAME" "git.jusemon.com/jusemon/threejs-test:latest" docker push "git.jusemon.com/jusemon/threejs-test:latest" - name: Generate Release Notes id: release_notes run: | VERSION="${{ steps.version.outputs.version }}" TAG="${{ steps.version.outputs.tag }}" IMAGE_NAME="git.jusemon.com/jusemon/threejs-test:$VERSION" COMMIT_HASH=$(git rev-parse HEAD) COMMIT_SHORT=$(git rev-parse --short HEAD) BUILD_DATE=$(date -u +"%Y-%m-%d %H:%M:%S UTC") # Get commits since last tag LATEST_TAG=$(git describe --tags --match 'v*.*.*' --abbrev=0 2>/dev/null || echo "") if [[ -n "$LATEST_TAG" ]]; then COMMITS=$(git log ${LATEST_TAG}..HEAD --pretty=format:"- %s (%h)" --no-merges) else COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges -10) fi # Get author of the commit COMMIT_AUTHOR=$(git log -1 --pretty=format:"%an <%ae>") # Read template and replace placeholders TEMPLATE_FILE=".forgejo/release-template.md" if [[ ! -f "$TEMPLATE_FILE" ]]; then echo "Error: Template file not found: $TEMPLATE_FILE" exit 1 fi # Replace placeholders in template # Handle COMMITS separately due to multi-line content that can break sed # First, replace all single-line placeholders sed -e "s|{{VERSION}}|$VERSION|g" \ -e "s|{{IMAGE_NAME}}|$IMAGE_NAME|g" \ -e "s|{{COMMIT_HASH}}|$COMMIT_HASH|g" \ -e "s|{{COMMIT_SHORT}}|$COMMIT_SHORT|g" \ -e "s|{{BUILD_DATE}}|$BUILD_DATE|g" \ -e "s|{{COMMIT_AUTHOR}}|$COMMIT_AUTHOR|g" \ "$TEMPLATE_FILE" > /tmp/release_message_temp.txt # Replace COMMITS placeholder - use a while loop to handle multi-line safely if [[ -n "$COMMITS" ]]; then # Write COMMITS to a temp file and use it for replacement echo "$COMMITS" > /tmp/commits.txt # Use a simple approach: read template line by line and replace while IFS= read -r line; do if [[ "$line" == *"{{COMMITS}}"* ]]; then cat /tmp/commits.txt else echo "$line" fi done < /tmp/release_message_temp.txt > /tmp/release_message.txt else # If no commits, just remove the placeholder sed 's|{{COMMITS}}||g' /tmp/release_message_temp.txt > /tmp/release_message.txt fi echo "Release notes generated from template" - name: Create Git Tag run: | git config user.name "forgejo-actions" git config user.email "forgejo-actions@forgejo.io" TAG="${{ steps.version.outputs.tag }}" # Check if tag already exists if git rev-parse "$TAG" >/dev/null 2>&1; then echo "Tag $TAG already exists, skipping tag creation" echo "TAG_CREATED=false" >> $GITHUB_ENV else git tag -a "$TAG" -F /tmp/release_message.txt git push origin "$TAG" echo "Created tag $TAG with detailed release notes" echo "TAG_CREATED=true" >> $GITHUB_ENV fi - name: Update Version Files run: | VERSION="${{ steps.version.outputs.version }}" TAG="${{ steps.version.outputs.tag }}" echo "📝 Updating version files to: $VERSION" # Configure git git config user.name "forgebot" git config user.email "forgebot@forgejo.io" # Fetch latest changes to avoid conflicts git fetch origin main || echo "Fetch completed or already up to date" git checkout main || echo "Already on main" # Update VERSION file echo "$VERSION" > VERSION # Update portainer.yml with new version sed -i "s|\(image: git.jusemon.com/jusemon/threejs-test:\)[0-9.]*|\1$VERSION|" portainer.yml # Verify the updates if grep -q "^$VERSION$" VERSION && grep -q "image: git.jusemon.com/jusemon/threejs-test:$VERSION" portainer.yml; then echo "✅ Successfully updated VERSION and portainer.yml to $VERSION" else echo "❌ Failed to update version files" exit 1 fi # Check if there are changes to commit if git diff --quiet VERSION portainer.yml; then echo "ℹ️ No changes to commit (files already up to date)" else # Stage and commit with [skip ci] to prevent infinite loop # Note: Forgejo Actions should respect [skip ci] in commit messages git add VERSION portainer.yml git commit -m "chore: update version to $VERSION [skip ci]" || { echo "⚠️ Commit failed (may already be committed)" exit 0 } # Push to main branch (remote should already be configured with token) git push origin main || { echo "⚠️ Push failed (check token permissions)" exit 0 } echo "✅ Successfully committed and pushed version update to $VERSION" fi - name: Trigger Portainer Deployment if: success() run: | VERSION="${{ steps.version.outputs.version }}" WEBHOOK_URL="${{ secrets.PORTAINER_WEBHOOK_URL }}" if [[ -z "$WEBHOOK_URL" ]]; then echo "⚠️ Warning: PORTAINER_WEBHOOK_URL secret not set, skipping webhook call" exit 0 fi echo "🚀 Triggering Portainer deployment for version $VERSION" # Call Portainer webhook to trigger stack update HTTP_CODE=$(curl -s -o /tmp/webhook_response.txt -w "%{http_code}" -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ --max-time 30) if [[ "$HTTP_CODE" -ge 200 && "$HTTP_CODE" -lt 300 ]]; then echo "✅ Successfully triggered Portainer deployment (HTTP $HTTP_CODE)" if [[ -f /tmp/webhook_response.txt ]]; then echo "Response: $(cat /tmp/webhook_response.txt)" fi else echo "⚠️ Warning: Webhook call returned HTTP $HTTP_CODE" if [[ -f /tmp/webhook_response.txt ]]; then echo "Response: $(cat /tmp/webhook_response.txt)" fi # Don't fail the workflow if webhook fails exit 0 fi - name: Job Summary if: success() run: | SUMMARY_FILE="${FORGEJO_STEP_SUMMARY:-/dev/stdout}" VERSION="${{ steps.version.outputs.version }}" TAG="${{ steps.version.outputs.tag }}" IMAGE_NAME="git.jusemon.com/jusemon/threejs-test:$VERSION" TAG_STATUS="${TAG_CREATED:-false}" cat >> "$SUMMARY_FILE" << EOF ## 🚀 Release Published **Version:** \`$VERSION\` **Docker Image:** \`$IMAGE_NAME\` **Git Tag:** \`$TAG\` ### Published Images - ✅ \`$IMAGE_NAME\` - ✅ \`git.jusemon.com/jusemon/threejs-test:latest\` ### Git Tag EOF if [[ "$TAG_STATUS" == "true" ]]; then echo "- ✅ Created and pushed \`$TAG\` with release notes" >> "$SUMMARY_FILE" else echo "- ⚠️ Tag \`$TAG\` already exists, skipped creation" >> "$SUMMARY_FILE" fi cat >> "$SUMMARY_FILE" << EOF ### Version Files - ✅ VERSION file updated to \`$VERSION\` - ✅ portainer.yml updated to \`$VERSION\` ### Pull Command \`\`\`bash docker pull git.jusemon.com/jusemon/threejs-test:latest \`\`\` EOF