publish/github — the trusted-publishing road to PyPI
The second of facett's two roads to PyPI (the first is cargo ops publish-pypi with an API token; both are written out step by step in .nornir/pypi-publish.md).
.github/workflows/pypi.yml in this folder is a GitHub Actions workflow that publishes the two facett releases — facett (the components) and facett-demo (the Streamlit app) — with PyPI trusted publishing (OIDC): no API token is stored anywhere.
What it does, and what it deliberately does not
- It builds nothing. The wheels and the demo's sdist are built on the build box by facett's build thing (
cargo xtask wrap-wasm, thencargo ops publish-pip: the pinnednordisk-pip-toolchaincontainer, network off, every wheel read back against its own RECORD) and checked the way PyPI will check them (cargo ops publish-pypi --dry-run). They reach GitHub as release assets, with theirSHA256SUMS. - It downloads the assets of one release, checks each file against the
SHA256SUMSshipped beside it, refuses anything that is not one of the two releases (and the demo without the components), and hands the files topypa/gh-action-pypi-publish.
Where it runs: the ignalina GitHub mirror
The nordisk repos are mirrored from Codeberg to PRIVATE GitHub repos under the Ignalina organization by ~/mirror-ignalina/mirror-to-github.sh (SSH, all branches + tags + LFS, force-pushed from Codeberg on every run). facett is mirrored the same way, to github.com/Ignalina/facett. Two consequences:
- GitHub only runs workflows from the repo ROOT,
.github/workflows/. Because the mirror force-pushes every branch, a commit made only on GitHub would be overwritten on the next mirror run — so the workflow reaches GitHub by being committed at the root on Codeberg main:.github/workflows/pypi.yml= this folder's file (an owner step; this lane did not place it there). On Codeberg it is inert: Actions are disabled fornordisk/facett, and the job also refuses any server but github.com. - Tags arrive with the mirror; releases do not (they are not git refs). The release that carries the wheels is created on GitHub, on the mirrored tag.
The PyPI side (once per project, per index)
pypi.org (the ignalina account) → Your account → Publishing → Add a new pending publisher → GitHub:
| field | facett |
facett-demo |
| PyPI project name | facett |
facett-demo |
| Owner | Ignalina |
Ignalina |
| Repository name | facett |
facett |
| Workflow name | pypi.yml |
pypi.yml |
| Environment name | pypi |
pypi |
The same two on test.pypi.org with environment testpypi. In the GitHub repo, Settings → Environments: create pypi (add a required reviewer) and testpypi.
A release
# on the build box, in the facett checkout (main, pushed to Codeberg)
cargo xtask wrap-wasm && cargo ops publish-pip && cargo ops verify-demo-install
cargo ops publish-pypi --dry-run
git tag v0.2.0 && git push origin v0.2.0
bash ~/mirror-ignalina/mirror-to-github.sh facett # the tag (and the workflow) reach GitHub
Then on github.com/Ignalina/facett: Releases → Draft a new release, tag v0.2.0, attach pkg-facet-wrapped/dist/facett-0.2.0-py3-none-any.whl, py/dist/facett_demo-0.2.0-py3-none-any.whl, py/dist/facett_demo-0.2.0.tar.gz, and the two SHA256SUMS renamed SHA256SUMS.facett / SHA256SUMS.facett-demo (both are named SHA256SUMS on disk); tick pre-release, publish.
- A pre-release does not trigger the workflow. Rehearse: Actions → pypi → Run workflow, tag
v0.2.0,testpypi. - Then edit the release, untick pre-release, Set as the latest release: that fires the
releasedevent and publishes to pypi.org once thepypienvironment's reviewer approves.
If the repository is private and the publish action reports that it cannot produce attestations for it, add attestations: false under its with:.