--- since: 1.1.1 --- # Binary Distribution Operational reference for compiling, packaging, and releasing gitaiflow as a native standalone binary across four platforms, via the GitLab Generic Package Registry and Cloudflare R2. ## Distribution Architecture ```mermaid flowchart TD REPO["๐Ÿ“ฆ Source Repository
GitLab: clizero/gitaiflow (private)"] MAKE["โš™๏ธ make all
test โ†’ build-all โ†’ release"] NUITKA["๐Ÿ”จ Nuitka / PyInstaller
--standalone --onefile"] BUILDS["๐Ÿ–ฅ๏ธ Platform Binaries
darwin-arm64 ยท linux-amd64
linux-arm64 ยท windows-amd64
"] REGISTRY["๐Ÿ“ฅ GitLab Generic Package Registry
versioned + latest per asset"] R2["โ˜๏ธ Cloudflare R2
djangoplay-data/gitaiflow/installers/"] NGINX["๐ŸŒ install.djangoplay.org
redirect to R2"] USER["๐Ÿ‘ค End User
curl / irm"] REPO --> MAKE MAKE --> NUITKA NUITKA --> BUILDS BUILDS --> REGISTRY BUILDS --> R2 R2 --> NGINX NGINX --> USER classDef source fill:#e6f1fb,stroke:#2474b5,stroke-width:2px,color:#174d7a classDef build fill:#fff2d9,stroke:#d89b18,stroke-width:2px,color:#76520b classDef storage fill:#eeeaff,stroke:#7655c7,stroke-width:2px,color:#4a3485 classDef edge fill:#e5f6f1,stroke:#159a7a,stroke-width:2px,color:#075d4b class REPO source class MAKE,NUITKA,BUILDS build class REGISTRY,R2 storage class NGINX,USER edge ``` ## Overview * **Source Repository**: GitLab (`clizero/gitaiflow`) โ€” private repository. * **Public Edge Distribution**: standalone binaries hosted on Cloudflare R2 under `djangoplay-data/gitaiflow/installers/`. * **Public Domains**: * macOS/Linux: `https://install.djangoplay.org/gitaiflow` * Windows: `https://install.djangoplay.org/gitaiflow.ps1` ### Complete Asset Coverage on R2 | Binary File | Target OS / Environment | Target Architecture | | :--- | :--- | :--- | | `gitaiflow-darwin-arm64` | macOS (Apple Silicon: M1, M2, M3, M4) | ARM 64-bit (`arm64`) | | `gitaiflow-linux-amd64` | Linux (Ubuntu, Debian, RedHat, standard cloud VMs) | Intel/AMD 64-bit (`x86_64`) | | `gitaiflow-linux-arm64` | Linux (AWS Graviton, Raspberry Pi, Apple Silicon Docker) | ARM 64-bit (`aarch64`/`arm64`) | | `gitaiflow-windows-amd64.exe` | Windows 10, 11, Server | 64-bit (`x86_64`) | --- ## Section A: Cross-Platform Binary Builds All four platform targets are wired as `Makefile` targets, run from the repository root: ```bash make build-all # runs, in sequence: compile (macOS) โ†’ build-linux-amd64 โ†’ build-linux-arm64 โ†’ build-windows ``` ### 1. macOS Apple Silicon (`darwin-arm64`) โ€” `make compile` Runs `scripts/install/compile.sh` locally (not in a container โ€” this target must run on actual macOS ARM64 hardware), producing `bin/gitaiflow`, then copies it to `installers/gitaiflow-darwin-arm64`. ### 2. Linux x86_64 (`linux-amd64`) โ€” `make build-linux-amd64` ```bash docker run --rm --platform linux/amd64 -v "$PWD":/workspace -w /workspace python:3.11-bookworm bash -c ' apt-get update && apt-get install -y --no-install-recommends gcc g++ ccache patchelf pip install -U nuitka zstandard pip install -e . python -m nuitka --standalone --onefile --remove-output \ --include-package=gitaiflow \ --include-package-data=gitaiflow \ --include-package=httpx \ --include-package-data=certifi \ --output-dir=installers \ --output-filename=gitaiflow-linux-amd64 \ gitaiflow/generate_summary.py ' ``` ### 3. Linux ARM64 (`linux-arm64`) โ€” `make build-linux-arm64` Identical container recipe to the amd64 build, run under `--platform linux/arm64`, producing `installers/gitaiflow-linux-arm64`. ### 4. Windows x86_64 (`windows-amd64`) โ€” `make build-windows` ```bash docker run --rm --platform linux/amd64 -v "$PWD":/workspace -w /workspace tobix/pywine:3.11 bash -c ' wine pip install -U pyinstaller wine pip install -e . wine pyinstaller \ --onefile \ --clean \ --noupx \ --name gitaiflow-windows-amd64 \ --add-data "gitaiflow/prompts;gitaiflow/prompts" \ --add-data "gitaiflow/templates;gitaiflow/templates" \ --collect-all gitaiflow \ --collect-all httpx \ --collect-all certifi \ gitaiflow/generate_summary.py ' mv dist/gitaiflow-windows-amd64.exe installers/ rm -rf build dist gitaiflow-windows-amd64.spec ``` As with astwire, the Windows build uses **PyInstaller** inside Wine rather than Nuitka โ€” cross-compiling a native Windows PE with Nuitka from a Linux container isn't supported. The `--add-data` flags explicitly bundle `gitaiflow/prompts/` and `gitaiflow/templates/` (the AI system prompt and the changelog Markdown template), since PyInstaller doesn't pick up non-Python package data automatically the way `--include-package-data` does under Nuitka. ### 5. Verify Artifact Architecture Headers ```bash file installers/* ``` Expected output: * `installers/gitaiflow-darwin-arm64`: `Mach-O 64-bit executable arm64` * `installers/gitaiflow-linux-amd64`: `ELF 64-bit LSB pie executable, x86-64` * `installers/gitaiflow-linux-arm64`: `ELF 64-bit LSB pie executable, ARM aarch64` * `installers/gitaiflow-windows-amd64.exe`: `PE32+ executable (console) x86-64, for MS Windows` --- ## Section B: Cloudflare R2 Sync via Dynamic Manifest ### 1. Structure ```text scripts/ โ”œโ”€โ”€ install/ โ”‚ โ”œโ”€โ”€ compile.sh โ”‚ โ”œโ”€โ”€ install.sh โ”‚ โ””โ”€โ”€ install.ps1 โ””โ”€โ”€ r2/ โ”œโ”€โ”€ gitai_manifest.json โ”œโ”€โ”€ gitai_r2.sh โ””โ”€โ”€ generate_gitai_manifest.py installers/ โ”œโ”€โ”€ gitaiflow-darwin-arm64 โ”œโ”€โ”€ gitaiflow-linux-amd64 โ”œโ”€โ”€ gitaiflow-linux-arm64 โ””โ”€โ”€ gitaiflow-windows-amd64.exe ``` ### 2. Generate Manifest ```bash make manifest # python3 scripts/r2/generate_gitai_manifest.py ``` Defaults to base directory = the current Git repository root, and default targets `scripts/install/install.sh`, `scripts/install/install.ps1`, and `installers/`. Installer scripts are flattened to top-level R2 keys (dropping their `scripts/install/` prefix); everything under `installers/` keeps its relative path. Output is written to `scripts/r2/gitai_manifest.json`. ### 3. Sync ```bash make sync # runs `manifest`, then chmod +x scripts/r2/gitai_r2.sh && ./scripts/r2/gitai_r2.sh ``` `gitai_r2.sh` syncs to `djangoplay-r2:djangoplay-data/gitaiflow` (overridable via `-r`/`--remote`) using `rclone copy` with `--checksum`, so a re-run against unchanged files produces zero unnecessary writes to Cloudflare R2. It syncs, in order: the `installers/` directory, `install.sh`, `install.ps1`, and the manifest file itself. --- ## Section C: Release Process ### 1. `make all` ```bash make all # test -> build-all -> release ``` `release` shells out to `scripts/release/registry_update.sh $(VERSION)`, where `VERSION` is derived from `pyproject.toml`'s `[project].version` (prefixed with `v`) unless overridden or resolved from the current Git tag. ### 2. `registry_update.sh` โ€” Package Registry Upload + R2 Sync Installers are now stored under a versioned path, `installers/vX.Y.Z/`, rather than flat โ€” `registry_update.sh` resolves the version from `pyproject.toml` if not passed explicitly, and `SKIP_R2=1` skips the R2 sync leg for a package-registry-only run. `gitai_r2.sh` also writes `installers/latest.txt` from the resolved version (or `GITAIFLOW_LATEST_VERSION`) so `install.sh`/`install.ps1` can resolve "latest" without hardcoding it, and its R2 remote is configurable via `R2_REMOTE`. For each file under `installers/`, uploads it to the GitLab Generic Package Registry twice โ€” once under the release version, once under `latest`: ```bash BASE_URL="https://gitlab.com/api/v4/projects/${PROJECT_ID}/packages/generic/gitaiflow" curl --fail-with-body --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \ --upload-file "installers/gitaiflow-linux-amd64" \ "${BASE_URL}/${VERSION}/gitaiflow-linux-amd64" curl --fail-with-body --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \ --upload-file "installers/gitaiflow-linux-amd64" \ "${BASE_URL}/latest/gitaiflow-linux-amd64" ``` `PROJECT_ID` defaults to `codefleet-labs%2Fgitaiflow`, overridable via the `GITLAB_PROJECT_ID` environment variable โ€” set this to match whichever GitLab project path is currently authoritative before running a release. `GITLAB_TOKEN` (a token with `api` scope) is required. After the package-registry upload, the script regenerates the R2 manifest and runs the R2 sync automatically โ€” a single `registry_update.sh` invocation handles both distribution surfaces. If `bin/gitaiflow` exists locally (from `make compile`) but `installers/gitaiflow-darwin-arm64` doesn't yet, the script copies it into place first, so a fresh `make compile && ./scripts/release/registry_update.sh vX.Y.Z` works without a separate manual copy step. ### 3. macOS-Specific Upload ```bash make release-mac ``` Runs `compile` (builds `bin/gitaiflow` on macOS ARM64 hardware), uploads it directly to the GitLab Generic Package Registry at the `clizero%2Fgitaiflow` project path under both the current `VERSION` and `latest`, then runs `sync`. This exists because CI cannot cross-compile a macOS binary โ€” the mac asset is always built and uploaded from real Apple Silicon hardware. ### 4. Git Release Tagging ```bash git tag -a v1.0.0 -m "Release v1.0.0" git push origin v1.0.0 ``` `Makefile`'s `VERSION` variable is derived from `pyproject.toml` by default (`VERSION ?= v$(PYPROJECT_VERSION)`), but `registry_update.sh` also accepts an explicit tag as its first argument, or falls back to `git describe --tags --exact-match` if none is given โ€” so tagging the release commit and running `make release` (or `./scripts/release/registry_update.sh vX.Y.Z` directly) both work. This step can now be delegated to `scripts/release/tag_release.sh`, which creates/pushes the annotated tag to both the GitLab and GitHub remotes, then creates the GitLab and GitHub releases themselves from `RELEASE_NOTES.md` (its `## Release Title` / `## Release Draft` sections) and links the built binaries from the GitLab package registry. Individual legs can be skipped with `SKIP_TAG`, `SKIP_RELEASE`, `SKIP_GITLAB`, or `SKIP_GITHUB`. Generate `RELEASE_NOTES.md` first with `gitaiflow --release-notes` (reads the current `CHANGELOG.md` entry and calls the AI) โ€” the release Makefile target does this automatically, with `SKIP_RELEASE_NOTES=1` as an escape hatch. --- ## Section D: Verification & Integration Testing ### 1. Local Machine Install Verification (macOS ARM64) ```bash curl -fsSL https://install.djangoplay.org/gitaiflow | bash gitaiflow --version gitaiflow --help ``` ### 2. Linux Container Install Verification **A. Linux x86_64 (`amd64`):** ```bash docker run --rm -it --platform linux/amd64 python:3.11-slim bash -c " apt-get update && apt-get install -y curl sudo && \ curl -fsSL https://install.djangoplay.org/gitaiflow | bash && \ gitaiflow --version && \ gitaiflow --help " ``` **B. Linux ARM64 (`aarch64`):** ```bash docker run --rm -it --platform linux/arm64 python:3.11-slim bash -c " apt-get update && apt-get install -y curl sudo && \ curl -fsSL https://install.djangoplay.org/gitaiflow | bash && \ gitaiflow --version && \ gitaiflow --help " ``` ### 3. Windows PowerShell Install Verification ```powershell irm https://install.djangoplay.org/gitaiflow.ps1 | iex gitaiflow --version gitaiflow --help ``` ### 4. Functional Smoke Test (all platforms) Installation succeeding is necessary but not sufficient โ€” verify the compiled binary can actually reach an AI provider and write a real artifact, since bundled dependencies (`httpx`, `certifi`, and โ€” on Windows โ€” the `prompts`/`templates` data files) are the most likely thing to silently break in a from-source-to-binary compile: ```bash AI_PROVIDER=custom AI_BASE_URL=https://openrouter.ai/api/v1 \ AI_API_KEY= AI_MODEL=openrouter/free \ gitaiflow --path . --change-summary ``` --- ## Related - [`package-release.md`](package-release.md) โ€” condensed release checklist - [`local-development.md`](local-development.md) โ€” local build/test setup - [`../runbooks/release.md`](../runbooks/release.md) โ€” step-by-step release runbook