diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..83811cb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,39 @@ +# Exclude VCS and IDE files +.git +.gitignore +.idea/ +*.iml + +# Gradle caches +.gradle/ +**/.gradle/ + +# Node modules and app build cache +app/node_modules/ +app/.pnpm-store/ +app/.npm/ +app/.yarn/ +app/.vite/ +app/dist/ + +# General build outputs (keep only the jars we actually need) +**/build/ +!app/build/ +!app/build/libs/ +!app/build/libs/app.jar + +# Only keep plugin jars in build/libs +plugins/** +!plugins/*/build/ +!plugins/*/build/libs/ +!plugins/*/build/libs/*.jar + +# Large local/runtime data not needed in image context +data/ +db/ +logs/ +plugindata/ + +# Docker intermediate artifacts +**/.DS_Store + diff --git a/.github/actions/docker-build-push/action.yml b/.github/actions/docker-build-push/action.yml index 2170b2c..b8f7135 100644 --- a/.github/actions/docker-build-push/action.yml +++ b/.github/actions/docker-build-push/action.yml @@ -1,18 +1,12 @@ name: 'Docker Build and Push' -description: 'Builds and pushes Docker images to Docker Hub and GHCR with flexible tagging.' +description: 'Builds and pushes Docker images to GHCR with flexible tagging.' runs: using: 'composite' steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ inputs.dockerhub_username }} - password: ${{ inputs.dockerhub_token }} - - - name: Log in to GitHub Container Registry + - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io @@ -21,6 +15,7 @@ runs: - name: Prepare Ubuntu tags id: ubuntu_tags + if: ${{ inputs.variant != 'alpine' }} shell: bash run: | TAGS="${{ inputs.tags }}" @@ -28,6 +23,7 @@ runs: echo "ubuntu_tags=$UBUNTU_TAGS" >> $GITHUB_OUTPUT - name: Build and push Docker image (Alpine) + if: ${{ inputs.variant != 'ubuntu' }} uses: docker/build-push-action@v5 with: context: ${{ inputs.context }} @@ -39,6 +35,7 @@ runs: cache-to: type=gha - name: Build and push Docker image (Ubuntu) + if: ${{ inputs.variant != 'alpine' }} uses: docker/build-push-action@v5 with: context: ${{ inputs.context }} @@ -50,12 +47,6 @@ runs: cache-to: type=gha inputs: - dockerhub_username: - required: true - description: 'Docker Hub username' - dockerhub_token: - required: true - description: 'Docker Hub token' ghcr_username: required: true description: 'GHCR username' @@ -74,3 +65,7 @@ inputs: tags: required: true description: 'Comma-separated list of image tags' + variant: + required: true + default: 'both' + description: 'Image variant to build: alpine, ubuntu, or both' diff --git a/.github/workflows/docker-delete-tag-on-merge.yml b/.github/workflows/docker-delete-tag-on-merge.yml index 80822b4..0c7e60b 100644 --- a/.github/workflows/docker-delete-tag-on-merge.yml +++ b/.github/workflows/docker-delete-tag-on-merge.yml @@ -1,4 +1,4 @@ -name: Delete Docker Tag on Merge +name: Delete Image Tags on Merge on: pull_request: @@ -9,10 +9,11 @@ jobs: delete-docker-tag: if: startsWith(github.event.pull_request.head.ref, 'fix/') || startsWith(github.event.pull_request.head.ref, 'release/') runs-on: ubuntu-latest + name: Cleanup Image Tags from GHCR permissions: packages: write steps: - - name: Extract merged branch name and tag + - name: Extract tag from branch name id: extract_branch run: | BRANCH="${{ github.event.pull_request.head.ref }}" @@ -26,50 +27,8 @@ jobs: echo "tag=$TAG" >> $GITHUB_OUTPUT shell: bash - - name: Delete image tag from Docker Hub + - name: Delete tags if: steps.extract_branch.outputs.tag != '' - env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - TAG: ${{ steps.extract_branch.outputs.tag }} - run: | - echo "Deleting Docker tag from Docker Hub: $TAG" - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -u "$DOCKERHUB_USERNAME:$DOCKERHUB_TOKEN" \ - "https://hub.docker.com/v2/repositories/grimsi/gameyfin/tags/$TAG/") - if [ "$RESPONSE" != "204" ]; then - echo "Failed to delete Docker Hub tag: $TAG (HTTP $RESPONSE)" >&2 - exit 1 - fi - shell: bash - - - name: Delete image tag from GHCR - if: steps.extract_branch.outputs.tag != '' - env: - GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ steps.extract_branch.outputs.tag }} - REPO: gameyfin/gameyfin - OWNER: ${{ github.repository_owner }} - run: | - echo "Deleting Docker tag from GHCR: $TAG" - # Get the package ID - PACKAGE_ID=$(curl -s -H "Authorization: Bearer $GHCR_TOKEN" \ - "https://api.github.com/users/$OWNER/packages/container/$REPO" | jq -r '.id') - if [ "$PACKAGE_ID" = "null" ] || [ -z "$PACKAGE_ID" ]; then - echo "Failed to get GHCR package ID for $REPO" >&2 - exit 1 - fi - # Get the version ID for the tag - VERSION_ID=$(curl -s -H "Authorization: Bearer $GHCR_TOKEN" \ - "https://api.github.com/users/$OWNER/packages/container/$REPO/versions" | jq -r ".[] | select(.metadata.container.tags[]? == \"$TAG\") | .id") - if [ -z "$VERSION_ID" ]; then - echo "Failed to find GHCR version for tag: $TAG" >&2 - exit 1 - fi - # Delete the version - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: Bearer $GHCR_TOKEN" \ - "https://api.github.com/users/$OWNER/packages/container/$REPO/versions/$VERSION_ID") - if [ "$RESPONSE" != "204" ]; then - echo "Failed to delete GHCR tag: $TAG (HTTP $RESPONSE)" >&2 - exit 1 - fi - shell: bash + uses: dataaxiom/ghcr-cleanup-action@v1 + with: + tags: ${{ steps.extract_branch.outputs.tag }},${{ steps.extract_branch.outputs.tag }}-ubuntu diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml deleted file mode 100644 index 2e0c221..0000000 --- a/.github/workflows/docker-develop.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Build and Push Docker Image - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - image_tag: - description: 'Docker image tag' - required: false - default: 'develop' - -jobs: - build-and-push: - runs-on: ubuntu-latest - permissions: - packages: write - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up JDK 21 - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '21' - - - name: Run production build - env: - GAMEYFIN_KEYSTORE_PASSWORD: ${{ secrets.GAMEYFIN_KEYSTORE_PASSWORD }} - run: ./gradlew clean build -Pvaadin.productionMode=true - - - name: Build and push Docker image - uses: ./.github/actions/docker-build-push - with: - dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} - dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} - ghcr_username: ${{ github.actor }} - ghcr_token: ${{ secrets.GITHUB_TOKEN }} - context: . - dockerfile: docker/Dockerfile - platforms: linux/arm64/v8,linux/amd64 - tags: grimsi/gameyfin:${{ inputs.image_tag || 'develop' }},ghcr.io/gameyfin/gameyfin:${{ inputs.image_tag || 'develop' }} diff --git a/.github/workflows/docker-fix.yml b/.github/workflows/docker-fix.yml index 87b2b5f..e2c6a3a 100644 --- a/.github/workflows/docker-fix.yml +++ b/.github/workflows/docker-fix.yml @@ -6,10 +6,12 @@ on: - 'fix/*' jobs: - build-and-push: + build: runs-on: ubuntu-latest permissions: + contents: write packages: write + checks: write steps: - name: Checkout code uses: actions/checkout@v5 @@ -25,6 +27,39 @@ jobs: GAMEYFIN_KEYSTORE_PASSWORD: ${{ secrets.GAMEYFIN_KEYSTORE_PASSWORD }} run: ./gradlew clean build -Pvaadin.productionMode=true + - name: Publish Test Report + uses: mikepenz/action-junit-report@v6 + if: success() || failure() # always run even if the previous step fails + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + + - name: Upload build outputs + uses: actions/upload-artifact@v4 + with: + name: build-outputs + path: | + app/build/libs/** + plugins/**/build/libs/**/*.jar + + docker: + needs: build + runs-on: ubuntu-latest + permissions: + packages: write + strategy: + fail-fast: false + matrix: + variant: [ alpine, ubuntu ] + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Download build outputs + uses: actions/download-artifact@v5 + with: + name: build-outputs + path: . + - name: Extract tag from branch name id: extract_tag run: | @@ -32,14 +67,13 @@ jobs: TAG="${BRANCH_NAME#fix/}" echo "tag=$TAG" >> $GITHUB_OUTPUT - - name: Build and push Docker image + - name: Build and push Docker image (${{ matrix.variant }}) uses: ./.github/actions/docker-build-push with: - dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} - dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} ghcr_username: ${{ github.actor }} ghcr_token: ${{ secrets.GITHUB_TOKEN }} context: . dockerfile: docker/Dockerfile platforms: linux/arm64/v8,linux/amd64 - tags: grimsi/gameyfin:${{ steps.extract_tag.outputs.tag }},ghcr.io/gameyfin/gameyfin:${{ steps.extract_tag.outputs.tag }} + tags: ghcr.io/gameyfin/gameyfin:${{ steps.extract_tag.outputs.tag }} + variant: ${{ matrix.variant }} diff --git a/.github/workflows/docker-preview.yml b/.github/workflows/docker-preview.yml index 61bedf2..88cab9b 100644 --- a/.github/workflows/docker-preview.yml +++ b/.github/workflows/docker-preview.yml @@ -6,13 +6,19 @@ on: - 'release/*' jobs: - build-and-push: + build: runs-on: ubuntu-latest permissions: + contents: write packages: write + checks: write + outputs: + version: ${{ steps.extract_version.outputs.version }} steps: - name: Checkout code uses: actions/checkout@v5 + with: + fetch-depth: 0 - name: Set up JDK 21 uses: actions/setup-java@v5 @@ -20,26 +26,79 @@ jobs: distribution: 'temurin' java-version: '21' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Extract version from branch name + id: extract_version + run: | + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + VERSION="${BRANCH_NAME#release/}-preview" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Update version in build.gradle.kts + run: | + sed -i "s/^version = .*/version = \"${{ steps.extract_version.outputs.version }}\"/" build.gradle.kts + + - name: Update version in app/package.json + run: | + jq ".version = \"${{ steps.extract_version.outputs.version }}\"" app/package.json > app/package.json.tmp && mv app/package.json.tmp app/package.json + + - name: Commit version bump (only if changes) + uses: stefanzweifel/git-auto-commit-action@v7 + with: + commit_message: 'chore: bump version to v${{ steps.extract_version.outputs.version }}' + file_pattern: | + build.gradle.kts + app/package.json + - name: Run production build env: GAMEYFIN_KEYSTORE_PASSWORD: ${{ secrets.GAMEYFIN_KEYSTORE_PASSWORD }} run: ./gradlew clean build -Pvaadin.productionMode=true - - name: Extract tag from branch name - id: extract_tag - run: | - BRANCH_NAME="${GITHUB_REF#refs/heads/}" - TAG="${BRANCH_NAME#release/}-preview" - echo "tag=$TAG" >> $GITHUB_OUTPUT + - name: Publish Test Report + uses: mikepenz/action-junit-report@v6 + if: success() || failure() # always run even if the previous step fails + with: + report_paths: '**/build/test-results/test/TEST-*.xml' - - name: Build and push Docker image + - name: Upload build outputs + uses: actions/upload-artifact@v4 + with: + name: build-outputs + path: | + app/build/libs/** + plugins/**/build/libs/**/*.jar + + docker: + needs: build + runs-on: ubuntu-latest + permissions: + packages: write + strategy: + fail-fast: false + matrix: + variant: [ alpine, ubuntu ] + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Download build outputs + uses: actions/download-artifact@v5 + with: + name: build-outputs + path: . + + - name: Build and push Docker image (${{ matrix.variant }}) uses: ./.github/actions/docker-build-push with: - dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} - dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} ghcr_username: ${{ github.actor }} ghcr_token: ${{ secrets.GITHUB_TOKEN }} context: . dockerfile: docker/Dockerfile platforms: linux/arm64/v8,linux/amd64 - tags: grimsi/gameyfin:${{ steps.extract_tag.outputs.tag }},ghcr.io/gameyfin/gameyfin:${{ steps.extract_tag.outputs.tag }} + tags: ghcr.io/gameyfin/gameyfin:${{ needs.build.outputs.version }} + variant: ${{ matrix.variant }} diff --git a/.github/workflows/image-registry-maintenance.yml b/.github/workflows/image-registry-maintenance.yml new file mode 100644 index 0000000..cf25078 --- /dev/null +++ b/.github/workflows/image-registry-maintenance.yml @@ -0,0 +1,35 @@ +name: GHCR Image Registry Maintenance + +on: + workflow_dispatch: + inputs: + older_than: + description: 'Only remove images older than (e.g. "1 year", leave empty to remove all untagged images)' + required: false + dry_run: + description: 'Dry run?' + required: true + default: true + type: boolean + validate: + description: 'Validate all multi-architecture images in the registry after cleanup?' + required: true + default: false + type: boolean + +jobs: + delete-untagged-images: + runs-on: ubuntu-latest + name: Delete Untagged Images from GHCR + permissions: + packages: write + steps: + - name: Delete untagged, ghost, and orphaned images + uses: dataaxiom/ghcr-cleanup-action@v1 + with: + older-than: ${{ github.event.inputs.older_than }} + dry-run: ${{ github.event.inputs.dry_run }} + validate: ${{ github.event.inputs.validate }} + delete-untagged: true + delete-ghost-images: true + delete-orphaned-images: true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ec690a..acc4545 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,18 +50,20 @@ jobs: jq ".version = \"$RELEASE_VERSION\"" app/package.json > app/package.json.tmp && mv app/package.json.tmp app/package.json - name: Upload modified files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: modified-files path: | build.gradle.kts app/package.json - docker: + build: needs: setup runs-on: ubuntu-latest permissions: + contents: write packages: write + checks: write steps: - name: Checkout code uses: actions/checkout@v5 @@ -69,7 +71,7 @@ jobs: fetch-depth: 0 - name: Download modified files - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: modified-files @@ -86,33 +88,70 @@ jobs: GAMEYFIN_KEYSTORE_PASSWORD: ${{ secrets.GAMEYFIN_KEYSTORE_PASSWORD }} run: ./gradlew clean build -Pvaadin.productionMode=true + - name: Publish Test Report + uses: mikepenz/action-junit-report@v6 + if: success() || failure() # always run even if the previous step fails + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + + - name: Upload build outputs + uses: actions/upload-artifact@v4 + with: + name: build-outputs + path: | + app/build/libs/** + plugins/**/build/libs/**/*.jar + + docker: + needs: [ setup, build ] + runs-on: ubuntu-latest + permissions: + packages: write + strategy: + fail-fast: false + matrix: + variant: [ alpine, ubuntu ] + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Download modified files + uses: actions/download-artifact@v5 + with: + name: modified-files + + - name: Download build outputs + uses: actions/download-artifact@v5 + with: + name: build-outputs + path: . + - name: Generate container image tags id: docker_tags run: | - VERSION="${{ needs.setup.outputs.release_version }}" - DOCKERHUB_TAGS="grimsi/gameyfin:$VERSION" + VERSION='${{ needs.setup.outputs.release_version }}' GHCR_TAGS="ghcr.io/gameyfin/gameyfin:$VERSION" if [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then MAJOR=${BASH_REMATCH[1]} MINOR=${BASH_REMATCH[2]} PATCH=${BASH_REMATCH[3]} - DOCKERHUB_TAGS="grimsi/gameyfin:latest,grimsi/gameyfin:develop,grimsi/gameyfin:$VERSION,grimsi/gameyfin:$MAJOR.$MINOR,grimsi/gameyfin:$MAJOR" GHCR_TAGS="ghcr.io/gameyfin/gameyfin:latest,ghcr.io/gameyfin/gameyfin:develop,ghcr.io/gameyfin/gameyfin:$VERSION,ghcr.io/gameyfin/gameyfin:$MAJOR.$MINOR,ghcr.io/gameyfin/gameyfin:$MAJOR" fi - TAGS="$DOCKERHUB_TAGS,$GHCR_TAGS" + TAGS="$GHCR_TAGS" echo "tags=$TAGS" >> $GITHUB_OUTPUT - - name: Build and push Docker image + - name: Build and push Docker image (${{ matrix.variant }}) uses: ./.github/actions/docker-build-push with: - dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} - dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} ghcr_username: ${{ github.actor }} ghcr_token: ${{ secrets.GITHUB_TOKEN }} context: . dockerfile: docker/Dockerfile platforms: linux/arm64/v8,linux/amd64 tags: ${{ steps.docker_tags.outputs.tags }} + variant: ${{ matrix.variant }} plugin_api: needs: setup @@ -124,7 +163,7 @@ jobs: fetch-depth: 0 - name: Download modified files - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: modified-files @@ -155,13 +194,13 @@ jobs: fetch-depth: 0 - name: Download modified files - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: modified-files - name: Commit version bump if: ${{ github.event.inputs.update_version }} - uses: stefanzweifel/git-auto-commit-action@v6 + uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: 'chore: release v${{ github.event.inputs.version }}' tagging_message: v${{ github.event.inputs.version }} diff --git a/.gitignore b/.gitignore index ffdb52a..4104262 100644 --- a/.gitignore +++ b/.gitignore @@ -48,9 +48,14 @@ out/ /packaged_plugins /logs /templates +/docker/docker-compose.yml /app/src/main/bundles/ /app/src/main/frontend/**/*.js /app/src/main/frontend/**/*.js.map /app/src/main/frontend/generated/ -/torrent_dotfiles/ +**/torrent_dotfiles/ *.state.json +/plugins/data/ +/plugins/state/ +/plugindata/ +/docker-debug/ diff --git a/.run/Gameyfin.run.xml b/.run/Gameyfin.run.xml index a35cfdb..a58e849 100644 --- a/.run/Gameyfin.run.xml +++ b/.run/Gameyfin.run.xml @@ -9,7 +9,7 @@