# 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 /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: ```toml [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//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.