diff --git a/.gitea/workflows/release-nightly.yml b/.gitea/workflows/release-nightly.yml index b4bd7f0b..54c3be66 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: @@ -27,6 +26,7 @@ jobs: - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 with: go-version-file: "go.mod" + check-latest: true - name: import gpg id: import_gpg uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7 @@ -45,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 2fc919e9..4d658c19 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: @@ -28,6 +27,7 @@ jobs: - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 with: go-version-file: 'go.mod' + check-latest: true - name: import gpg id: import_gpg uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7 @@ -46,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/.gitea/workflows/test-pr.yml b/.gitea/workflows/test-pr.yml index b1fa1355..b5c81466 100644 --- a/.gitea/workflows/test-pr.yml +++ b/.gitea/workflows/test-pr.yml @@ -20,6 +20,7 @@ jobs: - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 with: go-version-file: 'go.mod' + check-latest: true - name: lint and build run: | make clean @@ -45,6 +46,7 @@ jobs: - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 with: go-version-file: 'go.mod' + check-latest: true - name: wait for the gitea instance to be ready run: | for i in $(seq 1 30); do 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/Makefile b/Makefile index 6eaa2ae2..0376dbdf 100644 --- a/Makefile +++ b/Makefile @@ -118,7 +118,12 @@ unit-test-coverage: .PHONY: tidy tidy: + $(eval GO_TOOLCHAIN := $(shell grep -Eo '^toolchain\s+go[0-9.]+' go.mod | cut -d' ' -f2)) $(GO) mod tidy + @# workaround https://github.com/golang/go/issues/75331: restore toolchain if tidy dropped it + @if [ -n "$(GO_TOOLCHAIN)" ] && ! grep -qE '^toolchain\s' go.mod; then \ + $(GO) mod edit -toolchain=$(GO_TOOLCHAIN); \ + fi .PHONY: check check: test diff --git a/go.mod b/go.mod index dcb30bc3..fba7910c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module gitea.dev/tea -go 1.26 +go 1.26.0 + +toolchain go1.26.5 require ( charm.land/glamour/v2 v2.0.1 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") + } +} diff --git a/renovate.json5 b/renovate.json5 index 72388496..c7e09e46 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -1,8 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "local>gitea/renovate-config", - "local>gitea/renovate-config:security", - "local>gitea/renovate-config:go-deps" + "local>gitea/renovate-config" ] } 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.