fix(pypi): swap OIDC trusted-publisher for twine + PYPI_TOKEN; port .github -> .gitea #6
@ -9,15 +9,20 @@ on:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||
|
||||
# Post-2026-05-06 (Molecule-AI GitHub org suspension): PyPI's Trusted
|
||||
# Publisher OIDC flow only accepts GitHub/GitLab/Google/ActiveState
|
||||
# issuers — not Gitea. This workflow uses a long-lived PyPI API token
|
||||
# stored as the repo-level secret PYPI_TOKEN, fanned out from the
|
||||
# operator-host SSOT (/etc/molecule-bootstrap/all-credentials.env) by
|
||||
# /opt/molecule-bootstrap/sync-pypi-token.sh.
|
||||
permissions:
|
||||
contents: read
|
||||
# OIDC token for PyPI trusted-publisher auth — no secret token needed.
|
||||
# PyPI side: register
|
||||
# github.com/Molecule-AI/codex-channel-molecule
|
||||
# workflow=publish.yml environment=pypi
|
||||
# under "Trusted publisher management" on the codex-channel-molecule
|
||||
# PyPI project page (see README "Releasing" section).
|
||||
id-token: write
|
||||
|
||||
# Serialize tag-driven publishes so two concurrent tag pushes don't both
|
||||
# try to upload the same version and race PyPI.
|
||||
concurrency:
|
||||
group: publish-pypi
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -40,7 +45,7 @@ jobs:
|
||||
|
||||
- name: Build sdist + wheel
|
||||
run: |
|
||||
python -m pip install --upgrade pip build
|
||||
python -m pip install --upgrade pip build twine
|
||||
python -m build
|
||||
|
||||
- name: Smoke-import the built wheel
|
||||
@ -49,6 +54,9 @@ jobs:
|
||||
/tmp/install-test/bin/pip install dist/*.whl
|
||||
/tmp/install-test/bin/codex-channel-molecule --help
|
||||
|
||||
- name: Verify package metadata (twine check)
|
||||
run: python -m twine check dist/*
|
||||
|
||||
- uses: actions/upload-artifact@v3 # pinned to v3 for Gitea act_runner v0.6 compatibility (internal#46)
|
||||
with:
|
||||
name: dist
|
||||
@ -57,13 +65,31 @@ jobs:
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3 # pinned to v3 for Gitea act_runner v0.6 compatibility (internal#46)
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Publish to PyPI
|
||||
# PYPI_TOKEN: repo-level Gitea Actions secret, written by
|
||||
# /opt/molecule-bootstrap/sync-pypi-token.sh from the operator-host
|
||||
# SSOT. Never set this by hand — rotate via the SSOT instead
|
||||
# (ops/PYPI_TOKEN_ROTATION.md in operator-config).
|
||||
env:
|
||||
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$PYPI_TOKEN" ]; then
|
||||
echo "::error::PYPI_TOKEN secret is not set. Run sync-pypi-token.sh on the operator host to fan it out from SSOT."
|
||||
exit 1
|
||||
fi
|
||||
python -m pip install --upgrade twine
|
||||
python -m twine upload \
|
||||
--repository pypi \
|
||||
--username __token__ \
|
||||
--password "$PYPI_TOKEN" \
|
||||
dist/*
|
||||
16
README.md
16
README.md
@ -80,7 +80,7 @@ Tests are entirely real-subprocess (no mocking the spawn boundary) so the boot p
|
||||
|
||||
## Releasing
|
||||
|
||||
Tag-on-push triggers `publish.yml` which builds + publishes to PyPI via OIDC trusted publishing (no API token needed).
|
||||
Tag-on-push triggers `.gitea/workflows/publish.yml` which builds + publishes to PyPI via `twine upload` using the `PYPI_TOKEN` repo-level Gitea Actions secret.
|
||||
|
||||
```sh
|
||||
# Bump pyproject.toml `version`, commit, then:
|
||||
@ -89,19 +89,11 @@ git tag v0.1.1 && git push origin v0.1.1
|
||||
|
||||
The workflow refuses to publish if the tag doesn't match `pyproject.toml`'s `version` — keeps PyPI versions and git tags in lockstep.
|
||||
|
||||
**One-time PyPI setup** (before the first release):
|
||||
### Why twine, not Trusted Publisher OIDC
|
||||
|
||||
1. Create the project on PyPI by uploading the first wheel manually, OR
|
||||
2. Pre-register the project on PyPI under a "Pending publisher" config so the first tagged push creates it.
|
||||
Post-2026-05-06 (Molecule-AI GitHub-org suspension) the canonical SCM is Gitea. PyPI's Trusted-Publisher OIDC flow only recognises GitHub / GitLab / Google / ActiveState issuers — not Gitea — so this repo (and every other PyPI-publishing repo in `molecule-ai/*`) falls back to a long-lived API token.
|
||||
|
||||
Either way, on the project's PyPI page → "Manage" → "Publishing" → "Add a new publisher", configure:
|
||||
|
||||
- Owner: `Molecule-AI`
|
||||
- Repository: `codex-channel-molecule`
|
||||
- Workflow filename: `publish.yml`
|
||||
- Environment name: `pypi`
|
||||
|
||||
After this, every `git push origin v*.*.*` ships the wheel to PyPI without any further intervention.
|
||||
The `PYPI_TOKEN` secret is **not set by hand**. It is fanned out from the operator-host SSOT (`/etc/molecule-bootstrap/all-credentials.env`) by `/opt/molecule-bootstrap/sync-pypi-token.sh` (see [operator-config/etc/pypi-publishers.yaml](https://git.moleculesai.app/molecule-ai/operator-config/src/branch/main/etc/pypi-publishers.yaml)). Rotation procedure: [PYPI_TOKEN_ROTATION.md](https://git.moleculesai.app/molecule-ai/operator-config/src/branch/main/ops/PYPI_TOKEN_ROTATION.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user