---
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