Skip to content

feat(installer): the curl|bash no-warning path, auditable line by line (#479) - #511

Merged
eaitbrahim merged 2 commits into
mainfrom
feat-479-installer
Aug 23, 2026
Merged

feat(installer): the curl|bash no-warning path, auditable line by line (#479)#511
eaitbrahim merged 2 commits into
mainfrom
feat-479-installer

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

Closes #479

What

curl | bash installer that bootstraps the release wheels — the no-warning path. scripts/install.sh (~215 lines, every step says what and why before it runs), 16 property-pinning tests over its text, a two-OS CI smoke that runs it for real, and pointers from docs/desktop-install.md (which now leads its no-warning section with the installer) and the README quickstart.

Explicitly not downloading an app binary: the OS-warning path stays the .dmg/.zip walkthrough; signing stays #438.

curl -fsSL https://raw.githubusercontent.com/CodeGateSoftware/keel/main/scripts/install.sh | bash

Design decisions

  • Venv location: ~/.keel/.venv, config beside it. One per-user folder you can look inside — keel's deployment model (keel_core/paths.py). The venv is named .venv deliberately: that is the exact layout keel update's deployment_layout_refusal recognises, so the updater serves what the installer built. Verified locally: on a fresh install, keel update from ~/.keel answers already at the latest release (0.11.0). No sudo, nothing written outside $HOME.
  • uv if it works, pip otherwise. Discovery probes uv --version, not just command -v — a broken shim (asdf + changed HOME failed exactly this way on my machine) is honestly treated as absent and the pip path (python -m venv + ensurepip bootstrap + pip install --no-input) takes over. Both branches install by exact wheel path with --find-links, mirroring keel update's _uv_install form — never by name from an index (PyPI's keel is an unrelated project).
  • Checksums, honestly. The release publishes SHA256SUMS for the desktop artifacts only; there are no published wheel checksums, and the script says so in its output rather than pretending to verify — it records each wheel's sha256 as computed locally instead, leaving an auditable record of what was installed.
  • Exactly the five production wheels (keel_core, keel_broker_api, keel_broker_coinbase, keel_broker_alpaca, keel_trader), selected by exact <prefix>-<version>- name — the test pins the script's allowlist equal to PRODUCTION_WHEEL_PREFIXES from keel/commands/update.py, so the two selectors cannot drift. The names of the excluded venue wheels never appear in the script at all.
  • keel versions before success. The verify runs from ~/.keel (so state resolves against the deployment) and its failure fails the script; the success banner is unreachable otherwise. The next-steps output composes with D6 — Decide the update path for a packaged install #439 option A: re-run the installer, or keel update for this venv layout; desktop bundles update by re-downloading.
  • Never clobbers the user. An existing config.yaml is kept and said so; keel*.db is acknowledged and untouched; re-running upgrades the venv in place.

Real local run (this Mac, isolated HOME)

Fresh install via the pip path (this machine's uv is an asdf shim that breaks under a changed HOME, so the fallback earned its keep), then re-run for idempotency:

==> step 1/7: checking the platform
==>   ok: Darwin
==> step 2/7: finding Python >= 3.11
==>   ok: /Users/elmehdia/.local/bin/python3.13 (3.13.14)
==> step 3/7: resolving the latest release from the GitHub API
  GET https://api.github.com/repos/CodeGateSoftware/keel/releases/latest
==>   latest release: v0.11.0
==> step 4/7: downloading the five production wheels and config.yaml to .../keel-install.ZJwv6C
  GET https://github.com/CodeGateSoftware/keel/releases/download/v0.11.0/keel_core-0.11.0-py3-none-any.whl
  GET https://github.com/CodeGateSoftware/keel/releases/download/v0.11.0/keel_broker_api-0.11.0-py3-none-any.whl
  GET https://github.com/CodeGateSoftware/keel/releases/download/v0.11.0/keel_broker_coinbase-0.11.0-py3-none-any.whl
  GET https://github.com/CodeGateSoftware/keel/releases/download/v0.11.0/keel_broker_alpaca-0.11.0-py3-none-any.whl
  GET https://github.com/CodeGateSoftware/keel/releases/download/v0.11.0/keel_trader-0.11.0-py3-none-any.whl
  GET https://github.com/CodeGateSoftware/keel/releases/download/v0.11.0/config.yaml
==> step 5/7: recording the sha256 of each wheel as downloaded (no published wheel checksums exist to compare against)
  48c95e174eb004d17fe11f20361b652338b7ccba411f7f912cbe83269fc2e3e7  keel_core-0.11.0-py3-none-any.whl
  ... (4 more)
==> step 6/7: installing into /tmp/keel479/home/.keel (per-user; no elevated commands)
==>   uv not found: creating the venv with '... -m venv'
==>   running: ... -m pip install --no-input --find-links <tmp> <the five wheel paths>
  ... Successfully installed ... keel-broker-alpaca-0.11.0 keel-broker-api-0.11.0
  keel-broker-coinbase-0.11.0 keel-core-0.11.0 keel-trader-0.11.0 ...
==>   installing the release's default config.yaml beside the venv (the paper profile)
==> step 7/7: verifying the install with 'keel versions' (run from /tmp/keel479/home/.keel)
keel 0.11.0+f5f471f9118b [release]

keel-broker-alpaca    0.11.0
keel-broker-api       0.11.0
keel-broker-coinbase  0.11.0
keel-core             0.11.0
keel-trader           0.11.0

ok: 5 keel distributions, all at 0.11.0.
==> installed keel v0.11.0 (verified)

Next steps: ... (run it / paper profile + CDP key pointer / https://keeltrading.com /
updates: re-run installer or `keel update` / this folder)
EXIT: 0

Also exercised: the uv path with a working uv on PATH (exit 0; uv's output shows each keel wheel from file:///...whl — by path, proven), and a re-run over the existing install:

==>   .../.keel/.venv already exists: reusing it (re-running this script upgrades in place)
==>   .../config.yaml already exists: keeping it (not overwritten)
==>   existing database keel.db: not touched
==>   installed keel v0.11.0 (verified)      # config diff empty, sentinel db intact

CI

install-smoke.yml: workflow_dispatch + pull_request filtered to scripts/install.sh only; matrix ubuntu-latest × macos-latest; shellcheck on the Linux leg (ubuntu ships it, macOS doesn't); runs the script for real into $RUNNER_TEMP HOME and asserts ~/.keel/.venv/bin/keel versions exits 0.

Gates

  • uv run pytest -q — 4472 passed, 3 skipped (16 new)
  • uv run ruff check keel tests — clean
  • uv run mypy — clean (351 files)
  • shellcheck scripts/install.sh — clean locally

Notes / deviations

#479)

A terminal installer that bootstraps the release wheels into a per-user
venv at ~/.keel/.venv -- nothing is downloaded as an application, so no
OS trust dialog is ever involved (that path stays the .dmg/.zip with
docs/desktop-install.md; signing stays #438).

Auditable by construction: set -euo pipefail, every step prints what it
is about to do before running, every download URL is echoed, no sudo, no
credentials, no nested pipe-to-shell. Python >= 3.11 is checked via the
interpreter's own version_info and the failure names the floor. The five
production wheels are selected by exact allowlisted name (equal to
PRODUCTION_WHEEL_PREFIXES in keel/commands/update.py) and installed BY
PATH -- never by name from an index (the PyPI 'keel' is an unrelated
project). Wheel checksums are not published by the release; the script
says so honestly and records each wheel's sha256 locally instead.
config.yaml and keel*.db under ~/.keel are never overwritten. The venv
is named .venv deliberately so 'keel update' serves what this script
built (verified: it reports 'already at the latest release' on a fresh
install), and the printed next steps compose with #439 option A.

tests/test_install_script.py pins these properties over the script text,
the house pattern from tests/test_desktop_packaging.py.
The acceptance evidence for #479's 'works on macOS and Linux': run
scripts/install.sh FOR REAL on both runners (public repo, network, the
actual release wheels) into an isolated HOME, then assert the installed
venv's own 'keel versions' exits 0. shellcheck runs on the Linux leg
only (ubuntu images ship it, macOS images do not; the bytes are the
same on both). pull_request is filtered to the script's path so an
unchanged installer costs no runners; the text-pinning suite in
tests/test_install_script.py runs in CI on every PR regardless.
@eaitbrahim eaitbrahim self-assigned this Aug 22, 2026
@eaitbrahim
eaitbrahim merged commit 10882cf into main Aug 23, 2026
7 checks passed
@eaitbrahim
eaitbrahim deleted the feat-479-installer branch August 23, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A curl | bash installer that bootstraps the wheels — the no-warning path, auditable line by line

1 participant