From 4233ebcbb1b580907cb23fe3ab6399193475701e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 15 Aug 2026 20:17:51 +0000 Subject: [PATCH 1/4] ci: Drop AWS S3 release upload, keep only Cloudflare R2 (#1092) The release pipeline uploaded artifacts to both AWS S3 (goreleaser `blobs:`) and Cloudflare R2 (custom publisher) during the migration period. The migration is done, so this removes the S3 half: - drop the `blobs:` block from `.goreleaser.yaml` - drop the `AWS_*` / `S3_*` env from the nightly and tag release workflows - update the comments in `.goreleaser.yaml` and `scripts/upload-r2.sh` accordingly Cloudflare R2 upload (including the early `--check-config` preflight step) is unchanged, and the `AWS_*` repo secrets are no longer used. Reviewed-on: https://gitea.com/gitea/tea/pulls/1092 Reviewed-by: bircni Co-authored-by: Lunny Xiao --- .gitea/workflows/release-nightly.yml | 12 +++--------- .gitea/workflows/release-tag.yml | 12 +++--------- .goreleaser.yaml | 25 +++++++------------------ scripts/upload-r2.sh | 12 ++++-------- 4 files changed, 17 insertions(+), 44 deletions(-) diff --git a/.gitea/workflows/release-nightly.yml b/.gitea/workflows/release-nightly.yml index 7aa0a279..b86c3df2 100644 --- a/.gitea/workflows/release-nightly.yml +++ b/.gitea/workflows/release-nightly.yml @@ -12,11 +12,10 @@ jobs: with: fetch-depth: 0 - run: git fetch --force --tags - # Custom publishers (the R2 mirror below) run as the very last + # Custom publishers (the R2 upload below) run as the very last # step of goreleaser's publish pipeline, after the Gitea release - # has already been created and every artifact already uploaded - # to S3. Fail here instead, before anything is built or - # published, if the R2 secrets are missing. + # has already been created. Fail here instead, before anything + # is built or published, if the R2 secrets are missing. - name: check R2 configuration run: sh scripts/upload-r2.sh --check-config env: @@ -46,11 +45,6 @@ jobs: env: SDK_VERSION: ${{ steps.sdk_version.outputs.version }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - S3_REGION: ${{ secrets.AWS_REGION }} - S3_BUCKET: ${{ secrets.AWS_BUCKET }} R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} R2_BUCKET: ${{ secrets.R2_BUCKET }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} diff --git a/.gitea/workflows/release-tag.yml b/.gitea/workflows/release-tag.yml index 67821d19..0d814aae 100644 --- a/.gitea/workflows/release-tag.yml +++ b/.gitea/workflows/release-tag.yml @@ -13,11 +13,10 @@ jobs: with: fetch-depth: 0 - run: git fetch --force --tags - # Custom publishers (the R2 mirror below) run as the very last + # Custom publishers (the R2 upload below) run as the very last # step of goreleaser's publish pipeline, after the Gitea release - # has already been created and every artifact already uploaded - # to S3. Fail here instead, before anything is built or - # published, if the R2 secrets are missing. + # has already been created. Fail here instead, before anything + # is built or published, if the R2 secrets are missing. - name: check R2 configuration run: sh scripts/upload-r2.sh --check-config env: @@ -47,11 +46,6 @@ jobs: env: SDK_VERSION: ${{ steps.sdk_version.outputs.version }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - S3_REGION: ${{ secrets.AWS_REGION }} - S3_BUCKET: ${{ secrets.AWS_BUCKET }} R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} R2_BUCKET: ${{ secrets.R2_BUCKET }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4ff38545..1fa2b71e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -76,24 +76,13 @@ builds: - cmd: sh .goreleaser.checksum.sh {{ .Path }} - cmd: sh .goreleaser.checksum.sh {{ .Path }}.xz -blobs: - - - provider: s3 - bucket: "{{ .Env.S3_BUCKET }}" - region: "{{ .Env.S3_REGION }}" - directory: "tea/{{.Version}}" - extra_files: - - glob: ./**.xz - - glob: ./**.sha256 - -# Mirrors the S3 `blobs:` upload above into Cloudflare R2 during the -# parallel S3+R2 period (S3 will be removed once migration completes). -# A second `blobs:` entry is impossible here since the blob pipe -# authenticates from the global AWS_* env with no per-entry -# credentials; `publishers:` supports per-entry `env:` instead, so -# it's used to invoke scripts/upload-r2.sh once per artifact. Custom -# publishers inherit almost nothing from the environment, hence the -# explicit R2_* forwarding below. +# Uploads the release artifacts to Cloudflare R2. A `blobs:` entry is +# not used here since the blob pipe authenticates from the global +# AWS_* env with no per-entry credentials; `publishers:` supports +# per-entry `env:` instead, so it's used to invoke +# scripts/upload-r2.sh once per artifact. Custom publishers inherit +# almost nothing from the environment, hence the explicit R2_* +# forwarding below. # # This publisher fires more than once per distinct key because # goreleaser's release pipe already registers `release.extra_files` diff --git a/scripts/upload-r2.sh b/scripts/upload-r2.sh index efab8edc..0967819c 100755 --- a/scripts/upload-r2.sh +++ b/scripts/upload-r2.sh @@ -6,13 +6,9 @@ # Cloudflare R2 bucket, using curl's built-in AWS SigV4 signer (R2 is # S3-API compatible). # -# This is the R2 half of the release process's parallel S3+R2 upload -# period: goreleaser's `blobs:` pipe still uploads every release -# artifact to AWS S3, and this script is invoked once per artifact -# (via a goreleaser `publishers:` entry) to mirror the same artifact -# into R2. Once the migration away from S3 is complete, the `blobs:` -# block and the AWS_* secrets can be dropped without touching this -# script. +# It is invoked once per release artifact via a goreleaser +# `publishers:` entry, and is the only artifact storage upload in the +# release process. # # Usage: # upload-r2.sh @@ -24,7 +20,7 @@ # preflight step in CI: goreleaser custom publishers run as the very # last step of the publish pipeline, so without a preflight check a # missing R2_* secret would only be discovered after the Gitea release -# has already been created and every artifact already uploaded to S3. +# has already been created. # # Required environment variables: # R2_ENDPOINT Base URL of the R2 endpoint, e.g. From 943d4c1512b47e7ba488168f041df62d9c801138 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 15 Aug 2026 20:34:32 +0000 Subject: [PATCH 2/4] Drop AWS S3 release upload, keep only Cloudflare R2 (#1092) The release pipeline uploaded artifacts to both AWS S3 (goreleaser `blobs:`) and Cloudflare R2 (custom publisher) during the migration period. The migration is done, so this removes the S3 half: - drop the `blobs:` block from `.goreleaser.yaml` - drop the `AWS_*` / `S3_*` env from the nightly and tag release workflows - update the comments in `.goreleaser.yaml` and `scripts/upload-r2.sh` accordingly Cloudflare R2 upload (including the early `--check-config` preflight step) is unchanged, and the `AWS_*` repo secrets are no longer used. Reviewed-on: https://gitea.com/gitea/tea/pulls/1092 Reviewed-by: bircni Co-authored-by: Lunny Xiao From 276a4b735af69a2b403f007a9c0ef194b878228c Mon Sep 17 00:00:00 2001 From: James Braid Date: Sun, 16 Aug 2026 12:57:47 +0000 Subject: [PATCH 3/4] fix(oauth): don't wait for the browser opener to exit (#1093) Fixes `tea login add --oauth` hanging after the user authenticates in the browser. `xdg-open` (at least on Debian) runs the browser in the foreground, so it does not exit until the browser does. `open.Run` waits for it, so tea is blocked and doesn't get the oAuth callback from the browser. This only happens when `xdg-open` has to start the browser. With one already running, the new process hands off and exits immediately. `open.Start` launches the opener and returns. The test mocks `xdg-open` with a script that holds the foreground and fails if `openBrowser` waits on it. Reviewed-on: https://gitea.com/gitea/tea/pulls/1093 Reviewed-by: Lunny Xiao Co-authored-by: James Braid --- modules/auth/oauth.go | 4 +++- modules/auth/oauth_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/modules/auth/oauth.go b/modules/auth/oauth.go index c98a22fa..ccda4ab7 100644 --- a/modules/auth/oauth.go +++ b/modules/auth/oauth.go @@ -366,7 +366,9 @@ func startLocalServerAndOpenBrowser(authURL, expectedState string, opts *OAuthOp var openBrowser = func(url string) error { fmt.Printf("Please authorize the application by visiting this URL in your browser:\n%s\n", url) - return open.Run(url) + // Don't wait for the opener to exit, so a browser that holds the + // foreground can't block the wait for the callback. + return open.Start(url) } // createLoginFromToken creates a login entry using the obtained access token diff --git a/modules/auth/oauth_test.go b/modules/auth/oauth_test.go index 6ce82086..693ea830 100644 --- a/modules/auth/oauth_test.go +++ b/modules/auth/oauth_test.go @@ -6,11 +6,16 @@ package auth import ( "context" "encoding/json" + "fmt" "net/http" "net/http/httptest" "net/url" + "os" + "path/filepath" + "runtime" "sync" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -89,3 +94,36 @@ func TestPerformBrowserOAuthFlow_RedirectURIMatchesAcrossAuthorizeAndExchange(t assert.Equal(t, authorizeRedirectURI, exchangeRedirectURI, "redirect_uri must match between authorize and token exchange (RFC 6749 ยง4.1.3)") } + +// Regression test for the browser opener hang: xdg-open does not exit until +// the browser it launched does, and the callback is only consumed after +// openBrowser returns. Waiting on the opener hangs the CLI even though the +// user authenticated successfully. +func TestOpenBrowser_DoesNotWaitForOpener(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + t.Skip("xdg-open is not the opener on this platform") + } + + const ( + fakeOpenerSleepTime = 10 * time.Second + openBrowserTimeout = 2 * time.Second + ) + + // A stand-in xdg-open that holds the foreground the way a browser it had + // to launch would. + dir := t.TempDir() + opener := filepath.Join(dir, "xdg-open") + script := fmt.Sprintf("#!/bin/sh\nexec sleep %d\n", int(fakeOpenerSleepTime.Seconds())) + require.NoError(t, os.WriteFile(opener, []byte(script), 0o755)) + t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) + + done := make(chan error, 1) + go func() { done <- openBrowser("http://127.0.0.1:1/") }() + + select { + case err := <-done: + require.NoError(t, err) + case <-time.After(openBrowserTimeout): + t.Fatal("openBrowser blocked on the opener; the callback would never be consumed") + } +} From ee531914cd107a6c4239013f979e65835d4815f7 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 16 Aug 2026 13:01:15 +0000 Subject: [PATCH 4/4] chore(deps): update docker/login-action digest to dbcb813 (#1082) Co-authored-by: Renovate Bot --- .gitea/workflows/release-nightly.yml | 2 +- .gitea/workflows/release-tag.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release-nightly.yml b/.gitea/workflows/release-nightly.yml index b86c3df2..54c3be66 100644 --- a/.gitea/workflows/release-nightly.yml +++ b/.gitea/workflows/release-nightly.yml @@ -71,7 +71,7 @@ jobs: uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 - name: Login to DockerHub - uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.gitea/workflows/release-tag.yml b/.gitea/workflows/release-tag.yml index 0d814aae..4d658c19 100644 --- a/.gitea/workflows/release-tag.yml +++ b/.gitea/workflows/release-tag.yml @@ -72,7 +72,7 @@ jobs: uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 - name: Login to DockerHub - uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }}