Preskoči na sadržaj

Kako se bozana launcher samodistribuira?

TL;DR

bozana je Rust binarni launcher za ci-artifacts. U normal-modu čita bozana.json pin fajl, provjerava lokalni cache (hardening + sha256), po potrebi download-uje verzionirani binarni iz Bitbucket Downloads, verifikuje ga, i exec-uje. Loop-free: kad je TEKUĆI binarni već pin-ovana verzija, dispatch-uje verb kroz potrošačev ci/run.py umjesto samog sebe.

Šta je bozana?

bozana (crate: launcher-rs/) je single-binary Rust port Python launchera (launcher/ci_launcher.py). Zamjenjuje python3 ci/run.py <verb> poziv u potrošačkim repozitorijumima — potrošač treba samo jedan binarni fajl da pokrene CI pipeline.

Source of truth: launcher-rs/src/normal.rs (kod) + launcher-rs/CLAUDE.md (operativna pravila; može sadržavati stale preostali rad — provjeri src/normal.rs za aktivnu implementaciju).

bozana.json pin ugovor

Potrošački repozitorijum sadrži bozana.json u root-u (ili bilo kojem parent direktorijumu):

{
  "version": "0.2.0",
  "sha256": {
    "aarch64-apple-darwin": "<64 hex chars>",
    "x86_64-unknown-linux-gnu": "<64 hex chars>"
  }
}
  • versionCargo.toml verzija; koristi se kao cache-path segment i Downloads filename. Validacija: alnum prvi char, zatim [A-Za-z0-9._-], max 64.
  • sha256 — per-platform hex digest. Launcher bira svoj CI_TARGET_TRIPLE (inject-uje build.rs).

Pin je variant A — zaseban fajl, NE u services.json. Operater ga manuelno ažurira nakon što publish step emituje novi pin block.

Normal-mode protok

flowchart TD
    A[bozana \<verb\>] --> B{dev-mode?}
    B -- Da --> C[exec python3 ci/run.py]
    B -- Ne --> D[resolve_pin: nađi bozana.json]
    D --> E{LAUNCHER_VERSION == pin.version?}
    E -- Da --> F[Loop-free: nađi ci/run.py, exec python3]
    E -- Ne --> G{cache_hit?}
    G -- Da --> H[exec cached binary]
    G -- Ne --> I[download + sha256 verify]
    I --> H

Pet koraka:

  1. Pin resolve — hoda od cwd naviše, nalazi prvi bozana.json, čita version + sha256[<triple>].
  2. Loop-free dispatch — ako je LAUNCHER_VERSION == pin.version, ovaj binarni JE pin-ovana verzija. Ne download-uje sam sebe (beskonačna petlja); umjesto toga nalazi potrošačev DOWNLOADED ci/run.py (bez pyproject markera) i exec-uje python3 ci/run.py <args>.
  3. Cache check<CI_BIN_DIR or ~/.local/share/ci-artifacts-binaries>/bozana/bozana-<version>-<triple>. FULL ci/tool_installer.py _cache_hit disciplina (D5): symlink → regular file → owner exec bit → uid == euid → group/world writable → tek onda sha256.
  4. Download — iz Bitbucket Downloads. S3 redirect: 302 → čisti Authorization header (S3 odbija "Only one auth mechanism"). Stream + hash na temp fajl, sha256 verify, fchmod 0o755, atomic rename.
  5. Execexecv(cached_binary, original_argv).

Cache disciplina (D5)

Cache hardening je namjerna replika ci/tool_installer.py _cache_hit — nije shortcut. Redoslijed provjera (prije sha256):

  1. symlink? → reject (re-download)
  2. regular file? (fstat na OTVORENOM fd, ne na path)
  3. owner exec bit (mode & 0o100)
  4. uid == euid (isti korisnik)
  5. group/world writable (mode & 0o22 != 0) → reject
  6. sha256 na ISTOM fd (fd-pinning: metadata + hash = isti inode)

Zašto hardening prije checksuma: isti-uid prior job može forsirati matching checksum. uid/mode/symlink provjere minimiziraju TOCTOU prozor do execv. Rezidualni window ostaje (exec ide po path, ne po fd) — fexecve bi ga zatvorio ali nije portabilan na macOS; prihvaćen je kao dio threat modela (normal.rs:440-445).

HTTP-call invariant izuzetak

Odvojeni HTTP-location carve-out

HTTP za bozana launcher živi u launcher-rs/src/normal.rs (UreqGet), NE u vendor api/api.py. Launcher je standalone distribucioni artifact koji radi PRIJE nego ci/ postoji na mašini — ne može pozvati vendor.api.api.

Poštuje invariantovu namjeru: explicit User-Agent (bozana-launcher/<version>), timeout (120s), nema composed auth na S3 redirect. ureq + rustls (pure-Rust TLS, bez system openssl).

Detalji: launcher-rs/CLAUDE.md §Normal mode i root CLAUDE.md "HTTP-call invariant".

Autentikacija za download

Precedenca (mirroruje ci-download-scripts.sh):

  1. BITBUCKET_API_TOKEN + BITBUCKET_USERNAME → Basic auth
  2. else BITBUCKET_WORKSPACE_TOKENx-token-auth
  3. else → exit 2 (samo na cache miss; topli cache ne treba creds)

BITBUCKET_WORKSPACE defaultuje na infopuls.

Publish pipeline

ci/cargo/publish.py gradi --release, sha256, upload-uje na Bitbucket Downloads, emituje pin block. Idempotentno: preskače asset koji već postoji (list_downloads check) — Bitbucket Downloads zamjenjuje istoimene fajlove, pa re-upload pod istom verzijom bi mutirao verzionirani artifact.

CI wiring: &publish-launcher-rs step u custom: release, nakon Build & Deploy stage. Samo macOS danas; Linux publish je deferan (D9 / baby-step 5).

Idempotentno ponašanje: ci/cargo/publish.py provjerava list_downloads prije upload-a — ako asset već postoji, preskače upload i NE emituje pin block (publish.py:131-139). Operator koji rerun-uje job dobija "no pin emitted" poruku, ne novi pin.

Exit kodovi

Kod Značenje
0 uspjeh (exec zamjena, ili self --info)
1 prolazni — retry može pomoći (mreža, HTTP 5xx/404, sha256 mismatch)
2 konfiguracija — retry beskoristan (nema bozana.json / python3 / creds / HTTP 401/403)

Vidi i