diff --git a/.editorconfig b/.editorconfig index c48823257..cd11ca4c5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -39,8 +39,7 @@ indent_size = 4 [*.{yaml,yml}] indent_style = space -indent_size = 4 -trim_trailing_whitespace = false +indent_size = 2 [.php_cs{,.dist}] indent_style = space diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11e44ddd1..daa344962 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,7 @@ jobs: if: &normal_ci ${{ (github.event_name == 'pull_request' && github.event.action != 'labeled' && github.event.action != 'unlabeled' && github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id) || github.event_name == 'push' }} steps: - &checkout_step + name: 'Checkout Repository' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: 'Perform Spellcheck' @@ -80,8 +81,8 @@ jobs: php-build-test: name: 'PHP Build and Test (${{ matrix.php }})' permissions: - actions: write contents: read + packages: write needs: [ spellcheck ] if: *normal_ci runs-on: ubuntu-latest @@ -93,20 +94,36 @@ jobs: steps: - *checkout_step - - name: 'Set up Docker Buildx' + - &lowercase_repo + name: 'Lowercase github.repository' + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY@L}" >> ${GITHUB_ENV} + + - &login_ghcr + name: 'Login to GitHub Container Registry' + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - &setup_buildx + name: 'Set up Docker Buildx' uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: 'Build Docker Test Image' - run: | - docker buildx build \ - --target test \ - --build-arg PHP_VERSION=${{ matrix.php }} \ - --build-arg INSTALL_TRANSLATIONS=false \ - --cache-from type=gha,scope=fossbilling-test-${{ matrix.php }} \ - --cache-to type=gha,scope=fossbilling-test-${{ matrix.php }},mode=max \ - --load \ - --tag fossbilling:test-${{ matrix.php }} \ - . + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 + with: + build-args: | + PHP_VERSION=${{ matrix.php }} + INSTALL_TRANSLATIONS=false + cache-from: type=registry,ref=ghcr.io/${{ env.IMAGE_NAME }}:cache-ci-${{ matrix.php }} + cache-to: type=registry,ref=ghcr.io/${{ env.IMAGE_NAME }}:cache-ci-${{ matrix.php }},mode=max + context: . + load: true + tags: | + fossbilling:test-${{ matrix.php }} + target: test - name: 'PHPStan' run: | @@ -127,22 +144,74 @@ jobs: run: bash ./.github/scripts/run-docker-live-tests.sh fossbilling:test-${{ matrix.php }} preview-build: - name: 'Create Preview Release' + name: 'Create Preview Build' permissions: - actions: write contents: read + packages: write needs: [ php-build-test ] - if: *normal_ci - uses: ./.github/workflows/release-build.yml + if: ${{ (github.event_name == 'pull_request' && github.event.action != 'labeled' && github.event.action != 'unlabeled' && github.event.pull_request.base.repo.id != github.event.pull_request.head.repo.id) || (github.event_name == 'push' && github.ref != 'refs/heads/main') }} + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - *checkout_step + - *lowercase_repo + - *login_ghcr + - *setup_buildx + + - name: 'Build Release Artifact' + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 + with: + build-args: | + FOSSBILLING_VERSION=${{ github.sha }} + FOSSBILLING_VERSION_TRUNCATE=7 + SENTRY_DSN=${{ vars.SENTRY_DSN }} + cache-from: type=registry,ref=ghcr.io/${{ env.IMAGE_NAME }}:cache-preview + cache-to: type=registry,ref=ghcr.io/${{ env.IMAGE_NAME }}:cache-preview,mode=max + context: . + target: release-artifact + outputs: type=local,dest=./release-tree + + - name: 'Create Preview Build Archive' + run: tar -cf /tmp/build.tar -C ./release-tree ./ + + - name: 'Upload Preview Build Archive' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + if-no-files-found: 'error' + name: preview-build + path: /tmp/build.tar + retention-days: 1 + + preview-docker: + name: 'Build and Push Preview Docker Image' + permissions: + contents: read + packages: write + needs: [ php-build-test ] + if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} + uses: ./.github/workflows/docker-build.yml + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} with: - artifact-name: 'preview-build' - preview: true - cache-scope: fossbilling-preview + target: runtime + cache-scope: cache-preview + extract-release-tree: true + release-tree-artifact-name: preview-build + meta-images: fossbilling/fossbilling + meta-tags: | + type=raw,value=preview + meta-flavor: | + latest=false + build-args: | + FOSSBILLING_VERSION=${{ github.sha }} + FOSSBILLING_VERSION_TRUNCATE=7 + SENTRY_DSN=${{ vars.SENTRY_DSN }} upload-preview: name: 'Upload Preview Release' - needs: [ preview-build ] - if: *normal_ci + needs: [ preview-build, preview-docker ] + if: ${{ !cancelled() && (needs.preview-build.result == 'success' || needs.preview-docker.result == 'success') }} runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -178,7 +247,7 @@ jobs: AWS_S3_ENDPOINT: ${{ secrets.AWS_S3_ENDPOINT }} AWS_REGION: 'auto' SOURCE_DIR: './dist' - + - name: 'Upload Downloadable Preview to GitHub' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: @@ -186,36 +255,3 @@ jobs: name: 'FOSSBilling Preview' path: './src/*' retention-days: 14 - - deploy-preview-docker: - name: 'Build and Push Preview to Docker Hub' - permissions: - contents: read - id-token: write - needs: [ preview-build ] - if: ${{ github.ref == 'refs/heads/main' }} - uses: docker/github-builder/.github/workflows/build.yml@c2782c55efa56a01b9c30021db8f5ec3993228a3 # v1.8.0 - secrets: - registry-auths: | - - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - with: - output: image - push: true - platforms: linux/amd64,linux/arm64 - cache: true - cache-mode: max - cache-scope: fossbilling-preview - sbom: true - set-meta-labels: true - set-meta-annotations: true - meta-images: fossbilling/fossbilling - meta-flavor: | - latest=false - meta-tags: | - type=raw,value=preview - build-args: | - FOSSBILLING_VERSION=${{ github.sha }} - FOSSBILLING_VERSION_TRUNCATE=7 - SENTRY_DSN=${{ vars.SENTRY_DSN }} diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 000000000..efdb9651f --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,187 @@ +name: Multi-platform Docker Build + +on: + workflow_call: + inputs: + target: + required: true + type: string + build-args: + required: false + type: string + default: '' + cache-scope: + required: true + type: string + meta-images: + required: true + type: string + meta-tags: + required: true + type: string + meta-flavor: + required: false + type: string + default: '' + extract-release-tree: + required: false + type: boolean + default: false + release-tree-artifact-name: + required: false + type: string + default: 'release-tree' + secrets: + DOCKERHUB_USERNAME: + required: true + DOCKERHUB_TOKEN: + required: true + outputs: + release-tree-artifact: + value: ${{ jobs.build.outputs.release-tree-artifact }} + +permissions: + contents: read + packages: write + +jobs: + build: + name: 'Build (${{ matrix.platform }})' + runs-on: ${{ matrix.runner }} + strategy: + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + fail-fast: false + outputs: + release-tree-artifact: ${{ steps.release-tree-output.outputs.artifact-name || '' }} + steps: + - name: 'Checkout Repository' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: 'Lowercase github.repository' + run: echo "IMAGE_NAME=${GITHUB_REPOSITORY@L}" >> "${GITHUB_ENV}" + + - name: 'Set Release Tree Artifact Output' + id: release-tree-output + if: inputs.extract-release-tree + run: echo "artifact-name=${{ inputs.release-tree-artifact-name }}" >> "${GITHUB_OUTPUT}" + + - name: 'Login to GitHub Container Registry' + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 'Login to Docker Hub' + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 + with: + registry: docker.io + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: 'Set up Docker Buildx' + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + + - name: 'Docker Metadata' + id: meta + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 + with: + images: ${{ inputs.meta-images }} + tags: ${{ inputs.meta-tags }} + flavor: ${{ inputs.meta-flavor }} + + - name: 'Build and Push by Digest' + id: build + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 + with: + build-args: ${{ inputs.build-args }} + cache-from: type=registry,ref=ghcr.io/${{ env.IMAGE_NAME }}:${{ inputs.cache-scope }} + cache-to: type=registry,ref=ghcr.io/${{ env.IMAGE_NAME }}:${{ inputs.cache-scope }},mode=max + context: . + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,push-by-digest=true,name=${{ inputs.meta-images }} + target: ${{ inputs.target }} + + - name: 'Extract Release Tree' + if: inputs.extract-release-tree && matrix.platform == 'linux/amd64' + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 + with: + build-args: ${{ inputs.build-args }} + cache-from: type=registry,ref=ghcr.io/${{ env.IMAGE_NAME }}:${{ inputs.cache-scope }} + context: . + outputs: type=local,dest=./release-tree + target: release-artifact + + - name: 'Create Release Tree Archive' + if: inputs.extract-release-tree && matrix.platform == 'linux/amd64' + run: tar -cf /tmp/build.tar -C ./release-tree ./ + + - name: 'Upload Release Tree' + if: inputs.extract-release-tree && matrix.platform == 'linux/amd64' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + if-no-files-found: 'error' + name: ${{ inputs.release-tree-artifact-name }} + path: /tmp/build.tar + retention-days: 1 + + - name: 'Export Digest' + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: 'Upload Digest' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + if-no-files-found: 'error' + name: digest-${{ matrix.platform }} + path: /tmp/digests/* + retention-days: 1 + + merge: + name: 'Merge Multi-Platform Manifest' + needs: [ build ] + runs-on: ubuntu-latest + steps: + - name: 'Login to Docker Hub' + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 + with: + registry: docker.io + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: 'Set up Docker Buildx' + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + + - name: 'Docker Metadata' + id: meta + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 + with: + images: ${{ inputs.meta-images }} + tags: ${{ inputs.meta-tags }} + flavor: ${{ inputs.meta-flavor }} + + - name: 'Download Digests' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + path: /tmp/digests + pattern: digest-* + merge-multiple: true + + - name: 'Create Manifest List and Push' + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ inputs.meta-images }}@sha256:%s ' *) + + - name: 'Inspect Image' + run: | + docker buildx imagetools inspect \ + '${{ inputs.meta-images }}:${{ steps.meta.outputs.version }}' diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml deleted file mode 100644 index 1b7806889..000000000 --- a/.github/workflows/release-build.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: 'Build and Prepare Release Archive' - -on: - workflow_call: - inputs: - artifact-name: - description: 'The artifact name to use when uploading the preview archive.' - default: 'preview-build' - required: false - type: string - preview: - description: 'Whether the release is a preview release or not.' - default: true - required: false - type: boolean - cache-scope: - description: 'The GHA cache scope to read cached layers from (e.g. from a parallel Docker image build).' - default: '' - required: false - type: string - -jobs: - release-build: - name: 'Build and Prepare Release' - permissions: - actions: write - contents: read - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: 'Set up Docker Buildx' - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - - - name: 'Build Release Artifact' - run: | - cache_args=() - if [ -n "${{ inputs.cache-scope }}" ]; then - cache_args+=(--cache-from "type=gha,scope=${{ inputs.cache-scope }}") - fi - - docker buildx build \ - --target release-artifact \ - --build-arg FOSSBILLING_VERSION="${{ inputs.preview && github.sha || github.ref_name }}" \ - --build-arg FOSSBILLING_VERSION_TRUNCATE="${{ inputs.preview && '7' || '0' }}" \ - --build-arg SENTRY_DSN="${{ vars.SENTRY_DSN }}" \ - "${cache_args[@]}" \ - --output type=local,dest=./release-tree \ - . - - - name: 'Create Release Build Archive' - run: tar -cf /tmp/build.tar -C ./release-tree ./ - - - name: 'Upload Release Build Archive' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - if-no-files-found: 'error' - name: ${{ inputs.artifact-name }} - path: /tmp/build.tar - retention-days: 1 diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index 0c52637c8..744d38fe4 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -2,7 +2,7 @@ name: Upload Release to Docker Hub on: release: - types: + types: - published concurrency: @@ -12,7 +12,7 @@ concurrency: jobs: check-latest: name: 'Check if Release is Latest' - permissions: + permissions: contents: read runs-on: ubuntu-latest timeout-minutes: 10 @@ -38,67 +38,25 @@ jobs: deploy-release-docker: name: 'Build and Push Release to Docker Hub' - permissions: + permissions: contents: read - id-token: write + packages: write needs: [ check-latest ] - uses: docker/github-builder/.github/workflows/build.yml@c2782c55efa56a01b9c30021db8f5ec3993228a3 # v1.8.0 + uses: ./.github/workflows/docker-build.yml secrets: - registry-auths: | - - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} with: - output: image - push: true - platforms: linux/amd64,linux/arm64 - cache: true - cache-mode: max - cache-scope: fossbilling-release - sbom: true - set-meta-labels: true - set-meta-annotations: true + target: runtime + cache-scope: cache-release + extract-release-tree: true + release-tree-artifact-name: release-build meta-images: fossbilling/fossbilling - meta-flavor: | - latest=false meta-tags: | type=semver,pattern={{version}} type=raw,value=latest,enable=${{ needs.check-latest.outputs.is_latest == 'true' }} + meta-flavor: | + latest=false build-args: | FOSSBILLING_VERSION=${{ github.ref_name }} SENTRY_DSN=${{ vars.SENTRY_DSN }} - - build-release-archive: - name: 'Build Release Archive' - permissions: - actions: write - contents: read - needs: [ check-latest ] - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: 'Set up Docker Buildx' - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - - - name: 'Build Release Artifact' - run: | - docker buildx build \ - --target release-artifact \ - --build-arg FOSSBILLING_VERSION="${{ github.ref_name }}" \ - --build-arg SENTRY_DSN="${{ vars.SENTRY_DSN }}" \ - --cache-from type=gha,scope=fossbilling-release \ - --output type=local,dest=./release-tree \ - . - - - name: 'Create Release Build Archive' - run: tar -cf /tmp/build.tar -C ./release-tree ./ - - - name: 'Upload Release Build Archive' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - if-no-files-found: 'error' - name: release-build - path: /tmp/build.tar - retention-days: 1 diff --git a/.gitignore b/.gitignore index a9422d155..de077abae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .vscode ._* .DS_Store +.opencode /cache /src/config.php /src/data/log/*