Mando CLI GitHub Build Mirror

Cross-platform binary build pipeline using GitHub Actions, mirroring source from GitLab.

Architecture

graph LR
    GL["GitLab<br/>Source"] -->|submodule| GH["GitHub Mirror<br/>wowjeeez/mando_cli_mirror"]
    GH -->|v* tag| BA["GitHub Actions<br/>8 platform builds"]
    BA -->|artifacts| GHR["GitHub Release"]
    BA -->|package registry| GLR["GitLab Release"]
    GLR -->|glab api| USR["User Install"]
ComponentLocation
Sourcegitlab.com/alpiq_cicd/sales-and-origination/flexible-assets/bess/poc/mando-cli
Build mirrorgithub.com/wowjeeez/mando_cli_mirror
ConnectionGit submodule via SSH

Why GitHub?

GitLab CI runners at Alpiq don’t cover macOS/Windows. GitHub Actions provides native runners for all target platforms.

Build Targets

OSArchitectureArtifactRunner
Linuxx86_64 (glibc)mando-linux-x86_64ubuntu-latest
LinuxARM64 (glibc)mando-linux-aarch64ubuntu-latest
Linuxx86_64 (musl)mando-linux-x86_64-muslubuntu-latest
LinuxARM64 (musl)mando-linux-aarch64-muslubuntu-latest
macOSx86_64mando-macos-x86_64macos-latest (cross)
macOSARM64mando-macos-aarch64macos-latest
Windowsx86_64mando-windows-x86_64.exewindows-latest
WindowsARM64mando-windows-aarch64.exewindows-latest

macos-13 deprecated

Originally used macos-13 for Intel builds, but runners are scarce and hang. Switched to cross-compilation on macos-latest (ARM64).

Workflows

ci.yml — PR/Push CI

  • Triggers: push/PR to main/master
  • Jobs: cargo check, clippy -D warnings, fmt --check, test

build.yml — Release Builds

  • Triggers: v* tags, workflow_dispatch
  • 8 parallel build jobs → GitHub Release + GitLab Package Registry
  • ARM64 Linux cross-compilation uses gcc-aarch64-linux-gnu linker

GitLab Release Flow

  1. Upload binaries to Generic Package Registry (packages/generic/mando/{tag}/{filename})
  2. Create/recreate tag on GitLab (handles re-tags)
  3. Delete existing release if present (handles re-tags)
  4. Create release with asset links pointing to package registry URLs

Package Registry, not Project Uploads

Project uploads (POST /uploads) require web session auth — can’t be downloaded via API tokens. Package Registry URLs work with standard API auth via glab.

GitHub Secrets

SecretPurpose
GITLAB_SSH_KEYPersonal SSH key for submodule clone
GITLAB_TOKENGitLab legacy PAT (api scope) for package uploads + releases

Install Scripts

Located in scripts/install.sh (bash) and scripts/install.ps1 (PowerShell).

One-liners

Linux/macOS:

glab api "projects/alpiq_cicd%2Fsales-and-origination%2Fflexible-assets%2Fbess%2Fpoc%2Fmando-cli/repository/files/scripts%2Finstall.sh/raw?ref=main" | bash

Windows:

glab api "projects/alpiq_cicd%2Fsales-and-origination%2Fflexible-assets%2Fbess%2Fpoc%2Fmando-cli/repository/files/scripts%2Finstall.ps1/raw?ref=main" | iex

How it works

  1. glab api fetches the script from GitLab (handles auth)
  2. Script detects OS + arch
  3. glab api fetches latest release tag
  4. glab api downloads binary from Package Registry → temp file
  5. Moves to install dir (auto sudo if needed on macOS/Linux)

All output to stderr

Script output goes to stderr for pipe compatibility — stdout is the pipe feeding bash when using glab api ... | bash.

Defaults

PlatformInstall DirAdmin Required
Linux/macOS/usr/local/binsudo (auto-escalated)
Windows%USERPROFILE%\.local\binNo

Override with INSTALL_DIR env var.

Submodule Update Workflow

cd mando-cli && git fetch origin && git checkout origin/main
cd .. && git add mando-cli && git commit -m "Update submodule" && git push origin master:main

Local Dev

tmando zsh alias → /Volumes/bandi/coding/poc/mando-cli/target/release/mando

Key Design Decisions

DecisionReason
GitHub for builds, GitLab for sourceAlpiq runners lack macOS/Windows
Package Registry over project uploadsUpload URLs need web session auth, not API-compatible
glab api for install authglab auth status -t exposes different token than what glab uses internally
Delete + recreate releasesHandles re-tagged versions cleanly
macos-latest + cross-compilemacos-13 runners hang waiting for pickup

Parallel release flow (2026-05)

Restructured .github/workflows/build.yml so binaries publish to GitLab as soon as each target builds, instead of waiting for the slowest matrix job (Windows aarch64) before any GitLab work happens.

Old shape

Single gitlab-release job with needs: build waited on the full matrix, then sequentially: created tag, deleted+recreated release, uploaded all binaries, posted all asset links. Linux users waited on Windows.

New shape — 4 jobs

  1. init-gitlab-release — runs first on tag pushes. Creates GitLab tag (idempotent || true), DELETEs any existing release for the tag, POSTs a fresh empty release. Acts as upload precondition for the matrix.
  2. build matrix — needs: init-gitlab-release with if: always() && (result == 'success' || result == 'skipped') so non-tag workflow_dispatch runs still work. After actions/upload-artifact, each matrix job does curl --upload-file to GitLab Package Registry, then POSTs an asset link to /releases/${TAG}/assets/links. Each binary publishes the moment its target finishes — Linux/macOS no longer block on Windows.
  3. release (GitHub) — unchanged, needs: build.
  4. gitlab-finalizeneeds: build. Only computes the aggregate checksums.txt. Still gated on the full matrix because checksums are logically all-or-nothing; binaries themselves are already live by then.

Design notes

  • Concurrent POSTs to /releases/:tag/assets/links from parallel matrix jobs are safe — each link is an independent record, no race.
  • DELETE + POST in init-gitlab-release is needed for re-tag safety (clean asset-links state before the matrix starts publishing).
  • GITLAB_PROJECT env var moved to top-level workflow env (was per-job before).
  • URL pattern ${API}/packages/generic/mando/${TAG}/${filename} preserved so install scripts in the upstream GitLab repo (scripts/install.sh, scripts/install.ps1) keep working unchanged.

Edge case

No concurrency group on the workflow, so re-pushing a tag while a previous run is still building can produce overlapping API calls into the same release. Pre-existing limitation, not introduced by this restructure.

Local one-off Linux build

This note covers the automated CI pipeline. To build a Linux/WSL binary manually from an Apple Silicon Mac (e.g. ad-hoc for a tester), see mando-cli-wsl-linux-build — it documents the --platform linux/amd64 and poc/-parent-mount gotchas that CI’s native x86_64 runners never hit.