diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 3e4c64e..f9c6aac 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -21,6 +21,14 @@ on: release_name: required: false type: string + sign_image: + required: false + type: boolean + default: true + branch_name: + required: false + type: string + default: "main" secrets: OP_SERVICE_ACCOUNT_TOKEN: required: true @@ -32,7 +40,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.head_ref || inputs.branch_name }} - name: Docker meta id: docker_meta @@ -44,24 +54,28 @@ jobs: org.opencontainers.image.title=${{ inputs.app_name }} org.opencontainers.image.description=${{ inputs.app_name }} org.opencontainers.image.vendor=${{ inputs.repo_owner }} + org.opencontainers.image.documentation=https://github.com/linuxserver-labs/${{ inputs.app_name }} + flavor: | + latest=false tags: | - type=schedule type=ref,event=branch type=ref,event=tag type=ref,event=pr - type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=${{ github.head_ref || inputs.branch_name }},enable=true + type=raw,value=${{ github.head_ref || inputs.branch_name }},enable=true - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@v4.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.12.0 + uses: docker/setup-buildx-action@v4.0.0 - name: Install Cosign - uses: sigstore/cosign-installer@v3.10.0 + if: ${{ inputs.sign_image == 'true' }} + uses: sigstore/cosign-installer@v4.1.1 - name: Login to GitHub Container Registry - uses: docker/login-action@v3.6.0 + uses: docker/login-action@v4.1.0 with: registry: ghcr.io username: ${{ inputs.repo_owner }} @@ -78,18 +92,18 @@ jobs: id: gen_release run: | if [ -z ${{ github.event.release.tag_name }} ]; then - IMAGE_VERSION=$(curl -s "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | jq -r '(sort_by(.published_at) | .[-1].tag_name)?') + IMAGE_VERSION=$(curl -s "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | jq -r 'last((sort_by(.published_at) | .[] | select(.target_commitish == "${{ inputs.branch_name }}") | .tag_name)?)') if [ -z $IMAGE_VERSION ] || [ $IMAGE_VERSION == null ]; then case ${{ inputs.release_type }} in github) IMAGE_VERSION=$(curl -sX GET "${{ inputs.release_url }}/releases/latest" | awk '/tag_name/{print $4;exit}' FS='[""]'); ;; - github_tag) - IMAGE_VERSION=$(curl -sX GET "${{ inputs.release_url }}/tags" | jq -r 'first(.[] | select(.name | contains("${{ inputs.release_name }}") )) | .name'); - ;; github_commit) IMAGE_VERSION=$(curl -sL "${{ inputs.release_url }}" | jq -r 'first(.[])' | cut -c1-8); ;; + github_tag) + IMAGE_VERSION=$(curl -sX GET "${{ inputs.release_url }}/tags" | jq -r 'first(.[] | select(.name | contains("${{ inputs.release_name }}") )) | .name'); + ;; alpine) IMAGE_VERSION=$(curl -sL "http://dl-cdn.alpinelinux.org/alpine/${{ inputs.release_url }}/x86_64/APKINDEX.tar.gz" | tar -xz -C /tmp && awk '/^P:'"${{ inputs.release_name }}"'$/,/V:/' /tmp/APKINDEX | sed -n 2p | sed 's/^V://'); ;; @@ -110,7 +124,7 @@ jobs: - name: Build and push id: build_push - uses: docker/bake-action@v6.10.0 + uses: docker/bake-action@v7.0.0 with: files: | ./docker-bake.hcl @@ -123,9 +137,55 @@ jobs: push: true provenance: false sbom: true + - + name: Create manifest for release + if: ${{ github.event_name != 'pull_request' }} + id: manifest + run: | + TAG=$(jq -r '.tags[0]' <<< $TAGS | cut -f 2- -d ':') + for registry in ghcr.io; do + if [[ ${{ github.event.repository.default_branch }} == ${{ inputs.branch_name }} ]]; then + docker buildx imagetools create -t ${registry}/${MANIFESTIMAGE}:latest ${registry}/${MANIFESTIMAGE}:${TAG} + fi + done + env: + MANIFESTIMAGE: ${{ inputs.repo_owner }}/${{ inputs.app_name }} + TAGS: ${{ steps.docker_meta.outputs.json }} + - + name: Commit release version + if: ${{ github.event_name != 'pull_request' }} + id: commit_release + env: + CI_COMMIT_MESSAGE: CI Build Workflow Updates + run: | + FILE_BASE64=$(base64 <<< "${{ steps.gen_release.outputs.app_version }}") + FILE_BLOB=$(curl -L \ + -H "Accept: application/vnd.github.object" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/contents/version.txt?ref=${{ inputs.branch_name }}) + if jq -re .sha <<< ${FILE_BLOB} 2> /dev/null; then + FILE_SHA=$(jq -r .sha <<< ${FILE_BLOB}) + curl -L \ + -X PUT \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/contents/version.txt \ + -d "{\"branch\":\"${{ inputs.branch_name }}\",\"sha\":\"${FILE_SHA}\",\"message\":\"${{ env.CI_COMMIT_MESSAGE }}\",\"content\":\"${FILE_BASE64}\"}" + else + curl -L \ + -X PUT \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/contents/version.txt \ + -d "{\"branch\":\"${{ inputs.branch_name }}\",\"message\":\"${{ env.CI_COMMIT_MESSAGE }}\",\"content\":\"${FILE_BASE64}\"}" + fi - name: Get Digest id: get_digest + if: ${{ inputs.sign_image == 'true' }} env: BAKE_METADATA: ${{ steps.build_push.outputs.metadata }} run: | @@ -135,7 +195,8 @@ jobs: - name: Load Key id: op-load-key - uses: 1password/load-secrets-action@v3 + if: ${{ inputs.sign_image == 'true' }} + uses: 1password/load-secrets-action@v4.0.0 with: export-env: true env: @@ -144,6 +205,7 @@ jobs: COSIGN_PASSWORD: op://Labs/labs-sigstore-pass/password - name: Sign image with a key + if: ${{ inputs.sign_image == 'true' }} run: | images="" for tag in ${TAGS}; do diff --git a/.github/workflows/build-split-image.yml b/.github/workflows/build-split-image.yml index 6abeffd..92a0b8d 100644 --- a/.github/workflows/build-split-image.yml +++ b/.github/workflows/build-split-image.yml @@ -21,6 +21,14 @@ on: release_name: required: false type: string + sign_image: + required: false + type: boolean + default: true + branch_name: + required: false + type: string + default: "main" secrets: OP_SERVICE_ACCOUNT_TOKEN: required: true @@ -35,14 +43,18 @@ jobs: outputs: meta-amd64: ${{ steps.dump_tags.outputs.meta-amd64 }} meta-arm64v8: ${{ steps.dump_tags.outputs.meta-arm64v8 }} + meta-riscv64: ${{ steps.dump_tags.outputs.meta-riscv64 }} + app_version: ${{ steps.gen_release.outputs.app_version }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.head_ref || inputs.branch_name }} - name: Docker meta id: docker_meta - uses: docker/metadata-action@v5.10.0 + uses: docker/metadata-action@v6.0.0 with: images: | ghcr.io/${{ inputs.repo_owner }}/${{ inputs.app_name }} @@ -50,23 +62,25 @@ jobs: org.opencontainers.image.title=${{ inputs.app_name }} org.opencontainers.image.description=${{ inputs.app_name }} org.opencontainers.image.vendor=${{ inputs.repo_owner }} + org.opencontainers.image.documentation=https://github.com/linuxserver-labs/${{ inputs.app_name }} flavor: | + latest=false prefix=${{ matrix.arch }}-,onlatest=true tags: | - type=schedule type=ref,event=branch type=ref,event=tag type=ref,event=pr,prefix=${{ matrix.arch }}-pr-,enable=true,priority=600 - type=raw,value=latest,enable={{is_default_branch}} + type=raw,prefix=${{ matrix.arch }}-,value=${{ github.head_ref || inputs.branch_name }},enable=true + type=raw,prefix=${{ matrix.arch }}-,value=${{ github.head_ref || inputs.branch_name }},enable=true - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@v4.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.12.0 + uses: docker/setup-buildx-action@v4.0.0 - name: Login to GitHub Container Registry - uses: docker/login-action@v3.6.0 + uses: docker/login-action@v4.1.0 with: registry: ghcr.io username: ${{ inputs.repo_owner }} @@ -83,18 +97,18 @@ jobs: id: gen_release run: | if [ -z ${{ github.event.release.tag_name }} ]; then - IMAGE_VERSION=$(curl -s "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | jq -r '(sort_by(.published_at) | .[-1].tag_name)?') - if [ -z $IMAGE_VERSION ] || [ $IMAGE_VERSION == null ]; then + IMAGE_VERSION=$(curl -s "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | jq -r 'last((sort_by(.published_at) | .[] | select(.target_commitish == "${{ inputs.branch_name }}") | .tag_name)?)') + if [ -z ${IMAGE_VERSION} ] || [ ${IMAGE_VERSION} == null ]; then case ${{ inputs.release_type }} in github) IMAGE_VERSION=$(curl -sX GET "${{ inputs.release_url }}/releases/latest" | awk '/tag_name/{print $4;exit}' FS='[""]'); ;; - github_tag) - IMAGE_VERSION=$(curl -sX GET "${{ inputs.release_url }}/tags" | jq -r 'first(.[] | select(.name | contains("${{ inputs.release_name }}") )) | .name'); - ;; github_commit) IMAGE_VERSION=$(curl -sL "${{ inputs.release_url }}" | jq -r 'first(.[])' | cut -c1-8); ;; + github_tag) + IMAGE_VERSION=$(curl -sX GET "${{ inputs.release_url }}/tags" | jq -r 'first(.[] | select(.name | contains("${{ inputs.release_name }}") )) | .name'); + ;; alpine) IMAGE_VERSION=$(curl -sL "http://dl-cdn.alpinelinux.org/alpine/${{ inputs.release_url }}/x86_64/APKINDEX.tar.gz" | tar -xz -C /tmp && awk '/^P:'"${{ inputs.release_name }}"'$/,/V:/' /tmp/APKINDEX | sed -n 2p | sed 's/^V://'); ;; @@ -114,8 +128,7 @@ jobs: echo "app_version=${APP_VERSION}" >> $GITHUB_OUTPUT - name: Build and push - id: build_push - uses: docker/bake-action@v6.10.0 + uses: docker/bake-action@v7.0.0 with: files: | ./docker-bake.hcl @@ -131,6 +144,8 @@ jobs: - name: Dump Tags id: dump_tags + env: + JSON_OUTPUT: ${{ steps.docker_meta.outputs.json }} run: | echo "meta-${{ matrix.arch }}=${{ fromJSON(steps.docker_meta.outputs.json).tags[0] }}" >> $GITHUB_OUTPUT @@ -147,30 +162,33 @@ jobs: - name: Login to DockerHub if: matrix.registry == 'docker.io' - uses: docker/login-action@v3.6.0 + uses: docker/login-action@v4.1.0 with: username: ${{ inputs.dockerhub_user }} password: ${{ secrets.dockerhub_password }} - name: Login to GitHub Container Registry if: matrix.registry == 'ghcr.io' - uses: docker/login-action@v3.6.0 + uses: docker/login-action@v4.1.0 with: registry: ghcr.io username: ${{ inputs.repo_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Install Cosign - uses: sigstore/cosign-installer@v3.10.0 + if: ${{ inputs.sign_image == 'true' }} + uses: sigstore/cosign-installer@v4.1.1 - name: Create image tags id: image_tags run: | AMD64=$(echo ${{ needs.bake.outputs.meta-amd64 }} | cut -f 2- -d '/') ARM64=$(echo ${{ needs.bake.outputs.meta-arm64v8 }} | cut -f 2- -d '/') + RSICV64=$(echo ${{ needs.bake.outputs.meta-riscv64 }} | cut -f 2- -d '/') if [[ -n $AMD64 ]]; then TAG_NAME=$(echo ${{ needs.bake.outputs.meta-amd64 }} | cut -f 2- -d ':' | cut -f 2- -d '-'); AMD64="${{ matrix.registry }}/${AMD64}"; fi if [[ -n $ARM64 ]]; then TAG_NAME=$(echo ${{ needs.bake.outputs.meta-arm64v8 }} | cut -f 2- -d ':' | cut -f 2- -d '-'); ARM64="${{ matrix.registry }}/${ARM64}"; fi - EXTRA_IMAGES=$(echo $AMD64 $ARM64 | tr ' ' ',') + if [[ -n $RSICV64 ]]; then TAG_NAME=$(echo ${{ needs.bake.outputs.meta-riscv64 }} | cut -f 2- -d ':' | cut -f 2- -d '-'); RSICV64="${{ matrix.registry }}/${RSICV64}"; fi + EXTRA_IMAGES=$(echo $AMD64 $ARM64 $RSICV64 | tr ' ' ',') echo "Extra images are: ${EXTRA_IMAGES}" FINAL_IMAGE=ghcr.io/${{ inputs.repo_owner }}/${{ inputs.app_name }}:${TAG_NAME} echo "extra_images=${EXTRA_IMAGES}" >> $GITHUB_OUTPUT @@ -179,7 +197,8 @@ jobs: - name: Load Key id: op-load-key - uses: 1password/load-secrets-action@v3 + if: ${{ inputs.sign_image == 'true' }} + uses: 1password/load-secrets-action@v4.0.0 with: export-env: true env: @@ -191,41 +210,64 @@ jobs: if: ${{ github.event_name != 'pull_request' }} id: manifest_sign run: | - if [[ ${{ steps.image_tags.outputs.extra_images }} =~ "arm64v8" ]]; then - docker buildx imagetools create -t ${MANIFESTIMAGE}:latest ${MANIFESTIMAGE}:amd64-latest ${MANIFESTIMAGE}:arm64v8-latest + if [[ ${{ steps.image_tags.outputs.extra_images }} =~ "riscv64" ]]; then + if [[ ${{ github.event.repository.default_branch }} == ${{ inputs.branch_name }} ]]; then + docker buildx imagetools create -t ${MANIFESTIMAGE}:riscv64-latest ${MANIFESTIMAGE}:riscv64-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:arm64v8-latest ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:amd64-latest ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:latest ${MANIFESTIMAGE}:amd64-latest ${MANIFESTIMAGE}:arm64v8-latest ${MANIFESTIMAGE}:riscv64-latest + fi + docker buildx imagetools create -t ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:riscv64-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:${{ inputs.branch_name }} ${MANIFESTIMAGE}:amd64-${{ inputs.branch_name }} ${MANIFESTIMAGE}:arm64v8-${{ inputs.branch_name }} ${MANIFESTIMAGE}:riscv64-${{ inputs.branch_name }} + elif [[ ${{ steps.image_tags.outputs.extra_images }} =~ "arm64v8" ]]; then + if [[ ${{ github.event.repository.default_branch }} == ${{ inputs.branch_name }} ]]; then + docker buildx imagetools create -t ${MANIFESTIMAGE}:arm64v8-latest ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:amd64-latest ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:latest ${MANIFESTIMAGE}:amd64-latest ${MANIFESTIMAGE}:arm64v8-latest + fi docker buildx imagetools create -t ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:${{ inputs.branch_name }} ${MANIFESTIMAGE}:amd64-${{ inputs.branch_name }} ${MANIFESTIMAGE}:arm64v8-${{ inputs.branch_name }} else - docker buildx imagetools create -t ${MANIFESTIMAGE}:latest ${MANIFESTIMAGE}:amd64-latest + if [[ ${{ github.event.repository.default_branch }} == ${{ inputs.branch_name }} ]]; then + docker buildx imagetools create -t ${MANIFESTIMAGE}:amd64-latest ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:latest ${MANIFESTIMAGE}:amd64-latest + fi docker buildx imagetools create -t ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} + docker buildx imagetools create -t ${MANIFESTIMAGE}:${{ inputs.branch_name }} ${MANIFESTIMAGE}:amd64-${{ inputs.branch_name }} fi case "${{ matrix.registry }}" in "ghcr.io") AUTH_URL="https://ghcr.io/token?scope=repository%3A${{ inputs.repo_owner }}%2F${{ inputs.app_name }}%3Apull"; REGISTRY="ghcr.io";; "docker.io") AUTH_URL="https://auth.docker.io/token?service=registry.docker.io&scope=repository:${{ inputs.repo_owner }}/${{ inputs.app_name }}:pull"; REGISTRY="registry-1.docker.io";; esac - TOKEN="$(curl -s -f --retry 10 --retry-max-time 60 --retry-connrefused "${AUTH_URL}" | jq -r '.token')" - IFS=',' read -r -a IMAGES <<< "${{ steps.image_tags.outputs.extra_images }}" - for i in "${IMAGES[@]}"; do - i=$(echo $i | cut -d":" -f2 | cut -d"-" -f1) - DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/${i}-latest 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) + if [[ ${SIGN_IMAGE} == "true" ]]; then + TOKEN="$(curl -s -f --retry 10 --retry-max-time 60 --retry-connrefused "${AUTH_URL}" | jq -r '.token')" + IFS=',' read -r -a IMAGES <<< "${{ steps.image_tags.outputs.extra_images }}" + for i in "${IMAGES[@]}"; do + i=$(echo $i | cut -d":" -f2 | cut -d"-" -f1) + DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/${i}-latest 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) + DIGEST="${DIGEST//[$'\t\r\n ']}" + echo "Signing ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG} and ${MANIFESTIMAGE}:${i}-latest with digest ${DIGEST}" + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG}@${DIGEST} + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${i}-latest@${DIGEST} + done + DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/latest 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) DIGEST="${DIGEST//[$'\t\r\n ']}" - echo "Signing ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG} and ${MANIFESTIMAGE}:${i}-latest with digest ${DIGEST}" - cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG}@${DIGEST} - cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${i}-latest@${DIGEST} - done - DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/latest 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) - DIGEST="${DIGEST//[$'\t\r\n ']}" - echo "Signing ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} and ${MANIFESTIMAGE}:latest with digest ${DIGEST}" - cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${EXT_RELEASE_TAG}@${DIGEST} - cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:latest@${DIGEST} + echo "Signing ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} and ${MANIFESTIMAGE}:latest with digest ${DIGEST}" + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${EXT_RELEASE_TAG}@${DIGEST} + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:latest@${DIGEST} + fi env: EXT_RELEASE_TAG: ${{ steps.image_tags.outputs.tag_name }} MANIFESTIMAGE: ${{ matrix.registry }}/${{ inputs.repo_owner }}/${{ inputs.app_name }} + SIGN_IMAGE: ${{ inputs.sign_image }} - name: Create manifest and sign image for PRs if: ${{ github.event_name == 'pull_request' }} id: manifest_sign_pr run: | - if [[ ${{ steps.image_tags.outputs.extra_images }} =~ "arm64v8" ]]; then + if [[ ${{ steps.image_tags.outputs.extra_images }} =~ "riscv64" ]]; then + docker buildx imagetools create -t ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:riscv64-${EXT_RELEASE_TAG} + elif [[ ${{ steps.image_tags.outputs.extra_images }} =~ "arm64v8" ]]; then docker buildx imagetools create -t ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG} else docker buildx imagetools create -t ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} @@ -234,19 +276,66 @@ jobs: "ghcr.io") AUTH_URL="https://ghcr.io/token?scope=repository%3A${{ inputs.repo_owner }}%2F${{ inputs.app_name }}%3Apull"; REGISTRY="ghcr.io";; "docker.io") AUTH_URL="https://auth.docker.io/token?service=registry.docker.io&scope=repository:${{ inputs.repo_owner }}/${{ inputs.app_name }}:pull"; REGISTRY="registry-1.docker.io";; esac - TOKEN="$(curl -s -f --retry 10 --retry-max-time 60 --retry-connrefused "${AUTH_URL}" | jq -r '.token')" - IFS=',' read -r -a IMAGES <<< "${{ steps.image_tags.outputs.extra_images }}" - for i in "${IMAGES[@]}"; do - i=$(echo $i | cut -d":" -f2 | cut -d"-" -f1) - DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/${i}-${EXT_RELEASE_TAG} 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) + if [[ ${SIGN_IMAGE} == "true" ]]; then + TOKEN="$(curl -s -f --retry 10 --retry-max-time 60 --retry-connrefused "${AUTH_URL}" | jq -r '.token')" + IFS=',' read -r -a IMAGES <<< "${{ steps.image_tags.outputs.extra_images }}" + for i in "${IMAGES[@]}"; do + i=$(echo $i | cut -d":" -f2 | cut -d"-" -f1) + DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/${i}-${EXT_RELEASE_TAG} 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) + DIGEST="${DIGEST//[$'\t\r\n ']}" + echo "Signing ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG} with digest ${DIGEST}" + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG}@${DIGEST} + done + DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/${EXT_RELEASE_TAG} 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) DIGEST="${DIGEST//[$'\t\r\n ']}" - echo "Signing ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG} with digest ${DIGEST}" - cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${i}-${EXT_RELEASE_TAG}@${DIGEST} - done - DIGEST=$(curl -s -L --retry 10 --retry-max-time 60 --retry-connrefused -I -H "Authorization: Bearer ${TOKEN}" --header "Accept: application/vnd.docker.distribution.manifest.v2+json" --header "Accept: application/vnd.oci.image.index.v1+json" https://${REGISTRY}/v2/${MANIFESTIMAGE}/manifests/${EXT_RELEASE_TAG} 2>&1 | grep 'docker-content-digest' | cut -d' ' -f2) - DIGEST="${DIGEST//[$'\t\r\n ']}" - echo "Signing ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} with digest ${DIGEST}" - cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${EXT_RELEASE_TAG}@${DIGEST} + echo "Signing ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} with digest ${DIGEST}" + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${MANIFESTIMAGE}:${EXT_RELEASE_TAG}@${DIGEST} + fi env: EXT_RELEASE_TAG: ${{ steps.image_tags.outputs.tag_name }} MANIFESTIMAGE: ${{ matrix.registry }}/${{ inputs.repo_owner }}/${{ inputs.app_name }} + SIGN_IMAGE: ${{ inputs.sign_image }} + + finally: + runs-on: ubuntu-latest + if: ${{ github.actor != 'dependabot[bot]' }} + needs: + - bake + - manifest + steps: + - + name: Checkout + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.head_ref || inputs.branch_name }} + - + name: Commit release version + if: ${{ github.event_name != 'pull_request' }} + id: commit_release + env: + CI_COMMIT_MESSAGE: CI Build Workflow Updates + run: | + FILE_BASE64=$(base64 <<< "${{ needs.bake.outputs.app_version }}") + FILE_BLOB=$(curl -L \ + -H "Accept: application/vnd.github.object" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/contents/version.txt?ref=${{ inputs.branch_name }}) + if jq -re .sha <<< ${FILE_BLOB} 2> /dev/null; then + FILE_SHA=$(jq -r .sha <<< ${FILE_BLOB}) + curl -L \ + -X PUT \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/contents/version.txt \ + -d "{\"branch\":\"${{ inputs.branch_name }}\",\"sha\":\"${FILE_SHA}\",\"message\":\"${{ env.CI_COMMIT_MESSAGE }}\",\"content\":\"${FILE_BASE64}\"}" + else + curl -L \ + -X PUT \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/contents/version.txt \ + -d "{\"branch\":\"${{ inputs.branch_name }}\",\"message\":\"${{ env.CI_COMMIT_MESSAGE }}\",\"content\":\"${FILE_BASE64}\"}" + fi diff --git a/.github/workflows/check-and-release.yml b/.github/workflows/check-and-release.yml index 7fdacda..dc5db2e 100644 --- a/.github/workflows/check-and-release.yml +++ b/.github/workflows/check-and-release.yml @@ -21,7 +21,7 @@ on: prerelease: required: false type: boolean - branch: + branch_name: required: false type: string default: main @@ -41,7 +41,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 + with: + ref: ${{ inputs.branch_name }} - name: Check if we should release id: build_check @@ -77,7 +79,7 @@ jobs: echo "**** Retrieving last pushed version ****" IMAGE="${{ inputs.repo_owner }}/${{ inputs.app_name }}" - TAG="latest" + TAG="${{ inputs.branch_name }}" TOKEN=$(curl -s \ "https://ghcr.io/token?scope=repository%3A${{ inputs.repo_owner }}%2F${{ inputs.app_name }}%3Apull" \ | jq -r '.[]') @@ -133,7 +135,7 @@ jobs: LS_VERSION="ls1" elif [[ "${DIGEST}" == "unknown" ]]; then echo "**** Package exists but something is wrong with the manifest, using last release instead ****" - LAST_RELEASE=$(curl -sL "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | awk '/tag_name/{print $4;exit}' FS='[""]') + LAST_RELEASE=$(curl -sL "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | jq -r 'last((sort_by(.published_at) | .[] | select(.target_commitish == "${{ inputs.branch_name }}") | .tag_name))') IMAGE_VERSION=$(echo ${LAST_RELEASE} | awk -F'-ls' '{print $1}') LS_VERSION=$(echo ${IMAGE_RELEASE} | awk -F'-ls' '{print $2}') else @@ -178,7 +180,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 + with: + ref: ${{ inputs.branch_name }} - name: Generate release tag id: gen_tags @@ -188,10 +192,10 @@ jobs: echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT - name: Do release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v2.6.1 with: body: "* Updated ${{ needs.check.outputs.app_name }} to ${{ needs.check.outputs.image_release }}" token: ${{ secrets.repo_release_token }} tag_name: ${{ steps.gen_tags.outputs.tag_name }} prerelease: ${{ needs.check.outputs.prerelease }} - target_commitish: ${{ inputs.branch }} + target_commitish: ${{ inputs.branch_name }} diff --git a/.github/workflows/check-baseimage-update.yml b/.github/workflows/check-baseimage-update.yml index 8fe2c7e..374a68a 100644 --- a/.github/workflows/check-baseimage-update.yml +++ b/.github/workflows/check-baseimage-update.yml @@ -18,7 +18,7 @@ on: prerelease: required: false type: boolean - branch: + branch_name: required: false type: string default: main @@ -44,7 +44,7 @@ jobs: BASETIME=$(date -d $(echo $BASE | \ jq -r 'first(.[] | select(.tag_name | match("(${{ inputs.basebranch }})-[0-9a-z]{8}") | .captures[].string) .published_at)') +%s) RELEASETIME=$(date -d $(curl -s "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | \ - jq -r 'first(.[].published_at)') +%s) + jq -r 'first(.[] | select(.target_commitish == "${{ inputs.branch_name }}")).published_at') +%s) if [ $BASETIME -le $RELEASETIME ]; then echo "*** Baseimage release is older than latest container release, skipping ***" echo "modified=false" >> $GITHUB_OUTPUT @@ -59,12 +59,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 + with: + ref: ${{ inputs.branch_name }} - name: Generate release tag id: gen_tags run: | - IMAGE_RELEASE=$(curl -s "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | jq -r 'sort_by(.published_at) | .[-1].tag_name') + IMAGE_RELEASE=$(curl -s "https://api.github.com/repos/${{ inputs.repo_owner }}/docker-${{ inputs.app_name }}/releases" | jq -r 'last((sort_by(.published_at) | .[] | select(.target_commitish == "${{ inputs.branch_name }}") | .tag_name))') APP_VERSION=$(echo $IMAGE_RELEASE | awk '{sub("-[^-]+$","")} 1') LS_VERSION=$(( $(echo $IMAGE_RELEASE | awk -NF '-' '{print $NF}' | cut -c 3-8 | sed 's/^0*//')+1 )) RELEASE_TAG=$APP_VERSION-ls$(printf $LS_VERSION) @@ -79,10 +81,10 @@ jobs: fi - name: Do release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v2.6.1 with: body: "* Updated ${{ inputs.app_name }} with new ${{ inputs.baseimage }} ${{ inputs.basebranch }} version" token: ${{ secrets.repo_release_token }} tag_name: ${{ steps.gen_tags.outputs.tag_name }} prerelease: ${{ steps.gen_tags.outputs.prerelease }} - target_commitish: ${{ inputs.branch }} + target_commitish: ${{ inputs.branch_name }} diff --git a/README.md b/README.md index e507a8d..debb808 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ This workflow will build a Docker image on release or on a PR being opened. It i `target-arch` is mandatory and can be one of `all`, `amd64`, `arm`, `64` +`sign_image` is an optional boolean for whether or not to sign the resultant image (default `true`) + +`branch_name` is optional and should be the name of the branch being built from (default `main`) + Example workflow: ```yaml @@ -37,13 +41,15 @@ on: jobs: call-workflow: - uses: linuxserver-labs/docker-actions/.github/workflows/build-image.yml@v7 + uses: linuxserver-labs/docker-actions/.github/workflows/build-image.yml@v8 with: repo_owner: ${{ github.repository_owner }} app_name: "your_spotify" release_type: "github" release_url: "https://api.github.com/repos/Yooooomi/your_spotify" target-arch: "64" + sign_image: true + branch_name: main secrets: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} ``` @@ -62,7 +68,11 @@ This workflow will build a Docker image on release or on a PR being opened. It i `app_name` is a mandatory input and should be the desired name of the image and consistent across all workflows. -`target-arch` is mandatory and should be an array of one or more of `amd64`, `arm64v8` +`target-arch` is mandatory and should be an array of one or more of `amd64`, `arm64v8`, `risvc64` + +`sign_image` is an optional boolean for whether or not to sign the resultant image (default `true`) + +`branch_name` is optional and should be the name of the branch being built from (default `main`) Example workflow: @@ -76,13 +86,15 @@ on: jobs: call-workflow: - uses: linuxserver-labs/docker-actions/workflows/build-split-image.yml@v7 + uses: linuxserver-labs/docker-actions/workflows/build-split-image.yml@v8 with: repo_owner: ${{ github.repository_owner }} app_name: "radarr" release_type: "script" target-arch: >- ["amd64", "arm64v8"] + sign_image: true + branch_name: main secrets: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} ``` @@ -103,7 +115,7 @@ This workflow will check for updates to the upstream application and generate a `prerelease` is an optional input and will cause all releases to be marked as prerelease. -`branch` is an optional input and will cause all releases to be targeted against that branch. Defaults to `main` so needs to be specified if your default branch is `master` +`branch_name` is optional and will cause all releases to be targeted against that branch (default `main`). Example workflow: @@ -117,13 +129,13 @@ on: jobs: call-workflow: - uses: linuxserver-labs/docker-actions/.github/workflows/check-and-release.yml@v7 + uses: linuxserver-labs/docker-actions/.github/workflows/check-and-release.yml@v8 with: repo_owner: ${{ github.repository_owner }} app_name: "radarr" release_type: "script" prerelease: true - branch: nightly + branch_name: nightly secrets: repo_release_token: ${{ secrets.repo_release_token }} ``` @@ -138,7 +150,7 @@ This workflow will check for updates to the base image used by the repo and gene `basebranch` is a mandatory input and should be the release tag of the base image you're using. e.g. if you're using the focal tag of the Ubuntu baseimage then the `basebranch` should be `focal`. -`branch` is an optional input and will cause all releases to be targeted against that branch. Defaults to `main` so needs to be specified if your default branch is `master` +`branch_name` is optional and will cause all releases to be targeted against that branch (default `main`). Example workflow: @@ -151,13 +163,13 @@ on: jobs: call-workflow: - uses: linuxserver-labs/docker-actions/.github/workflows/check-baseimage-update.yml@v7 + uses: linuxserver-labs/docker-actions/.github/workflows/check-baseimage-update.yml@v8 with: repo_owner: ${{ github.repository_owner }} baseimage: "alpine" basebranch: "3.19" app_name: "radarr" - branch: nightly + branch_name: nightly secrets: repo_release_token: ${{ secrets.repo_release_token }} ``` diff --git a/docker-bake.hcl b/docker-bake.hcl index 5400ab8..fcd506b 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -48,6 +48,7 @@ target "all" { inherits = ["image"] platforms = [ "linux/amd64", - "linux/arm64" + "linux/arm64", + "linux/riscv64" ] }