From 23a9c4d4bddd80fd2fea6a3db41d50b2f8855630 Mon Sep 17 00:00:00 2001 From: CloakHQ Date: Sun, 8 Mar 2026 02:32:33 +0100 Subject: [PATCH] ci: add publish workflow, binary attestation, and dev extras - publish.yml: automated PyPI/npm/Docker on v* tag push; OIDC trusted publishing for PyPI/npm; Docker signed with Cosign keyless + provenance attested - attest-release.yml: manual workflow to attest binary release assets via Sigstore (actions/attest-build-provenance@v2) - pyproject.toml: add dev extras (pytest, pytest-asyncio) --- .github/workflows/attest-release.yml | 28 ++++++ .github/workflows/publish.yml | 131 +++++++++++++++++++++++++++ .github/workflows/release-binary.yml | 45 --------- pyproject.toml | 1 + 4 files changed, 160 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/attest-release.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/release-binary.yml diff --git a/.github/workflows/attest-release.yml b/.github/workflows/attest-release.yml new file mode 100644 index 0000000..f16bccf --- /dev/null +++ b/.github/workflows/attest-release.yml @@ -0,0 +1,28 @@ +name: Attest Release Binary + +on: + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g. chromium-v145.0.7632.159.2)' + required: true + +jobs: + attest: + runs-on: ubuntu-latest + permissions: + id-token: write # Sigstore OIDC + attestations: write # GitHub attestation API + contents: write # Download release assets + steps: + - name: Download release binaries + run: gh release download ${{ github.event.inputs.tag }} --repo CloakHQ/cloakbrowser --pattern "cloakbrowser-*.tar.gz" --pattern "cloakbrowser-*.zip" + env: + GH_TOKEN: ${{ github.token }} + + - name: Attest build provenance + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + cloakbrowser-*.tar.gz + cloakbrowser-*.zip diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ed51e28 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,131 @@ +name: Publish + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + job: + description: 'Job to run (leave empty to run all)' + required: false + type: choice + options: + - '' + - publish-pypi + - publish-npm + - publish-docker + +concurrency: + group: publish + cancel-in-progress: false + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Python tests + run: | + pip install -e ".[dev]" pytest pytest-asyncio + pytest tests/ -v -m "not slow" + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: JavaScript tests + run: cd js && npm ci && npm run build && npm test + + validate-version: + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Check tag matches package versions + run: | + TAG="${GITHUB_REF_NAME#v}" + PY=$(python -c 'from cloakbrowser._version import __version__; print(__version__)') + JS=$(python -c 'import json; print(json.load(open("js/package.json"))["version"])') + echo "Tag: $TAG | Python: $PY | npm: $JS" + [ "$TAG" = "$PY" ] || { echo "ERROR: tag v$TAG != _version.py $PY"; exit 1; } + [ "$TAG" = "$JS" ] || { echo "ERROR: tag v$TAG != package.json $JS"; exit 1; } + + publish-pypi: + needs: [test, validate-version] + if: always() && needs.test.result == 'success' && (needs.validate-version.result == 'success' || needs.validate-version.result == 'skipped') + runs-on: ubuntu-latest + permissions: + id-token: write # OIDC trusted publishing — no PYPI_TOKEN needed + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Build + run: | + pip install build + python -m build + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + publish-npm: + needs: [test, validate-version] + if: always() && needs.test.result == 'success' && (needs.validate-version.result == 'success' || needs.validate-version.result == 'skipped') + runs-on: ubuntu-latest + permissions: + id-token: write # OIDC trusted publishing + provenance — no NPM_TOKEN needed + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Build + run: cd js && npm ci && npm run build + - name: Publish to npm + run: cd js && npm publish --provenance --access public + + publish-docker: + needs: [test, validate-version] + if: always() && needs.test.result == 'success' && (needs.validate-version.result == 'success' || needs.validate-version.result == 'skipped') + runs-on: ubuntu-latest + permissions: + id-token: write # Cosign keyless signing + attestations + contents: read + attestations: write + packages: write + steps: + - uses: actions/checkout@v4 + - name: Extract version + run: | + VERSION=$(python -c 'from cloakbrowser._version import __version__; print(__version__)') + echo "VERSION=$VERSION" >> $GITHUB_ENV + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PAT }} + - name: Build and push + id: build + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + cloakhq/cloakbrowser:${{ env.VERSION }} + cloakhq/cloakbrowser:latest + provenance: true + sbom: true + - uses: sigstore/cosign-installer@v3 + - name: Sign image + run: cosign sign --yes cloakhq/cloakbrowser@${{ steps.build.outputs.digest }} + - name: Attest build provenance + uses: actions/attest-build-provenance@v2 + with: + subject-name: index.docker.io/cloakhq/cloakbrowser + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml deleted file mode 100644 index b00af37..0000000 --- a/.github/workflows/release-binary.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Release Binary - -on: - workflow_dispatch: - inputs: - tag: - description: 'Release tag (e.g. chromium-v145.0.7718.0)' - required: true - title: - description: 'Release title (e.g. Chromium v145 — Stealth Build)' - required: true - default: 'Stealth Chromium Build' - patch_count: - description: 'Number of fingerprint patches' - required: true - default: '16' - -jobs: - release: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - - name: Create release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.event.inputs.tag }} - name: "${{ github.event.inputs.title }}" - body: | - ## Stealth Chromium Build - - Pre-built Chromium with ${{ github.event.inputs.patch_count }} source-level fingerprint patches. - - ### Install - ```bash - pip install cloakbrowser # Python - npm install cloakbrowser # JavaScript - # Binary auto-downloads on first launch - ``` - - > Binary integrity is verified automatically via SHA-256 checksums on download. - > - > Release signed with CloakHQ GPG key: `C60C0DDC9D0DE2DD` diff --git a/pyproject.toml b/pyproject.toml index 5ee4d87..c0b6a43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ dependencies = [ [project.optional-dependencies] geoip = ["geoip2>=4.0"] patchright = ["patchright>=1.40"] +dev = ["pytest>=7.0", "pytest-asyncio>=0.23"] [project.urls] Homepage = "https://github.com/CloakHQ/CloakBrowser"