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.
On this page ▾
- Distribution Architecture
- Overview
- Complete Asset Coverage on R2
- Section A: Cross-Platform Binary Builds
- 1. macOS Apple Silicon (darwin-arm64) — make compile
- 2. Linux x86_64 (linux-amd64) — make build-linux-amd64
- 3. Linux ARM64 (linux-arm64) — make build-linux-arm64
- 4. Windows x86_64 (windows-amd64) — make build-windows
- 5. Verify Artifact Architecture Headers
- Section B: Cloudflare R2 Sync via Dynamic Manifest
- 1. Structure
- 2. Generate Manifest
- 3. Sync
- Section C: Release Process
- 1. make all
- 2. registry_update.sh — Package Registry Upload + R2 Sync
- 3. macOS-Specific Upload
- 4. Git Release Tagging
- Section D: Verification & Integration Testing
- 1. Local Machine Install Verification (macOS ARM64)
- 2. Linux Container Install Verification
- 3. Windows PowerShell Install Verification
- 4. Functional Smoke Test (all platforms)
- Related
Distribution Architecture
flowchart TD
REPO["📦 Source Repository<br/><small>GitLab: clizero/gitaiflow (private)</small>"]
MAKE["⚙️ make all<br/><small>test → build-all → release</small>"]
NUITKA["🔨 Nuitka / PyInstaller<br/><small>--standalone --onefile</small>"]
BUILDS["🖥️ Platform Binaries<br/><small>darwin-arm64 · linux-amd64<br/>linux-arm64 · windows-amd64</small>"]
REGISTRY["📥 GitLab Generic Package Registry<br/><small>versioned + latest per asset</small>"]
R2["☁️ Cloudflare R2<br/><small>djangoplay-data/gitaiflow/installers/</small>"]
NGINX["🌐 install.djangoplay.org<br/><small>redirect to R2</small>"]
USER["👤 End User<br/><small>curl / irm</small>"]
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 edgeOverview
- 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
- macOS/Linux:
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:
make build-all
# runs, in sequence: compile (macOS) → build-linux-amd64 → build-linux-arm64 → build-windows1. 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
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
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.specAs 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
file installers/*Expected output:
installers/gitaiflow-darwin-arm64:Mach-O 64-bit executable arm64installers/gitaiflow-linux-amd64:ELF 64-bit LSB pie executable, x86-64installers/gitaiflow-linux-arm64:ELF 64-bit LSB pie executable, ARM aarch64installers/gitaiflow-windows-amd64.exe:PE32+ executable (console) x86-64, for MS Windows
Section B: Cloudflare R2 Sync via Dynamic Manifest
1. Structure
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.exe2. Generate Manifest
make manifest
# python3 scripts/r2/generate_gitai_manifest.pyDefaults 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
make sync
# runs `manifest`, then chmod +x scripts/r2/gitai_r2.sh && ./scripts/r2/gitai_r2.shgitai_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
make all
# test -> build-all -> releaserelease 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:
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
make release-macRuns 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
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0Makefile'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)
curl -fsSL https://install.djangoplay.org/gitaiflow | bash
gitaiflow --version
gitaiflow --help2. Linux Container Install Verification
A. Linux x86_64 (amd64):
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):
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
irm https://install.djangoplay.org/gitaiflow.ps1 | iex
gitaiflow --version
gitaiflow --help4. 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:
AI_PROVIDER=custom AI_BASE_URL=https://openrouter.ai/api/v1 \
AI_API_KEY=<key> AI_MODEL=openrouter/free \
gitaiflow --path . --change-summaryRelated
package-release.md— condensed release checklistlocal-development.md— local build/test setup../runbooks/release.md— step-by-step release runbook