Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Release

# Build the sdist + wheel on every tag, validate them, and publish to PyPI via
# Trusted Publishing (OIDC — no long-lived API token stored in the repo). The
# publish job is gated on the ``pypi`` GitHub Environment, so it does nothing
# until a maintainer configures that environment and the matching Trusted
# Publisher on PyPI; until then tagging still produces validated, downloadable
# artifacts without pushing anything to the index.

on:
push:
tags:
- "v*"
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: build + validate distributions
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install build tooling
run: python -m pip install -e .[release]
- name: Build sdist and wheel
run: python -m build
- name: Verify tag matches package version
if: startsWith(github.ref, 'refs/tags/')
run: |
version="$(python -c 'import importlib.metadata as m; print(m.version("pyisolate"))')"
tag="${GITHUB_REF_NAME#v}"
if [ "$version" != "$tag" ]; then
echo "tag $tag does not match package version $version" >&2
exit 1
fi
- name: Check distribution metadata
run: python -m twine check dist/*
- name: Capture release provenance
run: pyisolate-doctor --json > dist/provenance.json || true
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
name: publish to PyPI (Trusted Publishing)
needs: build
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-24.04
environment:
name: pypi
url: https://pypi.org/project/pyisolate/
permissions:
id-token: write # required for OIDC Trusted Publishing
steps:
- name: Download built distributions
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Remove non-distribution artifacts
run: rm -f dist/provenance.json
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ __pycache__/
*.bpf.o
*.egg-info/
.env/
/dist/
/build/
26 changes: 26 additions & 0 deletions docs/packaging-reproducibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,29 @@ Suggested release flow:

Persist `pyisolate-doctor` output next to signatures to tie artifacts back to an
exact interpreter + kernel environment.

## 7) Automated PyPI publishing

`.github/workflows/release.yml` builds, validates, and (optionally) publishes the
package. It runs on any `v*` tag and on manual `workflow_dispatch`:

- **build job** — installs `.[release]`, runs `python -m build`, asserts the tag
matches the package version, runs `twine check dist/*`, captures
`pyisolate-doctor --json` provenance, and uploads `dist/` as a workflow
artifact. This job needs no secrets, so tagging always yields validated,
downloadable distributions.
- **publish job** — runs only for tag pushes, is gated on the `pypi` GitHub
Environment, and uploads via **PyPI Trusted Publishing** (OIDC). No API token
is stored in the repository.

To enable publishing:

1. On PyPI, add a *Trusted Publisher* for the project pointing at this repo,
the `Release` workflow, and the `pypi` environment.
2. In GitHub, create an Environment named `pypi` (add reviewers/branch
protection if you want a manual approval gate before upload).
3. Bump `version` in `pyproject.toml`, commit, then push a matching `vX.Y.Z`
tag. The version-match check fails the build if the tag and metadata differ.

Until the Trusted Publisher and environment exist, the publish job is a no-op
gate and only the build/validation job runs.
35 changes: 34 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,45 @@
name = "pyisolate"
version = "0.0.1"
description = "Prototype PyIsolate sandbox; kernel enforcement and no-GIL support are experimental roadmap items"
readme = "README.md"
license = "MIT"
license-files = ["LICENSE"]
authors = [{ name = "PyIsolate" }]
requires-python = ">=3.11"
keywords = [
"sandbox",
"isolation",
"security",
"seccomp",
"landlock",
"ebpf",
"sub-interpreter",
"no-gil",
]
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Security",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Systems Administration",
]
dependencies = ["pyyaml", "cryptography", "platformdirs"]

[project.urls]
Homepage = "https://github.com/seanwevans/pyisolate"
Repository = "https://github.com/seanwevans/pyisolate"
Issues = "https://github.com/seanwevans/pyisolate/issues"
Changelog = "https://github.com/seanwevans/pyisolate/blob/main/ROADMAP.md"

[build-system]
requires = ["setuptools>=64"]
# setuptools >= 77 emits PEP 639 license metadata (License-Expression /
# License-File) that twine check and modern PyPI validate cleanly.
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
Expand Down
Loading