From 717a4234491baf183ad9a8ce2cffd553e91158be Mon Sep 17 00:00:00 2001 From: Simon <9295182+grimsi@users.noreply.github.com> Date: Mon, 17 Nov 2025 08:45:39 +0100 Subject: [PATCH] Release v2.2.0 (#741) * Migrate to TailwindCSS v4 (#740) * Remove "material-tailwind" dependencies due to incompatibility of Stepper component with Tailwind v4 * Clean up Tailwind configs before upgrade * Run HeroUI upgrade * Run TailwindCSS upgrade * Replace PostCSS with Vite * Migrate custom styles to v4 * Remove tailwind.config.ts * Add heroui.ts Add tailwind vite plugin * Fix small UI color inconsistency * Fix theming system Rename purple theme to pink * Re-implement stepper in HeroUI * Fix RoleChip colors * Migrate icon names (#743) * Add migration script for phosphor-icons * Migrate icon usages * Update version to 2.2.0-preview * Revert accidental rename of menu title * Bump stefanzweifel/git-auto-commit-action from 6 to 7 (#750) Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v6...v7) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Improve library scanning (#749) * Update script to generate example libraries using SteamSpy API * Refactor library scanning process * Display Flyway startup log by default * Fix race condition in CompanyService * Fix race condition in ImageService Remove obsolete table * Fix SMTP config requiring an email as username (#755) * Disable length limit for config values (#757) * Deprecate DockerHub image (#759) * Remove deprecation warning from web UI * Reworked the CICD pipelines * Optimize container image (#761) * Fix Gradle warning * Rework Docker image to improve layer caching * Bump stefanzweifel/git-auto-commit-action from 6 to 7 (#765) Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 6 to 7. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v6...v7) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Multi platform support (#764) * Remove migrate-phosphor-icons.js since migration has been successful * Refactor GameMetadata into separate files * Add Platform enum * Implement platform support in Plugin API * Implement platform support in Steam Plugin * Implement platform support in IGDB Plugin * Add database migration for platform support * Implement platform support in GameService * Implement platform support on most endpoints and features, some are still missing Implemented platform support in all bundled plugins (although not finished polishing yet) * Implement platforms in UI * Make GameRequest platform aware * Return headerImages from IGDB * Implement proper PlatformMapper for IGDB plugin * Fix various smaller issues and inconsistencies * Replace placeholder in LibraryOverviewCard (#767) * Bump actions/download-artifact from 5 to 6 (#769) * Bump actions/upload-artifact from 4 to 5 (#770) * Multi platform support (#773) * Fix bug in Plugin API related to state loading/saving * Hide Flyway query logs by default * Extend migration script for multi platform tables * Plugins now store their data and state in ./plugindata * Add "plugindata" directory to entrypoint scripts * Improve download handling (#756) * Process download in background thread to avoid session timeout affecting it * Increase default session timeout to 24h * Use virtual thread pool for download task in background * Make KSP extensions.idx generation more robust * Implement download bandwidth limiter Implement SliderInput Refactor NumberInput * Implement download bandwidth throttling Implement real-time download monitoring * Improve UI for DownloadManagement Track more stats in SessionStats * Update Hilla Use React 19 * Implement real-time graph to track bandwidth usage Implement downloaded data sum over last day Small bug fixes Small refactorings * Update docker-compose.example.yml * Improve DownloadSessionCard (#784) * Fix unit on y-axis of download graph * Show game size and library in tooltip Make game chips interactive in DownloadSessionCard (leads to game page when clicked) Optimize graph settings * Migrate torrent plugin to libtorrent (#775) * Disable TorrentDownloadPlugin in Alpine based Docker image * Improve test coverage (#785) * Fix potential divide by zero bug * Add mockk dependency * Add tests for org.gameyfin.app.core.download * Add tests for Filesytem package Fix DownloadServiceTest * Fix FilesystemServiceTest * Add tests for "job" package * Upgrade Gradle wrapper Enable Gradle config cache * Added more tests * Added tests for the "security" package * Add tests for "game" package * Fix AsyncFileTailer not shutting down properly on Windows * Fix GameServiceTest * Added tests for "libraries" package * Added tests for "media" package * Fix warning in ImageService * Add tests fpr "messages" package Make sure transport is closed even in case an exception is thrown * Add tests for "platforms" package * Add tests for "requests" package * Moved "token" package to "core" package (from "shared") * Add tests for "token" package * Fix issue in RoleEnum.safeValueOf() throwing Exception * Fix potential issue in UserEndpoint.getUserInfo() when auth is null * Added tests for "user" package * Migrate package for "token" in FE * Publish test report in CI * Fix workflow permissions * Remove test because of timing issue in CI * Replaced "unmatched paths" with "ignored paths" (#791) * Use new "AutoComplete" component (#793) * Use ArrayInputAutocomplete in EditGameMetadataModal * Add test for getEnumPropertyValues --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .dockerignore | 39 + .github/actions/docker-build-push/action.yml | 23 +- .../workflows/docker-delete-tag-on-merge.yml | 55 +- .github/workflows/docker-develop.yml | 44 - .github/workflows/docker-fix.yml | 44 +- .github/workflows/docker-preview.yml | 81 +- .../workflows/image-registry-maintenance.yml | 35 + .github/workflows/release.yml | 65 +- .gitignore | 7 +- .run/Gameyfin.run.xml | 2 +- .run/Rebuild all.run.xml | 2 +- .run/UI debug.run.xml | 2 +- app/build.gradle.kts | 11 +- app/heroui.ts | 7 - app/package-lock.json | 11436 ++++++++-------- app/package.json | 297 +- app/postcss.config.js | 6 - app/src/main/bundles/prod.bundle | Bin 2057800 -> 1958968 bytes app/src/main/frontend/App.tsx | 10 +- .../main/frontend/components/ProfileMenu.tsx | 10 +- .../administration/ConfigFormField.tsx | 19 +- .../administration/DownloadManagement.tsx | 84 + .../administration/LibraryManagement.tsx | 6 +- .../administration/LogManagement.tsx | 8 +- .../administration/MessageManagement.tsx | 15 +- .../administration/PluginManagement.tsx | 2 +- .../administration/ProfileManagement.tsx | 16 +- .../administration/SsoManagement.tsx | 6 +- .../administration/UserManagement.tsx | 8 +- .../administration/withConfigPage.tsx | 10 +- .../frontend/components/general/ChipList.tsx | 46 + .../general/IconBackgroundPattern.tsx | 73 +- .../frontend/components/general/RoleChip.tsx | 2 +- .../general/ScanProgressPopover.tsx | 6 +- .../frontend/components/general/SearchBar.tsx | 6 +- .../general/cards/DownloadSessionCard.tsx | 136 + .../general/cards/LibraryOverviewCard.tsx | 13 +- .../general/cards/PluginManagementCard.tsx | 47 +- .../general/cards/UserManagementCard.tsx | 6 +- .../components/general/covers/CoverRow.tsx | 8 +- .../components/general/covers/GameCover.tsx | 2 +- .../general/covers/ImageCarousel.tsx | 14 +- .../components/general/input/ArrayInput.tsx | 4 +- .../general/input/ArrayInputAutocomplete.tsx | 100 + .../components/general/input/ComboButton.tsx | 6 +- .../general/input/DatePickerInput.tsx | 2 +- .../general/input/DirectoryMappingInput.tsx | 12 +- .../components/general/input/FileTreeView.tsx | 11 +- .../general/input/GameCoverPicker.tsx | 10 +- .../general/input/GameHeaderPicker.tsx | 8 +- .../components/general/input/Input.tsx | 6 +- .../components/general/input/NumberInput.tsx | 26 + .../components/general/input/SelectInput.tsx | 2 +- .../components/general/input/SliderInput.tsx | 23 + .../general/input/TextAreaInput.tsx | 2 +- .../library/LibraryManagementDetails.tsx | 12 +- .../library/LibraryManagementGames.tsx | 17 +- ....tsx => LibraryManagementIgnoredPaths.tsx} | 78 +- .../general/modals/AssignRolesModal.tsx | 2 +- .../general/modals/EditGameMetadataModal.tsx | 38 +- .../general/modals/GameCoverPickerModal.tsx | 12 +- .../general/modals/GameHeaderPickerModal.tsx | 12 +- .../general/modals/InviteUserModal.tsx | 2 +- .../general/modals/LibraryCreationModal.tsx | 16 +- .../general/modals/MatchGameModal.tsx | 11 +- .../general/modals/PasswordResetModal.tsx | 4 +- .../modals/PasswortResetTokenModal.tsx | 2 +- .../general/modals/PathPickerModal.tsx | 4 +- .../general/modals/PluginDetailsModal.tsx | 4 +- .../general/modals/PluginPrioritiesModal.tsx | 6 +- .../general/modals/RequestGameModal.tsx | 38 +- .../components/general/plugin/PluginIcon.tsx | 4 +- .../plugin/PluginManagementSection.tsx | 6 +- .../temp/DockerHubDeprecationPopover.tsx | 44 - .../components/theming/ThemePreview.tsx | 2 +- .../frontend/components/wizard/Stepper.tsx | 208 + .../frontend/components/wizard/Wizard.tsx | 90 +- app/src/main/frontend/heroui.ts | 10 + app/src/main/frontend/main.css | 86 +- app/src/main/frontend/routes.tsx | 6 + .../frontend/state/DownloadSessionState.ts | 81 + app/src/main/frontend/state/PlatformState.ts | 47 + app/src/main/frontend/state/UserState.ts | 32 + app/src/main/frontend/theming/themes.ts | 4 +- .../theming/themes/{purple.ts => pink.ts} | 4 +- app/src/main/frontend/util/utils.ts | 79 +- .../frontend/views/AdministrationView.tsx | 33 +- .../frontend/views/EmailConfirmationView.tsx | 14 +- app/src/main/frontend/views/ErrorView.tsx | 47 +- .../main/frontend/views/GameRequestView.tsx | 23 +- app/src/main/frontend/views/GameView.tsx | 74 +- .../views/InvitationRegistrationView.tsx | 8 +- .../frontend/views/LibraryManagementView.tsx | 10 +- app/src/main/frontend/views/LoginView.tsx | 2 +- app/src/main/frontend/views/MainLayout.tsx | 34 +- .../main/frontend/views/PasswordResetView.tsx | 12 +- app/src/main/frontend/views/ProfileView.tsx | 6 +- app/src/main/frontend/views/SearchView.tsx | 30 +- app/src/main/frontend/views/SetupView.tsx | 12 +- .../org/gameyfin/app/GameyfinApplication.kt | 2 - .../gameyfin/app/config/ConfigProperties.kt | 29 +- .../org/gameyfin/app/config/ConfigService.kt | 5 +- .../gameyfin/app/config/dto/ConfigEntryDto.kt | 5 +- .../app/config/entities/ConfigEntry.kt | 1 + .../main/kotlin/org/gameyfin/app/core/Role.kt | 7 +- .../kotlin/org/gameyfin/app/core/Utils.kt | 93 +- .../gameyfin/app/core/config/AsyncConfig.kt | 18 + .../gameyfin/app/core/config/JacksonConfig.kt | 48 + .../app/core/download/DownloadService.kt | 39 - .../BandwidthMaintenanceScheduler.kt | 37 + .../bandwidth/BandwidthMonitoringEndpoint.kt | 35 + .../bandwidth/BandwidthMonitoringService.kt | 83 + .../bandwidth/SessionBandwidthManager.kt | 78 + .../bandwidth/SessionBandwidthTracker.kt | 222 + .../bandwidth/SessionMonitoredOutputStream.kt | 53 + .../core/download/bandwidth/SessionStats.kt | 37 + .../download/bandwidth/SessionStatsDto.kt | 23 + .../bandwidth/SessionThrottledOutputStream.kt | 65 + .../download/{ => files}/DownloadEndpoint.kt | 31 +- .../core/download/files/DownloadService.kt | 119 + .../{ => provider}/DownloadProviderDto.kt | 2 +- .../DownloadProviderEndpoint.kt | 3 +- .../org/gameyfin/app/core/events/Events.kt | 8 +- .../core/filesystem/FilesystemScanResult.kt | 3 +- .../app/core/filesystem/FilesystemService.kt | 15 +- .../interceptors/EntityUpdateInterceptor.kt | 6 + .../app/core/logging/util/AsyncFileTailer.kt | 1 + .../app/core/plugins/PluginService.kt | 20 +- .../GameyfinDevelopmentPluginLoader.kt | 7 +- .../management/GameyfinJarPluginLoader.kt | 8 +- .../GameyfinManifestPluginDescriptorFinder.kt | 2 +- .../management/GameyfinPluginClassLoader.kt | 4 +- .../management/GameyfinPluginManager.kt | 36 +- .../plugins/management/PluginManagerConfig.kt | 2 + .../management/SpringPluginStateListener.kt | 17 + .../app/core/security/AppKeyValidator.kt | 9 +- ...DynamicPublicAccessAuthorizationManager.kt | 11 +- .../app/core/security/EncryptionUtils.kt | 15 +- ...yDeseriazlizer.kt => ArrayDeserializer.kt} | 0 .../serialization/DisplayableSerializer.kt | 25 + .../serialization/GameFeatureDeserializer.kt | 23 + .../core/serialization/GenreDeserializer.kt | 22 + .../serialization/PlatformDeserializer.kt | 23 + .../PlayerPerspectiveDeserializer.kt | 23 + .../core/serialization/ThemeDeserializer.kt | 23 + .../app/{shared => core}/token/Token.kt | 4 +- .../app/{shared => core}/token/TokenDto.kt | 2 +- .../{shared => core}/token/TokenRepository.kt | 2 +- .../{shared => core}/token/TokenService.kt | 2 +- .../app/{shared => core}/token/TokenType.kt | 2 +- .../token/TokenTypeUserType.kt | 2 +- .../token/TokenValidationResult.kt | 2 +- .../org/gameyfin/app/games/CompanyService.kt | 13 +- .../org/gameyfin/app/games/GameEndpoint.kt | 19 +- .../org/gameyfin/app/games/GameService.kt | 169 +- .../org/gameyfin/app/games/dto/GameDto.kt | 28 +- .../games/dto/GameEnumPropertyValuesDto.kt | 13 + .../app/games/dto/GameSearchResultDto.kt | 2 + .../gameyfin/app/games/dto/GameUpdateDto.kt | 10 +- .../org/gameyfin/app/games/entities/Game.kt | 21 +- .../app/games/extensions/GameExtensions.kt | 26 +- .../app/games/repositories/GameRepository.kt | 11 +- .../app/games/repositories/ImageRepository.kt | 2 +- .../app/libraries/IgnoredPathRepository.kt | 9 + .../app/libraries/LibraryCoreService.kt | 49 +- .../app/libraries/LibraryRepository.kt | 4 + .../app/libraries/LibraryScanService.kt | 299 +- .../gameyfin/app/libraries/LibraryService.kt | 39 +- .../app/libraries/dto/IgnoredPathDto.kt | 17 + .../gameyfin/app/libraries/dto/LibraryDto.kt | 4 +- .../app/libraries/dto/LibraryScanResult.kt | 2 +- .../app/libraries/dto/LibraryUpdateDto.kt | 5 +- .../app/libraries/entities/IgnoredPath.kt | 49 + .../app/libraries/entities/Library.kt | 11 +- .../entities/LibraryEntityListener.kt | 7 + .../extensions/IgnoredPathExtensions.kt | 55 + .../libraries/extensions/LibraryExtensions.kt | 3 +- .../scan/CalculateFilesizesResult.kt | 7 - .../libraries/scan/DownloadImagesResult.kt | 7 - .../app/libraries/scan/FinishScanResult.kt | 7 - .../libraries/scan/LibraryGameProcessor.kt | 88 + .../app/libraries/scan/MatchNewGamesResult.kt | 3 +- .../org/gameyfin/app/media/ImageService.kt | 37 +- .../providers/EmailMessageProvider.kt | 27 +- .../app/platforms/PlatformEndpoint.kt | 23 + .../gameyfin/app/platforms/PlatformService.kt | 134 + .../app/platforms/dto/PlatformStatsDto.kt | 11 + .../app/requests/GameRequestEndpoint.kt | 4 +- .../app/requests/GameRequestRepository.kt | 19 +- .../app/requests/GameRequestService.kt | 71 +- .../requests/dto/GameRequestCreationDto.kt | 2 + .../app/requests/dto/GameRequestDto.kt | 2 + .../app/requests/entities/GameRequest.kt | 5 + .../extensions/GameRequestExtensions.kt | 1 + .../org/gameyfin/app/users/UserEndpoint.kt | 7 +- .../org/gameyfin/app/users/UserService.kt | 9 + .../EmailConfirmationEndpoint.kt | 2 +- .../EmailConfirmationService.kt | 14 +- .../passwordreset/PasswordResetEndpoint.kt | 6 +- .../passwordreset/PasswordResetService.kt | 11 +- .../users/registration/InvitationService.kt | 8 +- .../registration/RegistrationEndpoint.kt | 4 +- app/src/main/resources/application.yml | 8 +- ...1__Remove_obsolete_library_games_table.sql | 18 + ...Disable_length_limit_for_config_values.sql | 8 + .../V2.2.0.3__Multi_platform_support.sql | 46 + ...actor_unmatched_paths_to_ignored_paths.sql | 75 + .../resources/vaadin-featureflags.properties | 1 + .../BandwidthMaintenanceSchedulerTest.kt | 200 + .../BandwidthMonitoringEndpointTest.kt | 287 + .../BandwidthMonitoringServiceTest.kt | 466 + .../bandwidth/SessionBandwidthManagerTest.kt | 442 + .../bandwidth/SessionBandwidthTrackerTest.kt | 418 + .../SessionMonitoredOutputStreamTest.kt | 370 + .../download/bandwidth/SessionStatsDtoTest.kt | 267 + .../download/bandwidth/SessionStatsTest.kt | 409 + .../SessionThrottledOutputStreamTest.kt | 488 + .../download/files/DownloadEndpointTest.kt | 424 + .../download/files/DownloadServiceTest.kt | 429 + .../provider/DownloadProviderDtoTest.kt | 337 + .../provider/DownloadProviderEndpointTest.kt | 340 + .../core/filesystem/FilesystemEndpointTest.kt | 137 + .../core/filesystem/FilesystemServiceTest.kt | 659 + .../app/core/jobs/JobRunResultTest.kt | 315 + .../gameyfin/app/core/jobs/JobServiceTest.kt | 250 + .../app/core/jobs/LibraryScanJobTest.kt | 141 + .../app/core/logging/LogEndpointTest.kt | 257 + .../core/logging/util/AsyncFileTailerTest.kt | 362 + .../app/core/plugins/PluginEndpointTest.kt | 220 + .../app/core/plugins/PluginServiceTest.kt | 0 .../plugins/config/PluginConfigEntryTest.kt | 148 + .../plugins/dto/ExternalProviderIdDtoTest.kt | 57 + .../dto/PluginConfigMetadataDtoTest.kt | 149 + .../app/core/plugins/dto/PluginDtoTest.kt | 247 + .../core/plugins/dto/PluginUpdateDtoTest.kt | 144 + .../DatabasePluginStatusProviderTest.kt | 158 + .../GameyfinDevelopmentPluginLoaderTest.kt | 75 + .../management/GameyfinExtensionFinderTest.kt | 209 + .../management/GameyfinJarPluginLoaderTest.kt | 165 + ...eyfinManifestPluginDescriptorFinderTest.kt | 286 + .../GameyfinPluginClassLoaderTest.kt | 97 + .../GameyfinPluginDescriptorTest.kt | 266 + .../management/GameyfinPluginManagerTest.kt | 593 + .../management/PluginManagementEntryTest.kt | 141 + .../management/PluginManagerConfigTest.kt | 127 + .../SpringPluginStateListenerTest.kt | 168 + .../app/core/security/AppKeyValidatorTest.kt | 61 + .../security/AuthorityMapperConfigTest.kt | 102 + .../CustomAuthenticationEntryPointTest.kt | 115 + ...micPublicAccessAuthorizationManagerTest.kt | 204 + .../core/security/EncryptionConverterTest.kt | 186 + .../security/EncryptionMapConverterTest.kt | 254 + .../app/core/security/EncryptionUtilsTest.kt | 243 + .../security/PasswordEncoderConfigTest.kt | 98 + .../core/security/RoleHierarchyConfigTest.kt | 137 + .../app/core/security/SecurityUtilsTest.kt | 271 + .../security/SessionRegistryConfigTest.kt | 46 + .../core/security/SsoEnabledConditionTest.kt | 221 + .../gameyfin/app/core/token/TokenDtoTest.kt | 181 + .../app/core/token/TokenServiceTest.kt | 436 + .../org/gameyfin/app/core/token/TokenTest.kt | 131 + .../gameyfin/app/core/token/TokenTypeTest.kt | 56 + .../app/core/token/TokenTypeUserTypeTest.kt | 296 + .../gameyfin/app/games/CompanyServiceTest.kt | 164 + .../gameyfin/app/games/GameEndpointTest.kt | 308 + .../org/gameyfin/app/games/GameServiceTest.kt | 1250 ++ .../app/games/NoMatchExceptionTest.kt | 66 + .../games/extensions/GameExtensionsTest.kt | 356 + .../app/libraries/LibraryCoreServiceTest.kt | 238 + .../app/libraries/LibraryEndpointTest.kt | 257 + .../app/libraries/LibraryScanServiceTest.kt | 363 + .../app/libraries/LibraryServiceTest.kt | 487 + .../extensions/LibraryExtensionsTest.kt | 331 + .../scan/LibraryGameProcessorTest.kt | 350 + .../gameyfin/app/media/ImageEndpointTest.kt | 510 + .../gameyfin/app/media/ImageServiceTest.kt | 676 + .../app/messages/MessageEndpointTest.kt | 161 + .../app/messages/MessageServiceTest.kt | 647 + .../providers/EmailMessageProviderTest.kt | 365 + .../templates/MessageTemplateEndpointTest.kt | 235 + .../templates/MessageTemplateServiceTest.kt | 458 + .../app/platforms/PlatformEndpointTest.kt | 118 + .../app/platforms/PlatformServiceTest.kt | 445 + .../serialization/PlatformDeserializerTest.kt | 315 + .../serialization/PlatformSerializerTest.kt | 219 + .../app/requests/GameRequestEndpointTest.kt | 231 + .../app/requests/GameRequestServiceTest.kt | 859 ++ .../entities/GameRequestEntityListenerTest.kt | 234 + .../extensions/GameRequestExtensionsTest.kt | 343 + .../org/gameyfin/app/users/RoleServiceTest.kt | 379 + .../gameyfin/app/users/SessionServiceTest.kt | 157 + .../gameyfin/app/users/UserEndpointTest.kt | 393 + .../org/gameyfin/app/users/UserServiceTest.kt | 1068 ++ .../EmailConfirmationEndpointTest.kt | 192 + .../EmailConfirmationServiceTest.kt | 227 + .../users/extensions/UserExtensionsTest.kt | 95 + .../PasswordResetEndpointTest.kt | 241 + .../passwordreset/PasswordResetServiceTest.kt | 422 + .../UserPreferencesEndpointTest.kt | 53 + .../preferences/UserPreferencesServiceTest.kt | 135 + .../registration/InvitationServiceTest.kt | 228 + .../registration/RegistrationEndpointTest.kt | 114 + app/tailwind.config.ts | 28 - app/vite.config.ts | 3 +- build.gradle.kts | 29 +- docker/Dockerfile | 65 +- docker/Dockerfile.ubuntu | 66 +- docker/docker-compose.example.yml | 15 +- docker/entrypoint.sh | 26 +- docker/entrypoint.ubuntu.sh | 23 +- docker/how-to-build.txt | 2 +- gradle.properties | 8 +- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45633 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +- gradlew.bat | 3 +- plugin-api/build.gradle.kts | 3 + .../wrapper/ConfigurableGameyfinPlugin.kt | 7 +- .../pluginapi/core/wrapper/GameyfinPlugin.kt | 45 +- .../pluginapi/gamemetadata/GameFeature.kt | 33 + .../pluginapi/gamemetadata/GameMetadata.kt | 106 +- .../gamemetadata/GameMetadataProvider.kt | 11 +- .../gameyfin/pluginapi/gamemetadata/Genre.kt | 35 + .../pluginapi/gamemetadata/Platform.kt | 277 + .../gamemetadata/PlayerPerspective.kt | 17 + .../gameyfin/pluginapi/gamemetadata/Theme.kt | 32 + plugins/build.gradle.kts | 35 +- .../download/direct/CompressionMode.kt | 12 +- .../download/direct/DirectDownloadPlugin.kt | 5 +- .../src/main/resources/MANIFEST.MF | 2 +- plugins/igdb/build.gradle.kts | 16 +- .../plugins/metadata/igdb/IgdbPlugin.kt | 49 +- .../gameyfin/plugins/metadata/igdb/Mapper.kt | 137 - .../metadata/igdb/mapper/GameFeatureMapper.kt | 33 + .../metadata/igdb/mapper/GenreMapper.kt | 42 + .../metadata/igdb/mapper/MediaMapper.kt | 31 + .../metadata/igdb/mapper/PlatformMapper.kt | 290 + .../igdb/mapper/PlayerPerspectiveMapper.kt | 27 + .../metadata/igdb/mapper/ThemeMapper.kt | 43 + plugins/igdb/src/main/resources/MANIFEST.MF | 2 +- .../igdb/mapper/PlatformMapperTest.kt | 189 + plugins/steam/build.gradle.kts | 12 + .../plugins/metadata/steam/SteamPlugin.kt | 157 +- .../metadata/steam/dto/SteamGameDetails.kt | 1 + .../metadata/steam/dto/SteamGameOverview.kt | 3 +- .../metadata/steam/dto/SteamPlatforms.kt | 10 + plugins/steam/src/main/resources/MANIFEST.MF | 2 +- .../metadata/steamgriddb/SteamGridDbPlugin.kt | 19 +- plugins/torrentdownload/build.gradle.kts | 44 +- .../plugins/download/torrent/TorrentClient.kt | 211 + .../torrent/TorrentClientPerformanceMode.kt | 8 + .../download/torrent/TorrentDownloadPlugin.kt | 323 +- .../download/torrent/TorrentTracker.kt | 437 + .../download/torrent/TorrentVersion.kt | 7 + .../gameyfin/plugins/download/torrent/Util.kt | 15 + .../src/main/resources/MANIFEST.MF | 2 +- scripts/steam.sh | 77 +- 357 files changed, 39213 insertions(+), 7918 deletions(-) create mode 100644 .dockerignore delete mode 100644 .github/workflows/docker-develop.yml create mode 100644 .github/workflows/image-registry-maintenance.yml delete mode 100644 app/heroui.ts delete mode 100644 app/postcss.config.js create mode 100644 app/src/main/frontend/components/administration/DownloadManagement.tsx create mode 100644 app/src/main/frontend/components/general/ChipList.tsx create mode 100644 app/src/main/frontend/components/general/cards/DownloadSessionCard.tsx create mode 100644 app/src/main/frontend/components/general/input/ArrayInputAutocomplete.tsx create mode 100644 app/src/main/frontend/components/general/input/NumberInput.tsx create mode 100644 app/src/main/frontend/components/general/input/SliderInput.tsx rename app/src/main/frontend/components/general/library/{LibraryManagementUnmatchedPaths.tsx => LibraryManagementIgnoredPaths.tsx} (59%) delete mode 100644 app/src/main/frontend/components/temp/DockerHubDeprecationPopover.tsx create mode 100644 app/src/main/frontend/components/wizard/Stepper.tsx create mode 100644 app/src/main/frontend/heroui.ts create mode 100644 app/src/main/frontend/state/DownloadSessionState.ts create mode 100644 app/src/main/frontend/state/PlatformState.ts create mode 100644 app/src/main/frontend/state/UserState.ts rename app/src/main/frontend/theming/themes/{purple.ts => pink.ts} (93%) create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/config/AsyncConfig.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/config/JacksonConfig.kt delete mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/DownloadService.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMaintenanceScheduler.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringEndpoint.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringService.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthManager.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthTracker.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/SessionMonitoredOutputStream.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/SessionStats.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/SessionStatsDto.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/bandwidth/SessionThrottledOutputStream.kt rename app/src/main/kotlin/org/gameyfin/app/core/download/{ => files}/DownloadEndpoint.kt (54%) create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/download/files/DownloadService.kt rename app/src/main/kotlin/org/gameyfin/app/core/download/{ => provider}/DownloadProviderDto.kt (84%) rename app/src/main/kotlin/org/gameyfin/app/core/download/{ => provider}/DownloadProviderEndpoint.kt (79%) create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/plugins/management/SpringPluginStateListener.kt rename app/src/main/kotlin/org/gameyfin/app/core/serialization/{ArrayDeseriazlizer.kt => ArrayDeserializer.kt} (100%) create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/serialization/DisplayableSerializer.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/serialization/GameFeatureDeserializer.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/serialization/GenreDeserializer.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/serialization/PlatformDeserializer.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/serialization/PlayerPerspectiveDeserializer.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/serialization/ThemeDeserializer.kt rename app/src/main/kotlin/org/gameyfin/app/{shared => core}/token/Token.kt (96%) rename app/src/main/kotlin/org/gameyfin/app/{shared => core}/token/TokenDto.kt (92%) rename app/src/main/kotlin/org/gameyfin/app/{shared => core}/token/TokenRepository.kt (91%) rename app/src/main/kotlin/org/gameyfin/app/{shared => core}/token/TokenService.kt (98%) rename app/src/main/kotlin/org/gameyfin/app/{shared => core}/token/TokenType.kt (91%) rename app/src/main/kotlin/org/gameyfin/app/{shared => core}/token/TokenTypeUserType.kt (98%) rename app/src/main/kotlin/org/gameyfin/app/{shared => core}/token/TokenValidationResult.kt (63%) create mode 100644 app/src/main/kotlin/org/gameyfin/app/games/dto/GameEnumPropertyValuesDto.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/IgnoredPathRepository.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/dto/IgnoredPathDto.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/entities/IgnoredPath.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/extensions/IgnoredPathExtensions.kt delete mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/scan/CalculateFilesizesResult.kt delete mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/scan/DownloadImagesResult.kt delete mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/scan/FinishScanResult.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/libraries/scan/LibraryGameProcessor.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/platforms/PlatformEndpoint.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/platforms/PlatformService.kt create mode 100644 app/src/main/kotlin/org/gameyfin/app/platforms/dto/PlatformStatsDto.kt create mode 100644 app/src/main/resources/db/migration/V2.2.0.1__Remove_obsolete_library_games_table.sql create mode 100644 app/src/main/resources/db/migration/V2.2.0.2__Disable_length_limit_for_config_values.sql create mode 100644 app/src/main/resources/db/migration/V2.2.0.3__Multi_platform_support.sql create mode 100644 app/src/main/resources/db/migration/V2.2.0.4__Refactor_unmatched_paths_to_ignored_paths.sql create mode 100644 app/src/main/resources/vaadin-featureflags.properties create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMaintenanceSchedulerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/BandwidthMonitoringServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthManagerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionBandwidthTrackerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionMonitoredOutputStreamTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionStatsDtoTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionStatsTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/bandwidth/SessionThrottledOutputStreamTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/files/DownloadEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/files/DownloadServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/provider/DownloadProviderDtoTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/download/provider/DownloadProviderEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/filesystem/FilesystemEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/filesystem/FilesystemServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/jobs/JobRunResultTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/jobs/JobServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/jobs/LibraryScanJobTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/logging/LogEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/logging/util/AsyncFileTailerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/PluginEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/PluginServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/config/PluginConfigEntryTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/dto/ExternalProviderIdDtoTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/dto/PluginConfigMetadataDtoTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/dto/PluginDtoTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/dto/PluginUpdateDtoTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/DatabasePluginStatusProviderTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinDevelopmentPluginLoaderTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinExtensionFinderTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinJarPluginLoaderTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinManifestPluginDescriptorFinderTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginClassLoaderTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginDescriptorTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/GameyfinPluginManagerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/PluginManagementEntryTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/PluginManagerConfigTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/plugins/management/SpringPluginStateListenerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/AppKeyValidatorTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/AuthorityMapperConfigTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/CustomAuthenticationEntryPointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManagerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/EncryptionConverterTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/EncryptionMapConverterTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/EncryptionUtilsTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/PasswordEncoderConfigTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/RoleHierarchyConfigTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/SecurityUtilsTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/SessionRegistryConfigTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/security/SsoEnabledConditionTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/token/TokenDtoTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/token/TokenServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/token/TokenTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/token/TokenTypeTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/core/token/TokenTypeUserTypeTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/games/CompanyServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/games/GameEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/games/GameServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/games/NoMatchExceptionTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/games/extensions/GameExtensionsTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/libraries/LibraryCoreServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/libraries/LibraryEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/libraries/LibraryScanServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/libraries/LibraryServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/libraries/extensions/LibraryExtensionsTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/libraries/scan/LibraryGameProcessorTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/media/ImageEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/media/ImageServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/messages/MessageEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/messages/MessageServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/messages/providers/EmailMessageProviderTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/messages/templates/MessageTemplateEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/messages/templates/MessageTemplateServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/platforms/PlatformEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/platforms/PlatformServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformDeserializerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/platforms/serialization/PlatformSerializerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/requests/GameRequestEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/requests/GameRequestServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/requests/entities/GameRequestEntityListenerTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensionsTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/RoleServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/SessionServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/UserEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/UserServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/extensions/UserExtensionsTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/passwordreset/PasswordResetServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/preferences/UserPreferencesEndpointTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/preferences/UserPreferencesServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/registration/InvitationServiceTest.kt create mode 100644 app/src/test/kotlin/org/gameyfin/app/users/registration/RegistrationEndpointTest.kt delete mode 100644 app/tailwind.config.ts create mode 100644 plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/GameFeature.kt create mode 100644 plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/Genre.kt create mode 100644 plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/Platform.kt create mode 100644 plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/PlayerPerspective.kt create mode 100644 plugin-api/src/main/kotlin/org/gameyfin/pluginapi/gamemetadata/Theme.kt delete mode 100644 plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/Mapper.kt create mode 100644 plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/mapper/GameFeatureMapper.kt create mode 100644 plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/mapper/GenreMapper.kt create mode 100644 plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/mapper/MediaMapper.kt create mode 100644 plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/mapper/PlatformMapper.kt create mode 100644 plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/mapper/PlayerPerspectiveMapper.kt create mode 100644 plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/mapper/ThemeMapper.kt create mode 100644 plugins/igdb/src/test/kotlin/org/gameyfin/plugins/metadata/igdb/mapper/PlatformMapperTest.kt create mode 100644 plugins/steam/src/main/kotlin/org/gameyfin/plugins/metadata/steam/dto/SteamPlatforms.kt create mode 100644 plugins/torrentdownload/src/main/kotlin/org/gameyfin/plugins/download/torrent/TorrentClient.kt create mode 100644 plugins/torrentdownload/src/main/kotlin/org/gameyfin/plugins/download/torrent/TorrentClientPerformanceMode.kt create mode 100644 plugins/torrentdownload/src/main/kotlin/org/gameyfin/plugins/download/torrent/TorrentTracker.kt create mode 100644 plugins/torrentdownload/src/main/kotlin/org/gameyfin/plugins/download/torrent/TorrentVersion.kt mode change 100644 => 100755 scripts/steam.sh 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 @@