diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 625b842a..135c7bbc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,12 +4,17 @@ on: push: branches: - "main" + - "beta" workflow_dispatch: concurrency: - group: build + group: build-${{ github.ref_name }} cancel-in-progress: true +env: + CHANNEL: ${{ github.ref_name == 'main' && 'latest' || 'beta' }} + TAG_PREFIX: ${{ github.ref_name != 'main' && 'beta-' || '' }} + jobs: build-amd64: runs-on: ubuntu-24.04 @@ -31,11 +36,11 @@ jobs: file: ./Dockerfile push: true platforms: linux/amd64 - cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-amd64 - cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-amd64,mode=max + cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-${{ env.TAG_PREFIX }}amd64 + cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-${{ env.TAG_PREFIX }}amd64,mode=max tags: | - ghcr.io/${{ github.repository_owner }}/web:latest-amd64 - ghcr.io/${{ github.repository_owner }}/web:${{ github.sha }}-amd64 + ghcr.io/${{ github.repository_owner }}/web:${{ env.CHANNEL }}-amd64 + ghcr.io/${{ github.repository_owner }}/web:${{ env.TAG_PREFIX }}${{ github.sha }}-amd64 build-arm64: runs-on: ubuntu-24.04-arm steps: @@ -56,14 +61,16 @@ jobs: file: ./Dockerfile push: true platforms: linux/arm64 - cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-arm64 - cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-arm64,mode=max + cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-${{ env.TAG_PREFIX }}arm64 + cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/web:buildcache-${{ env.TAG_PREFIX }}arm64,mode=max tags: | - ghcr.io/${{ github.repository_owner }}/web:latest-arm64 - ghcr.io/${{ github.repository_owner }}/web:${{ github.sha }}-arm64 + ghcr.io/${{ github.repository_owner }}/web:${{ env.CHANNEL }}-arm64 + ghcr.io/${{ github.repository_owner }}/web:${{ env.TAG_PREFIX }}${{ github.sha }}-arm64 merge: needs: [build-amd64, build-arm64] runs-on: ubuntu-latest + permissions: + packages: write steps: - name: Log in to GitHub Docker Registry uses: docker/login-action@v3 @@ -73,16 +80,44 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Create manifest list and push run: | - docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/web:latest \ - ghcr.io/${{ github.repository_owner }}/web:latest-amd64 \ - ghcr.io/${{ github.repository_owner }}/web:latest-arm64 - docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/web:${{ github.sha }} \ - ghcr.io/${{ github.repository_owner }}/web:${{ github.sha }}-amd64 \ - ghcr.io/${{ github.repository_owner }}/web:${{ github.sha }}-arm64 - - name: Delete Package Versions - uses: actions/delete-package-versions@v5 + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/web:${{ env.CHANNEL }} \ + ghcr.io/${{ github.repository_owner }}/web:${{ env.CHANNEL }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/web:${{ env.CHANNEL }}-arm64 + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/web:${{ env.TAG_PREFIX }}${{ github.sha }} \ + ghcr.io/${{ github.repository_owner }}/web:${{ env.TAG_PREFIX }}${{ github.sha }}-amd64 \ + ghcr.io/${{ github.repository_owner }}/web:${{ env.TAG_PREFIX }}${{ github.sha }}-arm64 + - name: Prune old images for channel + uses: actions/github-script@v7 with: - package-name: web - package-type: container - min-versions-to-keep: 9 - ignore-versions: '^buildcache-*' \ No newline at end of file + script: | + const channel = context.ref === 'refs/heads/main' ? 'main' : 'beta'; + const keep = 9; + const owner = context.repo.owner; + const pkg = 'web'; + const isBuildcache = (t) => t.startsWith('buildcache'); + const isBeta = (t) => t === 'beta' || t.startsWith('beta-'); + const { data: ownerInfo } = await github.rest.users.getByUsername({ username: owner }); + const isOrg = ownerInfo.type === 'Organization'; + const listFn = isOrg + ? github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg + : github.rest.packages.getAllPackageVersionsForPackageOwnedByUser; + const listArgs = isOrg + ? { package_type: 'container', package_name: pkg, org: owner, per_page: 100 } + : { package_type: 'container', package_name: pkg, username: owner, per_page: 100 }; + const versions = await github.paginate(listFn, listArgs); + const inChannel = versions.filter((v) => { + const tags = v.metadata?.container?.tags || []; + if (tags.length === 0) return false; + if (tags.some(isBuildcache)) return false; + const beta = tags.some(isBeta); + return channel === 'beta' ? beta : !beta; + }); + inChannel.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + const toDelete = inChannel.slice(keep); + for (const v of toDelete) { + await (isOrg + ? github.rest.packages.deletePackageVersionForOrg({ package_type: 'container', package_name: pkg, org: owner, package_version_id: v.id }) + : github.rest.packages.deletePackageVersionForUser({ package_type: 'container', package_name: pkg, username: owner, package_version_id: v.id })); + core.info(`deleted ${pkg} ${v.id} [${(v.metadata?.container?.tags || []).join(', ')}]`); + } + core.info(`channel=${channel} candidates=${inChannel.length} deleted=${toDelete.length}`); diff --git a/composables/useSettingsNav.ts b/composables/useSettingsNav.ts index a458ff26..6e0e6f57 100644 --- a/composables/useSettingsNav.ts +++ b/composables/useSettingsNav.ts @@ -78,6 +78,10 @@ export const SETTINGS_NAV_GROUPS: SettingsNavGroup[] = [ path: "/settings/application/servers", labelKey: "pages.settings.application.servers.title", }, + { + path: "/settings/application/release-channel", + labelKey: "pages.settings.application.release_channel.title", + }, { path: "/settings/application/telemetry", labelKey: "pages.settings.application.telemetry.title", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index a59913e9..fee0730b 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -1772,6 +1772,17 @@ "reserved_disk_space_existing_gb_description": "Minimum disk space in GB required when CS2 is already installed on the node. Set to 0 to disable.", "update_cpu_pinning": "CPU Pinning updated" }, + "release_channel": { + "title": "Release Channel", + "section": "Release Channel", + "description": "Choose which release channel this install tracks. Latest is the stable channel; Beta receives pre-release builds early.", + "latest": "Latest", + "beta": "Beta", + "beta_warning": "Beta is a pre-release channel and may be unstable. Services will be updated to pre-release builds as they become available.", + "service_status": "Service Status", + "fell_back": "beta not available yet — using latest", + "updated": "Release channel updated" + }, "highlights": { "title": "Highlights", "updated": "Updated highlight settings" diff --git a/pages/settings/application/release-channel.vue b/pages/settings/application/release-channel.vue new file mode 100644 index 00000000..21984b6e --- /dev/null +++ b/pages/settings/application/release-channel.vue @@ -0,0 +1,176 @@ + + + + + + + + + + + + + {{ $t("pages.settings.application.release_channel.beta_warning") }} + + + + + + {{ $t("pages.settings.application.release_channel.service_status") }} + + + + {{ service.service }} + + {{ + $t("pages.settings.application.release_channel.fell_back") + }} + + {{ + service.tag + }} + + + + + + + + + +
+ {{ $t("pages.settings.application.release_channel.beta_warning") }} +