diff --git a/.github/workflows/notify-homebrew.yml b/.github/workflows/notify-homebrew.yml index fa6dfe8..25f7340 100644 --- a/.github/workflows/notify-homebrew.yml +++ b/.github/workflows/notify-homebrew.yml @@ -1,4 +1,8 @@ name: Notify Homebrew tap + +# Triggers on release tags for the CLI package. Sends a +# repository_dispatch event to Create-Python-App/homebrew-tap so its +# update-formula workflow can bump the formula and push it. on: push: tags: @@ -6,13 +10,42 @@ on: workflow_dispatch: inputs: version: - description: "Package version" + description: "Package version (e.g. 0.1.0)" required: true + +permissions: + contents: read + jobs: notify: + name: Dispatch to homebrew-tap runs-on: ubuntu-latest steps: - - name: Dispatch to homebrew-tap + - name: Resolve version + id: version + env: + TAG_REF: ${{ github.ref_name }} + INPUT_VERSION: ${{ github.event.inputs.version }} run: | - echo "Send repository_dispatch to Create-Python-App/homebrew-tap when it exists" - echo "Secret: HOMEBREW_TAP_TOKEN" + if [ -n "$INPUT_VERSION" ]; then + VERSION="$INPUT_VERSION" + else + VERSION="${TAG_REF#create-awesome-python-app@}" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Dispatch repository event + env: + GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + VERSION: ${{ steps.version.outputs.version }} + run: | + if [ -z "$GH_TOKEN" ]; then + echo "::error::HOMEBREW_TAP_TOKEN secret is not set" >&2 + exit 1 + fi + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/Create-Python-App/homebrew-tap/dispatches \ + -f "event_type=new-release" \ + -f "client_payload[version]=$VERSION" diff --git a/.github/workflows/publish-aur.yml b/.github/workflows/publish-aur.yml index b4189cd..cff5c06 100644 --- a/.github/workflows/publish-aur.yml +++ b/.github/workflows/publish-aur.yml @@ -1,4 +1,8 @@ name: Publish to AUR + +# Triggers on release tags for the CLI package. +# Publishes the updated PKGBUILD to aur.archlinux.org and keeps the +# Create-Python-App/aur-package GitHub mirror in sync. on: push: tags: @@ -6,14 +10,82 @@ on: workflow_dispatch: inputs: version: - description: "Package version" + description: "Package version (e.g. 0.1.0)" required: true + +permissions: + contents: read + jobs: aur: + name: Update AUR package runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Placeholder + - name: Resolve version + id: version + env: + TAG_REF: ${{ github.ref_name }} + INPUT_VERSION: ${{ github.event.inputs.version }} run: | - echo "AUR publish requires AUR_SSH_PRIVATE_KEY + optional Create-Python-App/aur-package mirror" - echo "See docs/DISTRIBUTION_SETUP.md" + if [ -n "$INPUT_VERSION" ]; then + VERSION="$INPUT_VERSION" + else + VERSION="${TAG_REF#create-awesome-python-app@}" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Checkout aur-package mirror repo + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: Create-Python-App/aur-package + token: ${{ secrets.AUR_REPO_TOKEN }} + path: aur-package + + - name: Update PKGBUILD (version + sha256 from PyPI) + working-directory: aur-package + env: + NEW_VERSION: ${{ steps.version.outputs.version }} + run: | + set -euo pipefail + # Reset pkgver and pkgrel; source URL already interpolates ${pkgver}. + sed -i "s/^pkgver=.*/pkgver=${NEW_VERSION}/" PKGBUILD + sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD + + META=$(curl -sfL "https://pypi.org/pypi/create-awesome-python-app/${NEW_VERSION}/json") + SHA=$(echo "$META" | jq -r '.urls[] | select(.packagetype=="sdist") | .digests.sha256') + if [ -z "$SHA" ] || [ "$SHA" = "null" ]; then + echo "::error::Failed to resolve PyPI sdist sha256 for v${NEW_VERSION}" >&2 + exit 1 + fi + sed -i "s/^sha256sums=.*/sha256sums=('${SHA}')/" PKGBUILD + + echo "----- Updated PKGBUILD -----" + cat PKGBUILD + + - name: Publish to AUR + # Pushes to aur.archlinux.org via SSH. The action reads the + # updated PKGBUILD, regenerates .SRCINFO, and pushes. + uses: ulises-jeremias/github-actions-aur-publish@217e4e2abbbee9ecc942bdc0681302e233656d9f # v1 + with: + pkgname: create-awesome-python-app + pkgbuild: aur-package/PKGBUILD + commit_username: "Create Python App Bot" + commit_email: "ulisescf.24@gmail.com" + commit_message: "Update to version ${{ steps.version.outputs.version }}" + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + allow_empty_commits: "false" + # dsa is no longer supported in modern OpenSSH; omit it to + # avoid "Unknown key type" errors during keyscan. + ssh_keyscan_types: "rsa,ecdsa,ed25519" + + - name: Sync updated PKGBUILD to GitHub mirror + # Keep the aur-package GitHub mirror in sync with what's live + # on AUR. Only PKGBUILD needs to be committed here — .SRCINFO + # is regenerated automatically by AUR from PKGBUILD. + uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0 + with: + repository: aur-package + commit_message: "chore: sync PKGBUILD for v${{ steps.version.outputs.version }}" + commit_user_name: "Create Python App Bot" + commit_user_email: "ulisescf.24@gmail.com" + file_pattern: "PKGBUILD" diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 8ca7608..c84933c 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -1,27 +1,96 @@ name: Publish Docker image + +# Triggers on release tags for the CLI package. Version comes directly +# from the tag ref (create-awesome-python-app@X.Y.Z). on: push: tags: - "create-awesome-python-app@*" + # Manual trigger to rebuild an image for an existing version. workflow_dispatch: inputs: version: - description: "Package version" + description: "Package version (e.g. 0.1.0)" required: true + +permissions: + contents: read + jobs: docker: + name: Build and push Docker image runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Login to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Resolve version + id: version + env: + TAG_REF: ${{ github.ref_name }} + INPUT_VERSION: ${{ github.event.inputs.version }} + run: | + if [ -n "$INPUT_VERSION" ]; then + VERSION="$INPUT_VERSION" + else + # Tag format: create-awesome-python-app@X.Y.Z + VERSION="${TAG_REF#create-awesome-python-app@}" + fi + MAJOR=$(echo "$VERSION" | cut -d. -f1) + MINOR=$(echo "$VERSION" | cut -d. -f2) + { + echo "version=$VERSION" + echo "major=$MAJOR" + echo "minor=$MINOR" + } >> "$GITHUB_OUTPUT" + + - name: Set up QEMU + # Required so buildx can cross-build linux/arm64 on the x86_64 + # ubuntu-latest runner. + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 + with: + platforms: linux/arm64 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + + - name: Log in to Docker Hub + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ulisesjeremias/create-awesome-python-app + # `latest` is pushed on tag pushes AND on explicit workflow_dispatch + # runs (which always execute against main, so they represent the + # current release). + tags: | + type=raw,value=latest + type=raw,value=${{ steps.version.outputs.version }} + type=raw,value=${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }} + type=raw,value=v${{ steps.version.outputs.major }} + labels: | + org.opencontainers.image.title=create-awesome-python-app + org.opencontainers.image.description=Composable scaffolding CLI for production-ready Python apps + org.opencontainers.image.url=https://github.com/Create-Python-App/create-python-app + org.opencontainers.image.source=https://github.com/Create-Python-App/create-python-app + org.opencontainers.image.licenses=MIT + org.opencontainers.image.version=${{ steps.version.outputs.version }} + - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . push: true - tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/create-awesome-python-app:latest + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ steps.version.outputs.version }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/smoke-distribution.yml b/.github/workflows/smoke-distribution.yml index 7140030..003db43 100644 --- a/.github/workflows/smoke-distribution.yml +++ b/.github/workflows/smoke-distribution.yml @@ -1,25 +1,152 @@ name: Distribution smoke tests + +# Smoke-tests each published distribution channel: +# uvx — PyPI via uvx +# docker — pull & run ulisesjeremias/create-awesome-python-app:latest +# homebrew — brew install on macos-latest +# aur — makepkg inside an archlinux container +# versions — cross-check channel versions against PyPI + on: schedule: - cron: "0 6 * * *" workflow_dispatch: + permissions: contents: read + +concurrency: + group: smoke-distribution + cancel-in-progress: true + jobs: uvx: + name: uvx (PyPI) runs-on: ubuntu-latest steps: - uses: astral-sh/setup-uv@v6 - - name: Smoke uvx (when published) + - name: Smoke uvx run: | - if uvx create-awesome-python-app@latest --help; then - echo ok - else - echo "Package not on PyPI yet — soft fail" - exit 0 - fi + set -euo pipefail + uvx create-awesome-python-app@latest --version + uvx create-awesome-python-app@latest --help | head -5 + docker: + name: Docker Hub + runs-on: ubuntu-latest + steps: + - name: Pull image + run: docker pull ulisesjeremias/create-awesome-python-app:latest + + - name: Verify --version + run: | + VERSION=$(docker run --rm ulisesjeremias/create-awesome-python-app:latest --version 2>&1) + echo "docker version: $VERSION" + echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' || { + echo "::error::Unexpected version output: $VERSION" + exit 1 + } + + - name: Verify --help + run: docker run --rm ulisesjeremias/create-awesome-python-app:latest --help | head -5 + + homebrew: + name: Homebrew (Create-Python-App/tap) + runs-on: macos-latest + steps: + - name: Tap and install + run: | + brew tap Create-Python-App/tap + brew trust create-python-app/tap || true + brew install create-awesome-python-app + + - name: Verify --version + run: | + VERSION=$(create-awesome-python-app --version 2>&1) + echo "homebrew version: $VERSION" + echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' || { + echo "::error::Unexpected version output: $VERSION" + exit 1 + } + + - name: Verify --help + run: create-awesome-python-app --help | head -5 + + aur: + name: AUR (archlinux container) runs-on: ubuntu-latest - continue-on-error: true + container: + image: archlinux:latest steps: - - run: echo "Docker smoke activates after first image publish" + - name: Bootstrap Arch system and non-root builder + run: | + pacman -Sy --noconfirm --needed \ + python python-pip python-build python-installer python-wheel \ + base-devel sudo git curl ca-certificates + useradd -m -G wheel builder + echo '%wheel ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + + - name: Clone AUR package and build + run: | + su builder -c " + set -euo pipefail + # Prefer live AUR; fall back to GitHub mirror during bootstrap. + if git clone https://aur.archlinux.org/create-awesome-python-app.git /tmp/cpa 2>/dev/null; then + cd /tmp/cpa + else + git clone https://github.com/Create-Python-App/aur-package.git /tmp/cpa + cd /tmp/cpa + fi + makepkg -si --noconfirm + " + + - name: Verify --version + run: | + su builder -c " + VERSION=\$(create-awesome-python-app --version 2>&1) + echo \"aur version: \$VERSION\" + echo \"\$VERSION\" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\$' || exit 1 + " + + - name: Verify --help + run: su builder -c "create-awesome-python-app --help | head -5" + + versions: + name: Cross-check published versions + runs-on: ubuntu-latest + if: always() + steps: + - uses: astral-sh/setup-uv@v6 + - name: Gather versions from each channel + run: | + set -euo pipefail + + PYPI=$(curl -sfL https://pypi.org/pypi/create-awesome-python-app/json | \ + python3 -c "import json,sys; print(json.load(sys.stdin)['info']['version'])") + + DOCKER=$(docker run --rm ulisesjeremias/create-awesome-python-app:latest --version 2>&1 \ + || echo "unavailable") + + HOMEBREW=$(curl -sfL \ + https://raw.githubusercontent.com/Create-Python-App/homebrew-tap/main/Formula/create-awesome-python-app.rb | \ + grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unavailable") + + AUR=$(curl -sfL \ + "https://aur.archlinux.org/rpc/v5/info?arg=create-awesome-python-app" | \ + python3 -c "import json,sys; d=json.load(sys.stdin); r=d.get('results') or []; print(r[0]['Version'].split('-')[0] if r else 'unavailable')" \ + || echo "unavailable") + + echo "Channel versions:" + echo " pypi: $PYPI" + echo " docker: $DOCKER" + echo " homebrew: $HOMEBREW" + echo " aur: $AUR" + + for CHANNEL_VER in "$DOCKER" "$HOMEBREW"; do + if [ "$CHANNEL_VER" != "unavailable" ] && [ "$CHANNEL_VER" != "$PYPI" ]; then + echo "::warning::Version mismatch vs pypi=$PYPI: $CHANNEL_VER" + fi + done + if [ "$AUR" != "unavailable" ] && [ "$AUR" != "$PYPI" ]; then + echo "::warning::Version mismatch: pypi=$PYPI aur=$AUR (AUR may lag)" + fi diff --git a/Dockerfile b/Dockerfile index 72ad4af..e63cbe8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,21 @@ -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim -WORKDIR /app -COPY . . -RUN uv sync --frozen --no-dev -ENTRYPOINT ["uv", "run", "create-awesome-python-app"] +# syntax=docker/dockerfile:1.9 +FROM python:3.12-slim-bookworm + +# VERSION is passed at build time by the publish workflow so the image +# is pinned to a specific PyPI package version (makes each image +# reproducible for its tag). +ARG VERSION=latest + +# hadolint ignore=DL3013 +RUN useradd --create-home --uid 1000 --shell /bin/bash app \ + && if [ "$VERSION" = "latest" ]; then \ + pip install --no-cache-dir create-awesome-python-app; \ + else \ + pip install --no-cache-dir "create-awesome-python-app==${VERSION}"; \ + fi + +USER app +WORKDIR /home/app + +ENTRYPOINT ["create-awesome-python-app"] +CMD ["--help"] diff --git a/README.md b/README.md index 0e3a003..4cf7a4e 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,11 @@ [![Lint](https://github.com/Create-Python-App/create-python-app/actions/workflows/lint.yml/badge.svg)](https://github.com/Create-Python-App/create-python-app/actions/workflows/lint.yml) [![Typecheck](https://github.com/Create-Python-App/create-python-app/actions/workflows/type-check.yml/badge.svg)](https://github.com/Create-Python-App/create-python-app/actions/workflows/type-check.yml) [![PyPI](https://img.shields.io/pypi/v/create-awesome-python-app.svg)](https://pypi.org/project/create-awesome-python-app/) +[![Docker](https://img.shields.io/docker/v/ulisesjeremias/create-awesome-python-app?style=flat-square&label=Docker&logo=docker&color=2496ED)](https://hub.docker.com/r/ulisesjeremias/create-awesome-python-app) +[![AUR](https://img.shields.io/aur/version/create-awesome-python-app?label=AUR&logo=archlinux)](https://aur.archlinux.org/packages/create-awesome-python-app) +[![Homebrew](https://img.shields.io/badge/homebrew-Create--Python--App%2Ftap-orange?logo=homebrew)](https://github.com/Create-Python-App/homebrew-tap) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) - - Composable scaffolding CLI for production-ready Python apps. > **Status:** CLI monorepo bootstrapped. Template bank: [`cpa-templates`](https://github.com/Create-Python-App/cpa-templates). Roadmap: [#1](https://github.com/Create-Python-App/create-python-app/issues/1). @@ -18,6 +19,8 @@ Composable scaffolding CLI for production-ready Python apps. |------------|------| | [create-python-app](https://github.com/Create-Python-App/create-python-app) (this repo) | CLI (`create-awesome-python-app`) and scaffolding engine (`create-python-app-core`) | | [cpa-templates](https://github.com/Create-Python-App/cpa-templates) | Official templates and extensions (`templates.json` catalog) | +| [homebrew-tap](https://github.com/Create-Python-App/homebrew-tap) | Homebrew formula | +| [aur-package](https://github.com/Create-Python-App/aur-package) | AUR PKGBUILD mirror | The CLI fetches the catalog from: @@ -28,7 +31,19 @@ Override with `CPA_CATALOG_URL` for forks or local testing (`file://` supported) ## Install ```bash +# PyPI / uv uvx create-awesome-python-app@latest my-app + +# Homebrew +brew tap Create-Python-App/tap +brew install create-awesome-python-app + +# AUR +yay -S create-awesome-python-app + +# Docker +docker run --rm -it -v "${PWD}:/app" -w /app \ + ulisesjeremias/create-awesome-python-app my-app ``` Or pin a version: @@ -96,8 +111,18 @@ uv sync --group dev ## Docker +Published image: [`ulisesjeremias/create-awesome-python-app`](https://hub.docker.com/r/ulisesjeremias/create-awesome-python-app) + +```bash +docker run --rm ulisesjeremias/create-awesome-python-app:0.1.0 --version +docker run --rm -it -v "${PWD}:/app" -w /app \ + ulisesjeremias/create-awesome-python-app my-app --template fastapi-starter --no-interactive +``` + +Local build (installs the given PyPI version into the image): + ```bash -docker build -t create-awesome-python-app . +docker build --build-arg VERSION=0.1.0 -t create-awesome-python-app . docker run --rm create-awesome-python-app --help ``` diff --git a/docs/DISTRIBUTION_SETUP.md b/docs/DISTRIBUTION_SETUP.md index 6bcc0c3..57574b2 100644 --- a/docs/DISTRIBUTION_SETUP.md +++ b/docs/DISTRIBUTION_SETUP.md @@ -7,37 +7,19 @@ | **PyPI** | `publish.yml` | OIDC Trusted Publishing (no token) | | **Docker** | `publish-docker.yml` | `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN` | | **AUR** | `publish-aur.yml` | `AUR_SSH_PRIVATE_KEY`, `AUR_REPO_TOKEN` | -| **Homebrew** | `notify-homebrew.yml` | `HOMEBREW_TAP_TOKEN` | +| **Homebrew** | `notify-homebrew.yml` → `homebrew-tap` | `HOMEBREW_TAP_TOKEN` | Configure secrets under **Settings → Secrets and variables → Actions**. ## PyPI Trusted Publishing The Release job uses the GitHub Actions environment **`pypi`** -(Settings → Environments). Configure **two** pending Trusted Publishers on PyPI -(same workflow publishes both packages): +(Settings → Environments). Configure Trusted Publishers on PyPI for: -### `create-python-app-core` - -| Field | Value | -|-------|--------| -| PyPI Project Name | `create-python-app-core` | -| Owner | `Create-Python-App` | -| Repository name | `create-python-app` | -| Workflow name | `publish.yml` | -| Environment name | `pypi` | - -### `create-awesome-python-app` - -| Field | Value | -|-------|--------| -| PyPI Project Name | `create-awesome-python-app` | -| Owner | `Create-Python-App` | -| Repository name | `create-python-app` | -| Workflow name | `publish.yml` | -| Environment name | `pypi` | - -On the first successful publish for tag `create-awesome-python-app@0.1.0`, OIDC creates the projects and uploads sdists/wheels for both packages. +| Project | Owner | Repository | Workflow | Environment | +|---------|-------|------------|----------|-------------| +| `create-python-app-core` | `Create-Python-App` | `create-python-app` | `publish.yml` | `pypi` | +| `create-awesome-python-app` | `Create-Python-App` | `create-python-app` | `publish.yml` | `pypi` | ## Cutting a release @@ -47,21 +29,103 @@ On the first successful publish for tag `create-awesome-python-app@0.1.0`, OIDC 4. Tag and push: ```bash -git tag create-awesome-python-app@0.1.0 -git push origin create-awesome-python-app@0.1.0 +git tag create-awesome-python-app@X.Y.Z +git push origin create-awesome-python-app@X.Y.Z ``` -5. Confirm the Release workflow published both packages -6. Smoke: `uvx create-awesome-python-app@0.1.0 --help` +Then: + +1. Confirm Release (PyPI), Notify Homebrew, Publish to AUR, and Docker workflows +2. Smoke: `uvx create-awesome-python-app@X.Y.Z --help` ## Docker Hub -Create a write token and set `DOCKERHUB_USERNAME` / `DOCKERHUB_TOKEN`. +Image: [`ulisesjeremias/create-awesome-python-app`](https://hub.docker.com/r/ulisesjeremias/create-awesome-python-app) + +Secrets (already used by CNA; reuse the same Hub account): + +- `DOCKERHUB_USERNAME` — e.g. `ulisesjeremias` +- `DOCKERHUB_TOKEN` — Hub access token with write scope + +Verify: + +```bash +gh workflow run "Publish Docker image" --repo Create-Python-App/create-python-app -f version=0.1.0 +docker run --rm ulisesjeremias/create-awesome-python-app:0.1.0 --version +``` + +## AUR (`AUR_SSH_PRIVATE_KEY`, `AUR_REPO_TOKEN`) + +**Prereqs**: An AUR account that can own `create-awesome-python-app`, and the mirror +[`Create-Python-App/aur-package`](https://github.com/Create-Python-App/aur-package). + +### Bootstrap the AUR package (first release only) + +```bash +ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts + +cd /tmp +rm -rf aur-bootstrap +git clone git@github.com:Create-Python-App/aur-package.git aur-bootstrap +cd aur-bootstrap +git remote add aur ssh://aur@aur.archlinux.org/create-awesome-python-app.git +git push aur main:master +``` + +If AUR rejects the push because the package does not exist yet, create it via +[aur.archlinux.org/submit](https://aur.archlinux.org/submit) first, then retry. + +### Generate / register the AUR SSH key -## AUR +```bash +ssh-keygen -t ed25519 -C "aur-publish-cpa" -f ~/.ssh/aur_publish_cpa -N "" +cat ~/.ssh/aur_publish_cpa.pub +``` + +Paste the public key under [AUR → My Account → SSH Public Key](https://aur.archlinux.org/account). +Paste the **private** key as repo secret `AUR_SSH_PRIVATE_KEY`. + +### Generate `AUR_REPO_TOKEN` + +Fine-grained PAT with **Contents: Read and write** on `Create-Python-App/aur-package` only. +Store as `AUR_REPO_TOKEN`. + +## Homebrew (`HOMEBREW_TAP_TOKEN`) + +**Prereqs**: [`Create-Python-App/homebrew-tap`](https://github.com/Create-Python-App/homebrew-tap) +with `Formula/create-awesome-python-app.rb` and `update-formula.yml`. + +Fine-grained PAT with: + +- Repository: `Create-Python-App/homebrew-tap` +- **Contents**: Read and write +- **Actions**: Read and write (needed for `repository_dispatch`) + +Store as `HOMEBREW_TAP_TOKEN`. -Bootstrap PKGBUILD in a future `Create-Python-App/aur-package` mirror; set SSH key secret. +Install: + +```bash +brew tap Create-Python-App/tap +brew install create-awesome-python-app +``` + +## Verification + +```bash +# Homebrew notify +gh workflow run "Notify Homebrew tap" --repo Create-Python-App/create-python-app -f version=0.1.0 + +# AUR publish +gh workflow run "Publish to AUR" --repo Create-Python-App/create-python-app -f version=0.1.0 + +# End-user checks +uvx create-awesome-python-app@0.1.0 --version +brew install create-awesome-python-app && create-awesome-python-app --version +yay -S create-awesome-python-app && create-awesome-python-app --version +``` -## Homebrew +## After secrets are in place -Create `Create-Python-App/homebrew-tap` and allow `repository_dispatch` with `HOMEBREW_TAP_TOKEN`. +Every subsequent release only requires tagging `create-awesome-python-app@X.Y.Z`. +PyPI, Homebrew notify, AUR, and Docker workflows fan out from that tag.