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>"
}
}
version—Cargo.tomlverzija; 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 svojCI_TARGET_TRIPLE(inject-ujebuild.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:
- Pin resolve — hoda od cwd naviše, nalazi prvi
bozana.json, čitaversion+sha256[<triple>]. - 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 DOWNLOADEDci/run.py(bez pyproject markera) i exec-ujepython3 ci/run.py <args>. - Cache check —
<CI_BIN_DIR or ~/.local/share/ci-artifacts-binaries>/bozana/bozana-<version>-<triple>. FULLci/tool_installer.py_cache_hitdisciplina (D5): symlink → regular file → owner exec bit → uid == euid → group/world writable → tek onda sha256. - Download — iz Bitbucket Downloads. S3 redirect: 302 →
čisti
Authorizationheader (S3 odbija "Only one auth mechanism"). Stream + hash na temp fajl, sha256 verify, fchmod 0o755, atomic rename. - Exec —
execv(cached_binary, original_argv).
Cache disciplina (D5)¶
Cache hardening je namjerna replika ci/tool_installer.py
_cache_hit — nije shortcut. Redoslijed provjera (prije sha256):
- symlink? → reject (re-download)
- regular file? (fstat na OTVORENOM fd, ne na path)
- owner exec bit (
mode & 0o100) uid == euid(isti korisnik)- group/world writable (
mode & 0o22 != 0) → reject - 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):
BITBUCKET_API_TOKEN+BITBUCKET_USERNAME→ Basic auth- else
BITBUCKET_WORKSPACE_TOKEN→x-token-auth - 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¶
launcher-rs/CLAUDE.md— full operativna dokumentacija02-systems/11-vendor-api-contract.md— HTTP-call invariant: 5 VendorAuthError izuzetaka + bozana carve-outci/cargo/CLAUDE.md— gate.py + publish.py.planning/launcher-rs-distribution/plan.md— baby-step breakdown