diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..612c8b86 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,88 @@ +name: CI Pipeline + +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + +# Add explicit permissions for the workflow +permissions: + contents: read + packages: write # For publishing to GitHub Container Registry + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Update build number + run: | + python scripts/increment_build.py + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version info + id: version + run: | + VERSION=$(python -c "import version; print(version.__version__)") + BUILD=$(python -c "import version; print(version.__build__)") + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "build=${BUILD}" >> $GITHUB_OUTPUT + echo "sha_short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - name: Set repository and image metadata + id: meta + run: | + # Get lowercase repository owner + REPO_OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + echo "repo_owner=${REPO_OWNER}" >> $GITHUB_OUTPUT + + # Get repository name + REPO_NAME=$(echo "${{ github.repository }}" | cut -d '/' -f 2 | tr '[:upper:]' '[:lower:]') + echo "repo_name=${REPO_NAME}" >> $GITHUB_OUTPUT + + # Determine branch name + if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + echo "branch_tag=latest" >> $GITHUB_OUTPUT + echo "is_main=true" >> $GITHUB_OUTPUT + elif [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then + echo "branch_tag=dev" >> $GITHUB_OUTPUT + echo "is_main=false" >> $GITHUB_OUTPUT + else + # For other branches, use the branch name + BRANCH=$(echo "${{ github.ref }}" | sed 's/refs\/heads\///' | sed 's/[^a-zA-Z0-9]/-/g') + echo "branch_tag=${BRANCH}" >> $GITHUB_OUTPUT + echo "is_main=false" >> $GITHUB_OUTPUT + fi + + # Determine if this is from a fork + if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then + echo "is_fork=true" >> $GITHUB_OUTPUT + else + echo "is_fork=false" >> $GITHUB_OUTPUT + fi + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64 # Fast build - amd64 only + tags: | + ghcr.io/${{ steps.meta.outputs.repo_owner }}/${{ steps.meta.outputs.repo_name }}:${{ steps.meta.outputs.branch_tag }} + ghcr.io/${{ steps.meta.outputs.repo_owner }}/${{ steps.meta.outputs.repo_name }}:${{ steps.version.outputs.version }}-${{ steps.version.outputs.build }} + ghcr.io/${{ steps.meta.outputs.repo_owner }}/${{ steps.meta.outputs.repo_name }}:${{ steps.version.outputs.sha_short }} + build-args: | + BRANCH=${{ github.ref_name }} + REPO_URL=https://github.com/${{ github.repository }} + file: ./docker/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..6fb7187b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,93 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + version_type: + description: 'Type of version increment' + required: true + default: 'patch' + type: choice + options: + - major + - minor + - patch + +# Add explicit permissions for the workflow +permissions: + contents: write # For managing releases and pushing tags + packages: write # For publishing to GitHub Container Registry + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + - name: Update Version + id: update_version + run: | + python scripts/bump_version.py ${{ github.event.inputs.version_type }} + NEW_VERSION=$(python -c "import version; print(f'{version.__version__}')") + echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT + + - name: Set lowercase repo owner + id: repo_owner + run: | + REPO_OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + echo "lowercase=${REPO_OWNER}" >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Commit and Tag + run: | + git add version.py + git commit -m "Release v${{ steps.update_version.outputs.new_version }}" + git tag -a "v${{ steps.update_version.outputs.new_version }}" -m "Release v${{ steps.update_version.outputs.new_version }}" + git push origin main --tags + + - name: Build and Push Release Image + uses: docker/build-push-action@v4 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64, #linux/arm/v7 # Multi-arch support for releases + tags: | + ghcr.io/${{ steps.repo_owner.outputs.lowercase }}/dispatcharr:latest + ghcr.io/${{ steps.repo_owner.outputs.lowercase }}/dispatcharr:${{ steps.update_version.outputs.new_version }} + ghcr.io/${{ steps.repo_owner.outputs.lowercase }}/dispatcharr:latest-amd64 + ghcr.io/${{ steps.repo_owner.outputs.lowercase }}/dispatcharr:latest-arm64 + ghcr.io/${{ steps.repo_owner.outputs.lowercase }}/dispatcharr:${{ steps.update_version.outputs.new_version }}-amd64 + ghcr.io/${{ steps.repo_owner.outputs.lowercase }}/dispatcharr:${{ steps.update_version.outputs.new_version }}-arm64 + ghcr.io/${{ steps.repo_owner.outputs.lowercase }}/dispatcharr:${{ steps.update_version.outputs.new_version }}-armv7 + build-args: | + BRANCH=${{ github.ref_name }} + REPO_URL=https://github.com/${{ github.repository }} + file: ./docker/Dockerfile + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.update_version.outputs.new_version }} + name: Release v${{ steps.update_version.outputs.new_version }} + draft: false + prerelease: false + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docker/build-dev.sh b/docker/build-dev.sh index c5c79474..65d643a7 100755 --- a/docker/build-dev.sh +++ b/docker/build-dev.sh @@ -1,3 +1,13 @@ #!/bin/bash - docker build --build-arg BRANCH=dev -t dispatcharr/dispatcharr:dev -f Dockerfile .. + +# Get version information +VERSION=$(python -c "import sys; sys.path.append('..'); import version; print(version.__version__)") +BUILD=$(python -c "import sys; sys.path.append('..'); import version; print(version.__build__)") + +# Build with version tags +docker build --build-arg BRANCH=dev \ + -t dispatcharr/dispatcharr:dev \ + -t dispatcharr/dispatcharr:${VERSION}-${BUILD} \ + -f Dockerfile .. +. diff --git a/scripts/bump_version.py b/scripts/bump_version.py new file mode 100644 index 00000000..59c98117 --- /dev/null +++ b/scripts/bump_version.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +""" +Bumps the version number according to semantic versioning. +Usage: python bump_version.py [major|minor|patch] +""" +import re +import sys +from pathlib import Path + +def bump_version(version_type='patch'): + version_file = Path(__file__).parent.parent / "version.py" + content = version_file.read_text() + + # Extract version + version_match = re.search(r"__version__ = '(\d+)\.(\d+)\.(\d+)'", content) + if not version_match: + print("Could not find version number in version.py") + return + + major, minor, patch = map(int, version_match.groups()) + + # Update version based on type + if version_type == 'major': + major += 1 + minor = 0 + patch = 0 + elif version_type == 'minor': + minor += 1 + patch = 0 + else: # patch + patch += 1 + + new_version = f"{major}.{minor}.{patch}" + + # Update version in file + new_content = re.sub( + r"__version__ = '\d+\.\d+\.\d+'", + f"__version__ = '{new_version}'", + content + ) + + # Reset build number + new_content = re.sub( + r"__build__ = '\d+'", + "__build__ = '0'", + new_content + ) + + version_file.write_text(new_content) + print(f"Version bumped to {new_version}") + return new_version + +if __name__ == "__main__": + version_type = 'patch' + if len(sys.argv) > 1: + version_type = sys.argv[1].lower() + + if version_type not in ('major', 'minor', 'patch'): + print("Invalid version type. Use major, minor, or patch.") + sys.exit(1) + + bump_version(version_type) diff --git a/scripts/increment_build.py b/scripts/increment_build.py new file mode 100644 index 00000000..091a4778 --- /dev/null +++ b/scripts/increment_build.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +""" +Increments the build number in version.py +""" +import re +import os +from pathlib import Path + +def increment_build(): + version_file = Path(__file__).parent.parent / "version.py" + content = version_file.read_text() + + # Extract build number + build_match = re.search(r"__build__ = '(\d+)'", content) + if not build_match: + print("Could not find build number in version.py") + return + + build = int(build_match.group(1)) + new_build = str(build + 1) + + # Update build number + new_content = re.sub( + r"__build__ = '\d+'", + f"__build__ = '{new_build}'", + content + ) + + version_file.write_text(new_content) + print(f"Build number incremented to {new_build}") + +if __name__ == "__main__": + increment_build() diff --git a/version.py b/version.py new file mode 100644 index 00000000..c865d13b --- /dev/null +++ b/version.py @@ -0,0 +1,5 @@ +""" +Dispatcharr version information. +""" +__version__ = '0.1.0' # Follow semantic versioning (MAJOR.MINOR.PATCH) +__build__ = '0' # Auto-incremented on builds