Files
bot-bottle/docs/prds/0078-install-script.md
T
didericis-codex 9c06702b32
prd-number / assign-numbers (push) Failing after 25s
lint / lint (push) Successful in 4m7s
Update Quality Badges / update-badges (push) Failing after 13m37s
test / coverage (push) Has been skipped
test / integration-docker (push) Failing after 13m51s
test / integration-firecracker (push) Failing after 13m48s
test / integration-macos (push) Failing after 13m54s
test / unit (push) Failing after 13m53s
test / publish-infra (push) Has been skipped
docs(prd): number merged placeholder documents
2026-07-26 06:06:04 +00:00

7.6 KiB

PRD 0078: Quick install script

  • Status: Active
  • Author: claude
  • Created: 2026-07-25
  • Issue: #197

Summary

Add a proper Python package distribution (pyproject.toml with a bot-bottle entry point) plus a thin install.sh bootstrapper, so users can install bot-bottle with a single command instead of cloning the repo and invoking cli.py directly. A new bot-bottle doctor subcommand verifies host prerequisites after install.

Problem

There is currently no install path for new users. The only way to run bot-bottle is to clone the repo and invoke ./cli.py. This blocks any public demo: readers want curl | sh or pipx install, not a manual clone-and-configure flow. There is also no single command that tells a user whether their host is actually ready to run a bottle.

Goals / Success Criteria

  • curl -fsSL <raw-url>/install.sh | sh leaves a working bot-bottle command on PATH.
  • Python-native users can install with pipx install bot-bottle or uv tool install bot-bottle (once published) — or from a local checkout today.
  • install.sh validates prerequisites (Python ≥ 3.11), creates the ~/.bot-bottle/ config tree, installs the package, and runs bot-bottle doctor. It never installs Docker or a VM backend silently and never uses sudo.
  • install.sh is idempotent — safe to re-run.
  • bot-bottle doctor reports Python version, backend readiness, and config-dir presence, exiting non-zero when a hard prerequisite is unmet.
  • The package keeps zero runtime pip dependencies (stdlib-only, matching the existing constraint in AGENTS.md).

Non-goals

  • Bundling a Python runtime or producing a standalone binary.
  • Automatic Docker / VM-backend installation.
  • Plugin-architecture changes (issue #197 floats a containerized-plugin direction; that's a separate feature).
  • Publishing to a package index in this PR — the package structure is the deliverable; publishing is a follow-up step.

Design

Package structure (pyproject.toml)

Fill out the previously-stub pyproject.toml with project metadata, a console-script entry point, and package-data for the non-Python assets the runtime reads from inside the package:

[project]
name = "bot-bottle"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []

[project.scripts]
bot-bottle = "bot_bottle.cli:main"

bot_bottle.cli:main already exists (the cli.py shim calls it), so no refactor of the entry point is needed. package-data ships the non-Python assets that live inside the package (egress_entrypoint.sh, the contrib Dockerfiles, the firecracker netpool defaults, the macos-container init script).

Self-contained wheel (build resources)

The gateway / infra / orchestrator images are built from a Docker (or Apple container) build context that must contain the bot_bottle package, pyproject.toml, and the root-level Dockerfiles as siblings. Several modules used to locate that context by walking __file__'s parents to the repo root (_REPO_ROOT = Path(__file__)…parents[N]) and reading Dockerfile.gateway, nix/firecracker-netpool.nix, and scripts/firecracker-netpool.sh from it. In an installed wheel the package lives in site-packages with no repo root above it, so those reads fail — doctor passes but start / backend setup breaks.

Fix: a single resolver, bot_bottle/resources.py.

  • build_root() returns a directory shaped like a repo root (has bot_bottle/, pyproject.toml, the Dockerfiles, nix/, scripts/). In a checkout it's the repo root itself — unchanged behavior. From an installed wheel it stages a copy under the app-data dir, keyed by a content digest of the installed package + bundled resources (not the distribution version): the installer defaults to a git branch and pipx install --force while version stays 0.1.0, so a version key would reuse a previous commit's tree — the digest key re-stages instead. Staging is concurrency-safe: a file lock serializes it, each writer builds into a private temp dir, and the finished tree is published with an atomic rename (never populating a shared path another process might read).
  • The root-level resources are shipped inside the wheel under bot_bottle/_resources/ by a setup.py build_py step (kept in sync with resources.BUNDLED_RESOURCES); MANIFEST.in includes them in the sdist.
  • Every former _REPO_ROOT / _REPO_DIR call site now derives from resources: the docker/macos agent-image launch, each backend's orchestrator / gateway / infra service, firecracker infra_vm / infra_artifact / setup, and the shared gateway build context. So checkout and wheel installs share one downstream path.

Verification: test_resources exercises both layouts — including the staged wheel context, a re-stage when package content changes at the same version, and a rebuild of a partial (crashed) stage. test_wheel_install builds the wheel, installs it into an isolated venv, and asserts bot-bottle doctor runs and build_root() produces a valid context; build is in requirements-dev.txt so it runs in CI, and a build/install failure fails the test (it does not skip). Running start end-to-end still needs a Docker/KVM host (CI), not a source checkout.

install.sh

A POSIX sh bootstrapper that:

  1. Checks python3 is present and ≥ 3.11; exits with a clear message otherwise.
  2. Checks git when installing a git+ spec, and — when falling back to pip — that pip is usable and the interpreter isn't externally managed (PEP 668), pointing at pipx otherwise.
  3. Creates ~/.bot-bottle/{agents,bottles,contrib}.
  4. Installs via pipx if available, else python3 -m pip install --user. The spec defaults to the git URL and is overridable via BOT_BOTTLE_INSTALL_SPEC (used by tests / local installs).
  5. Locates the bot-bottle entry point: PATH first, else the interpreter's own user-scheme scripts dir resolved via sysconfig (~/.local/bin on Linux, ~/Library/Python/<X.Y>/bin on a python.org macOS interpreter — not hardcoded).
  6. Runs bot-bottle doctor and reports the result.

It is idempotent and never calls sudo.

bot-bottle doctor

A new store-free subcommand (no DB migration required) that checks and reports:

  • python — interpreter version (hard requirement: ≥ 3.11).
  • backend — at least one backend ready on this host (macos-container / firecracker / docker), via is_backend_ready() — a full backend status() probe (daemon reachable, network pool present, KVM usable), not a PATH-only check: a stopped daemon or half-configured backend must not report ok when start can't work. Each not-ready backend prints its own diagnostics. Hard requirement.
  • config — whether ~/.bot-bottle/ exists (advisory only; start provisions on first run).

Exits 0 when both hard requirements pass, non-zero otherwise.

Testing strategy

  • Unit test bot-bottle doctor success/failure paths with backend readiness (is_backend_ready) and Python version mocked, including the available-but-not-ready → fail case.
  • Unit test that pyproject.toml parses, declares the entry point and an empty dependencies list, and that every package-data glob resolves to a file that exists on disk (guards against drift).
  • Unit test that install.sh is executable, POSIX-ish (set -eu), never calls sudo, and runs doctor after install.

Open questions

  • Should version be derived from a git tag at build time (e.g. hatch-vcs) or kept static? Static (0.1.0) is simpler for now.
  • Publishing target (PyPI vs. a self-hosted index) is deferred.