diff --git a/.github/actions/build-push-info/action.yml b/.github/actions/build-push-info/action.yml new file mode 100644 index 0000000..1240f8b --- /dev/null +++ b/.github/actions/build-push-info/action.yml @@ -0,0 +1,78 @@ +name: build-push-info + +description: Get docker tags and other version related infos + +inputs: + qbt_tag: + description: "qBittorrent release tag" + required: false + docker_tag: + description: "docker tag" + required: false + +outputs: + qbt_release_tag: + description: "The qBittorrent release tag" + value: ${{ steps.qbt-release.outputs.release_tag }} + qbt_version_number: + description: "The extracted version number from the release tag" + value: ${{ steps.qbt-release.outputs.version_number }} + date: + description: "Current UTC date in YYYYMMDD format" + value: ${{ steps.env-info.outputs.date }} + short_sha: + description: "Short SHA of the current commit" + value: ${{ steps.env-info.outputs.short_sha }} + tags: + description: "Docker tags to publish" + value: ${{ steps.docker-tags.outputs.tags }} + +runs: + using: "composite" + steps: + - name: Get latest release or use provided tag + id: qbt-release + uses: ./.github/actions/get-qbt-release + with: + qbt_tag: ${{ inputs.qbt_tag }} + + - name: Setup additional environment variables + id: env-info + shell: bash + run: | + echo "date=$(date -u +%Y%m%d)" | tee -a $GITHUB_OUTPUT + echo "short_sha=$(git rev-parse --short HEAD)" | tee -a $GITHUB_OUTPUT + + - name: Set up Docker Tags + id: docker-tags + shell: bash + run: | + GHCR_REPO="ghcr.io/trigus42/alpine-qbittorrentvpn" + DOCKERHUB_REPO="trigus42/qbittorrentvpn" + + if [[ -n "${{ inputs.docker_tag }}" ]]; then + TAGS_NAMES=( + ${{ inputs.docker_tag }} + ) + elif [ "${{ github.ref_name }}" == "master" ]; then + TAGS_NAMES=( + "latest" + "qbt${{ steps.qbt-release.outputs.version_number }}" + "qbt${{ steps.qbt-release.outputs.version_number }}-${{ steps.env-info.outputs.date }}" + ) + fi + + TAGS_NAMES+=( + "${{ github.head_ref || github.ref_name }}" + "${{ github.sha }}" + "${{ github.sha }}-qbt${{ steps.qbt-release.outputs.version_number }}" + ) + + # Prepare the tags for both repositories + DOCKER_TAGS=() + for tag in "${TAGS_NAMES[@]}"; do + DOCKER_TAGS+=("$GHCR_REPO:$tag") + DOCKER_TAGS+=("$DOCKERHUB_REPO:$tag") + done + + bash .github/helper/setOutput.sh "tags" "$(IFS=$'\n'; echo "${DOCKER_TAGS[*]}")" diff --git a/.github/actions/docker-setup/action.yml b/.github/actions/docker-setup/action.yml new file mode 100644 index 0000000..e91cad5 --- /dev/null +++ b/.github/actions/docker-setup/action.yml @@ -0,0 +1,46 @@ +name: docker-setup + +description: Setup Docker environment for QEMU, Buildx, and registry logins + +inputs: + login_enabled: + type: boolean + description: "Enable or disable all logins" + default: true + required: false + github_token: + type: string + description: "Set to enable GitHub Container Registry login" + required: false + dockerhub_username: + type: string + description: "Set to enable DockerHub login" + required: false + dockerhub_token: + type: string + description: "Set to enable DockerHub login" + required: false + +runs: + using: "composite" + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + if: ${{ inputs.login_enabled && inputs.github_token }} + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ inputs.github_token }} + + - name: Login to Docker Hub + if: ${{ inputs.login_enabled && inputs.dockerhub_username && inputs.dockerhub_token }} + uses: docker/login-action@v3 + with: + username: ${{ inputs.dockerhub_username }} + password: ${{ inputs.dockerhub_token }} diff --git a/.github/actions/get-qbt-release/action.yml b/.github/actions/get-qbt-release/action.yml new file mode 100644 index 0000000..014fd8f --- /dev/null +++ b/.github/actions/get-qbt-release/action.yml @@ -0,0 +1,32 @@ +name: get-qbt-release + +description: Get latest qbt release tag + +inputs: + qbt_tag: + description: "qBittorrent release tag" + required: false + +outputs: + release_tag: + description: "The qBittorrent release tag" + value: ${{ steps.qbt-release.outputs.release_tag }} + version_number: + description: "The extracted version number from the release tag" + value: ${{ steps.qbt-release.outputs.version_number }} + +runs: + using: "composite" + steps: + - name: Get latest release or use provided tag + id: qbt-release + shell: bash + run: | + if [ -z "${{ inputs.qbt_tag }}"]; then + echo "No qBittorrent tag provided, fetching latest release..." + RELEASE_TAG=$(curl -s https://api.github.com/repos/userdocs/qbittorrent-nox-static/releases/latest | jq -r '.tag_name') + else + RELEASE_TAG=${{ inputs.qbt_tag }} + fi + echo "release_tag=$RELEASE_TAG" | tee -a $GITHUB_OUTPUT + echo "version_number=$(echo $RELEASE_TAG | grep -P "\d(\.\d+)+" -o | head -n 1)" | tee -a $GITHUB_OUTPUT diff --git a/.github/actions/sign-docker-image/action.yml b/.github/actions/sign-docker-image/action.yml new file mode 100644 index 0000000..99e22e5 --- /dev/null +++ b/.github/actions/sign-docker-image/action.yml @@ -0,0 +1,28 @@ +name: sign-docker-image + +description: Sign the Docker image using cosign + +inputs: + digest: + type: string + description: "The image digest to sign" + required: true + tags: + type: string + description: "The image tags to sign" + required: true + +runs: + using: "composite" + steps: + - name: Install cosign + uses: sigstore/cosign-installer@v3 + with: + cosign-release: 'v2.2.4' + + - name: Sign Docker image + env: + COSIGN_EXPERIMENTAL: "true" + shell: bash + run: | + echo "${{ inputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ inputs.digest }} diff --git a/.github/helper/setOutput.sh b/.github/helper/setOutput.sh new file mode 100644 index 0000000..6fbdfef --- /dev/null +++ b/.github/helper/setOutput.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Description: This script is used to set the output for the GitHub action. +# It writes the output to the file specified by the GITHUB_OUTPUT environment variable. +# The output is formatted as name<> "$filePath" +echo "${value}" >> "$filePath" +echo "${delimiter}" >> "$filePath" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..2d45225 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,117 @@ +name: Build Docker Image + +on: + push: + branches: [ "master" ] + tags: [ 'v*.*.*' ] + paths: + - '.github/workflows/**' + - 'rootfs/**' + - 'build/**' + - 'Dockerfile' + pull_request: + branches: [ "master" ] + paths: + - '.github/workflows/**' + - 'rootfs/**' + - 'build/**' + - 'Dockerfile' + workflow_dispatch: + inputs: + push: + description: 'Push image to registry' + required: false + default: false + type: boolean + qbt_tag: + description: 'qBittorrent tag' + required: false + type: string + docker_tag: + description: 'Tag for the docker image' + required: false + type: string + commit_sha: + description: 'Commit SHA to checkout. Requires docker_tag.' + required: false + type: string + +jobs: + build-and-sign: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Evaluate run triggers + id: triggers + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.push }}" != "true" ]]; then + echo "Workflow was manually triggered, but push is disabled. Skipping push." + elif [[ -n "${{ inputs.commit_sha }}" && -z "${{ inputs.docker_tag }}" ]]; then + echo "commit_sha is set, but docker_tag isn't. Deploying specific commit to default tag is not allowed. Please provide a docker_tag." + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "Pull request event detected. Skipping push." + else + echo "do_push=true" >> $GITHUB_OUTPUT + fi + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get version infos + id: version_info + uses: ./.github/actions/build-push-info + with: + qbt_tag: ${{ inputs.qbt_tag }} + docker_tag: ${{ inputs.docker_tag }} + + - name: Setup Docker + uses: ./.github/actions/docker-setup + with: + login_enabled: ${{ steps.triggers.outputs.do_push == 'true' }} + github_token: ${{ secrets.GITHUB_TOKEN }} + dockerhub_username: ${{ vars.DOCKERHUB_USERNAME }} + dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/${{ github.repository }} + trigus42/qbittorrentvpn + + - name: Checkout custom commit before building + uses: actions/checkout@v4 + if: ${{ inputs.commit_sha }} + with: + ref: ${{ inputs.commit_sha }} + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 + push: ${{ steps.triggers.outputs.do_push == 'true' }} + build-args: | + "SOURCE_COMMIT=${{ steps.version_info.outputs.short_sha }}" + "QBITTORRENT_TAG=${{ steps.version_info.outputs.qbt_release_tag }}" + tags: ${{ steps.version_info.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Checkout back to current commit + if: ${{ inputs.commit_sha }} + run: git checkout ${{ github.sha }} + + - name: Sign Docker image + if: ${{ steps.triggers.outputs.do_push == 'true' }} + uses: ./.github/actions/sign-docker-image + with: + digest: ${{ steps.build.outputs.digest }} + tags: ${{ steps.version_info.outputs.tags }} diff --git a/.github/workflows/check_update.yml b/.github/workflows/check-update.yml similarity index 54% rename from .github/workflows/check_update.yml rename to .github/workflows/check-update.yml index 7c9b7a2..76f5fbe 100644 --- a/.github/workflows/check_update.yml +++ b/.github/workflows/check-update.yml @@ -1,4 +1,4 @@ -name: Check for qBT update +name: Check for qBt update on: schedule: @@ -8,8 +8,9 @@ on: jobs: check: runs-on: ubuntu-latest - steps: + - name: Checkout repository + uses: actions/checkout@v4 - name: Download previous release info id: download-artifact @@ -21,30 +22,25 @@ jobs: if_no_artifact_found: warn - name: Get latest release - id: get_release - run: | - # Fetch release information and extract the release tag - RELEASE_TAG=$(curl -s https://api.github.com/repos/userdocs/qbittorrent-nox-static/releases/latest | jq -r '.tag_name') - - echo "RELEASE_TAG=$RELEASE_TAG" | tee -a $GITHUB_ENV - echo "VERSION_NUMBER=$(echo $RELEASE_TAG | grep -P "\d(\.\d+)+" -o | head -n 1)" | tee -a $GITHUB_ENV + id: get-qbt-release + uses: ./.github/actions/get-qbt-release - name: Compare with previous release - id: compare_release + id: compare-release run: | # Read the release info from the downloaded artifact PREVIOUS_RELEASE=$(cat qbt-release-info 2> /dev/null || echo "NONE") echo "PREVIOUS_RELEASE=$PREVIOUS_RELEASE" # Compare the fetched release tag with the previous release tag - if [ "${{ env.RELEASE_TAG }}" != "$PREVIOUS_RELEASE" ]; then - echo "RELEASE_CHANGED=true" | tee -a $GITHUB_ENV + if [ "${{ steps.get-qbt-release.outputs.release_tag }}" == "$PREVIOUS_RELEASE" ]; then + echo release_changed=false | tee -a $GITHUB_OUTPUT else - echo "RELEASE_CHANGED=false" | tee -a $GITHUB_ENV + echo release_changed=true | tee -a $GITHUB_OUTPUT fi - name: Call workflow to build docker image - if: env.RELEASE_CHANGED == 'true' + if: ${{ steps.compare-release.outputs.release_changed == 'true' }} uses: benc-uk/workflow-dispatch@v1 with: - workflow: publish.yml + workflow: build.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index a07b520..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,171 +0,0 @@ -name: Build docker image - -on: - push: - branches: [ "master" ] - # Publish semver tags as releases. - tags: - - 'v*.*.*' - paths: - - '.github/workflows**' - - 'rootfs/**' - - 'build/**' - - 'Dockerfile' - pull_request: - branches: - - 'master' - paths: - - '.github/workflows**' - - 'rootfs/**' - - 'build/**' - - 'Dockerfile' - workflow_dispatch: - - -env: - # github.repository as / - IMAGE_NAME: ${{ github.repository }} - - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Install the cosign tool except on PR - - name: Install cosign - if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.2.4' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Docker Hub - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ghcr.io/${{ env.IMAGE_NAME }} - trigus42/qbittorrentvpn - - - name: Get latest release - run: | - # Fetch release information and extract the release tag - RELEASE_TAG=$(curl -s https://api.github.com/repos/userdocs/qbittorrent-nox-static/releases/latest | jq -r '.tag_name') - - echo "RELEASE_TAG=$RELEASE_TAG" | tee -a $GITHUB_ENV - echo "VERSION_NUMBER=$(echo $RELEASE_TAG | grep -P "\d(\.\d+)+" -o | head -n 1)" | tee -a $GITHUB_ENV - - - name: Setup vars - run: | - echo "DATE=$(date -u +%Y%m%d)" | tee -a $GITHUB_ENV - echo "SHORT_SHA=$(git rev-parse --short HEAD)" | tee -a $GITHUB_ENV - - # Build and push Docker image with Buildx (don't push on PR) - - name: Build and push Docker image - if: github.event_name != 'pull_request' && github.ref_name == 'master' - id: build-and-push-master - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - build-args: | - "SOURCE_COMMIT=${{ env.SHORT_SHA }}" - "QBITTORRENT_TAG=${{ env.RELEASE_TAG }}" - tags: | - ghcr.io/trigus42/alpine-qbittorrentvpn:latest - ghcr.io/trigus42/alpine-qbittorrentvpn:${{ github.head_ref || github.ref_name }} - ghcr.io/trigus42/alpine-qbittorrentvpn:${{ github.sha }} - ghcr.io/trigus42/alpine-qbittorrentvpn:qbt${{ env.VERSION_NUMBER }} - ghcr.io/trigus42/alpine-qbittorrentvpn:${{ github.sha }}-qbt${{ env.VERSION_NUMBER }} - ghcr.io/trigus42/alpine-qbittorrentvpn:qbt${{ env.VERSION_NUMBER }}-${{ env.DATE }} - trigus42/qbittorrentvpn:latest - trigus42/qbittorrentvpn:${{ github.head_ref || github.ref_name }} - trigus42/qbittorrentvpn:${{ github.sha }} - trigus42/qbittorrentvpn:qbt${{ env.VERSION_NUMBER }} - trigus42/qbittorrentvpn:${{ github.sha }}-qbt${{ env.VERSION_NUMBER }} - trigus42/qbittorrentvpn:qbt${{ env.VERSION_NUMBER }}-${{ env.DATE }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Build and push Docker image with Buildx (don't push on PR) - - name: Build and push Docker image - if: github.ref_name != 'master' - id: build-and-push - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - build-args: | - "SOURCE_COMMIT=${{ env.SHORT_SHA }}" - "QBITTORRENT_TAG=${{ env.RELEASE_TAG }}" - tags: | - ghcr.io/trigus42/alpine-qbittorrentvpn:${{ github.head_ref || github.ref_name }} - ghcr.io/trigus42/alpine-qbittorrentvpn:${{ github.head_ref || github.ref_name }}-${{ github.sha }} - trigus42/qbittorrentvpn:${{ github.head_ref || github.ref_name }} - trigus42/qbittorrentvpn:${{ github.head_ref || github.ref_name }}-${{ github.sha }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Sign the resulting Docker image digest except on PRs. - - name: Sign the published Docker image - if: ${{ github.event_name != 'pull_request' }} - env: - COSIGN_EXPERIMENTAL: "true" - # This step uses the identity token to provision an ephemeral certificate - # against the sigstore community Fulcio instance. - run: | - if [ -n "${{ steps.build-and-push-master.outputs.digest }}" ]; then - echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-and-push-master.outputs.digest }} - else - echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-and-push.outputs.digest }} - fi - - # For use in check_update workflow - - name: Store artifacts - if: github.event_name != 'pull_request' && github.ref_name == 'master' - run: | - # Store the newly fetched release version in a file - echo "${{ env.RELEASE_TAG }}" > qbt-release-info - echo "Saved ${{ env.RELEASE_TAG }} to qbt-release-info" - - - name: Upload new artifacts - if: github.event_name != 'pull_request' && github.ref_name == 'master' - uses: actions/upload-artifact@v4 - with: - name: qbt-release-info - path: qbt-release-info