diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 92ab75b..46e90b6 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -71,12 +71,22 @@ jobs: echo "publishing as ${tag}" - name: Publish release and upload plugin env: - # Deliberately not named GITEA_TOKEN: the Actions runner injects its - # own GITEA_TOKEN into every step, and that value wins over a - # step-level `env:` of the same name, so the org secret would be - # silently ignored. + # Passed under a name the runner does not reserve. A step-level + # GITEA_TOKEN is overridden by the runner's own job token, so the + # org secret would be silently ignored under that name. + # + # RELEASE_TOKEN is the org-wide API_TOKEN secret. If it is empty -- + # which happens when Actions is disabled on the repository, so org + # secrets are never exposed to it -- release.py falls back to the + # runner's GITEA_TOKEN, which can still publish to its own repo. RELEASE_TOKEN: ${{ secrets.API_TOKEN }} run: | + set -eu + if [ -n "${RELEASE_TOKEN:-}" ]; then + echo "org secret API_TOKEN is present (${#RELEASE_TOKEN} chars)" + else + echo "org secret API_TOKEN is empty; falling back to the runner token" + fi python3 tools/release.py \ --api "${{ github.server_url }}/api/v1/repos/${{ github.repository }}" \ --tag "${{ steps.tag.outputs.tag }}" \ diff --git a/README.md b/README.md index 428dd39..e2851ec 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,13 @@ credentials. The workflow passes it to `release.py` as `RELEASE_TOKEN`, **not** `GITEA_TOKEN`. The Actions runner injects its own `GITEA_TOKEN` into every step, and that value overrides a step-level -`env:` entry of the same name, so the org secret would be silently ignored and the publish -step would fail on permissions. Use `--token-env` if you need a different variable name. +`env:` entry of the same name, so the org secret would be silently ignored. + +If the org secret does not reach a repository the publish still works: `release.py` tries +`RELEASE_TOKEN` first and then falls back to the runner's `GITEA_TOKEN`, which can publish +releases in its own repository, and prints which one it used. An organisation secret is only +exposed to a repository that has Actions enabled, so check **Repo → Settings → Actions** if a +repo unexpectedly falls back. If you would rather not manage a token at all, that injected `GITEA_TOKEN` can create releases in its own repo: pass `--token-env GITEA_TOKEN` and drop the org secret. It is diff --git a/tools/release.py b/tools/release.py index c8f8365..2d11a08 100755 --- a/tools/release.py +++ b/tools/release.py @@ -152,16 +152,35 @@ def main(argv: Optional[list] = None) -> int: parser.add_argument( "--token-env", default="RELEASE_TOKEN", - help="environment variable holding the API token (default: RELEASE_TOKEN)", + help="preferred environment variable holding the API token", ) args = parser.parse_args(argv) - # Read from an explicitly named variable rather than GITEA_TOKEN: the - # Actions runner injects its own GITEA_TOKEN into every step, which - # silently wins over a step-level `env:` of the same name. - token = os.environ.get(args.token_env, "") + # Preferred name first, then the runner-provided job token. GITEA_TOKEN is + # not used as the primary name because the Actions runner injects its own + # value for it into every step, which overrides a step-level `env:` entry + # of the same name -- but it is a valid last resort, since it can publish + # releases in its own repository. + candidates = [args.token_env, "RELEASE_TOKEN", "GITEA_TOKEN", "GITHUB_TOKEN"] + token, token_source = "", "" + for name in candidates: + value = os.environ.get(name, "").strip() + if value: + token, token_source = value, name + break if not token: - fail(f"{args.token_env} is not set") + present = sorted(n for n in candidates if n in os.environ) + fail( + f"no API token found; looked at {', '.join(dict.fromkeys(candidates))}. " + + ( + f"These exist but are empty: {', '.join(present)}. " + "An organisation secret is only exposed to a repository when Actions " + "is enabled on that repository, so check Repo > Settings > Actions." + if present + else "None of them are set in the environment." + ) + ) + print(f"using token from {token_source}") artifact = Path(args.artifact) if not artifact.is_file() or artifact.stat().st_size == 0: