diff --git a/.forgejo/release-template.md b/.forgejo/release-template.md new file mode 100644 index 0000000..d1c2326 --- /dev/null +++ b/.forgejo/release-template.md @@ -0,0 +1,24 @@ +Release {{VERSION}} + +Docker Image: {{IMAGE_NAME}} +Commit: {{COMMIT_SHORT}} ({{COMMIT_HASH}}) +Build Date: {{BUILD_DATE}} +Author: {{COMMIT_AUTHOR}} + +## Changes + +{{COMMITS}} + +## Docker Image + +``` +docker pull {{IMAGE_NAME}} +``` + +## Deployment + +The image is also available as `latest`: +``` +docker pull git.jusemon.com/jusemon/threejs-test:latest +``` + diff --git a/.forgejo/workflows/build-and-validate.yaml b/.forgejo/workflows/build-and-validate.yaml new file mode 100644 index 0000000..eb9f679 --- /dev/null +++ b/.forgejo/workflows/build-and-validate.yaml @@ -0,0 +1,53 @@ +name: Build and Publish Docker Image + +on: + pull_request: + branches: + - main + +jobs: + build-and-validate: + name: Build and Validate + runs-on: ubuntu + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate PR Title + 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 + cat "$SUMMARY_FILE" \ No newline at end of file diff --git a/.forgejo/workflows/publish-and-deploy.yaml b/.forgejo/workflows/publish-and-deploy.yaml new file mode 100644 index 0000000..01c787b --- /dev/null +++ b/.forgejo/workflows/publish-and-deploy.yaml @@ -0,0 +1,336 @@ +name: Build and Publish Docker Image + +on: + push: + branches: + - main + +jobs: + publish: + name: Publish to Registry + runs-on: ubuntu + 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: 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)" + echo "VERSION_COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV + 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 + } + + # Get the commit hash of the version update commit + VERSION_COMMIT_HASH=$(git rev-parse HEAD) + echo "VERSION_COMMIT_HASH=$VERSION_COMMIT_HASH" >> $GITHUB_ENV + echo "✅ Successfully committed and pushed version update to $VERSION (commit: $VERSION_COMMIT_HASH)" + fi + + - 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" + # Use the version update commit hash (which has the correct version in files) + COMMIT_HASH="${VERSION_COMMIT_HASH:-$(git rev-parse HEAD)}" + COMMIT_SHORT=$(git rev-parse --short "$COMMIT_HASH") + BUILD_DATE=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + + # Get commits since last tag, excluding version update commits + LATEST_TAG=$(git describe --tags --match 'v*.*.*' --abbrev=0 2>/dev/null || echo "") + if [[ -n "$LATEST_TAG" ]]; then + # Get commits excluding "chore: update version" commits (from previous releases) + # We want commits from the tag to the version update commit (exclusive of the version commit itself) + COMMITS=$(git log ${LATEST_TAG}..${COMMIT_HASH}^ --pretty=format:"- %s (%h)" --no-merges | grep -v "chore: update version") + else + # Get commits excluding version update commits + COMMITS=$(git log ${COMMIT_HASH}^ --pretty=format:"- %s (%h)" --no-merges -10 | grep -v "chore: update version") + fi + + # Get author of the version update commit (or HEAD if no version commit) + COMMIT_AUTHOR=$(git log -1 --pretty=format:"%an <%ae>" "$COMMIT_HASH") + + # 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 }}" + # Use the version update commit hash (which has the correct version in files) + VERSION_COMMIT="${VERSION_COMMIT_HASH:-$(git rev-parse HEAD)}" + + # 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 + # Create tag pointing to the version update commit (which has correct VERSION file) + git tag -a "$TAG" -F /tmp/release_message.txt "$VERSION_COMMIT" + git push origin "$TAG" + echo "Created tag $TAG pointing to version update commit $VERSION_COMMIT with detailed release notes" + echo "TAG_CREATED=true" >> $GITHUB_ENV + 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 + cat "$SUMMARY_FILE" + + deploy: + name: Deploy to Portainer + runs-on: ubuntu + needs: publish + steps: + - name: Trigger Portainer Deployment + run: | + 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 latest 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 diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0