Files
bot-bottle/docs/prds/prd-new-install-script.md
T
didericis-claude 1a4b390e8a fix: make installed wheel self-contained + harden install.sh prereqs
Addresses the review on PR #481.

Self-contained wheel (review point 1): the gateway/infra/orchestrator
images build from a context that must hold bot_bottle/, pyproject.toml,
and the root-level Dockerfiles. Modules previously located these by
walking __file__ to the repo root, so an installed wheel (package in
site-packages, no repo root) passed `doctor` but failed `start`.

- Add bot_bottle/resources.py: build_root() returns the repo root in a
  checkout (unchanged) or a staged copy from the wheel's bundled
  _resources/ otherwise; dockerfile()/nix_netpool_module()/
  netpool_script() derive from it.
- setup.py bundles the root Dockerfiles, nix module, netpool script, and
  pyproject.toml into bot_bottle/_resources/ at build; MANIFEST.in ships
  them in the sdist.
- Route every _REPO_ROOT/_REPO_DIR call site (docker/macos launch, macos
  infra, firecracker infra_vm/infra_artifact/setup, orchestrator
  lifecycle/gateway) through resources. Checkout behavior is unchanged.

install.sh prerequisites (review point 2): check for git when installing
a git+ spec, and — before the pip fallback — that pip is usable and the
interpreter isn't externally managed (PEP 668), pointing at pipx.

Tests: test_resources covers checkout + staged-wheel layouts;
test_wheel_install builds the wheel, installs it into an isolated venv,
and asserts `doctor` runs and build_root() yields a valid context.
Running `start` end-to-end still needs a Docker/KVM host (CI).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 22:34:25 -04:00

167 lines
7.2 KiB
Markdown

# PRD prd-new: Quick install script
- **Status:** Draft
- **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 availability, 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).
4. Locates the `bot-bottle` entry point (PATH or `~/.local/bin`).
5. 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 available on this host
(macos-container / firecracker / docker), reusing
`is_backend_available()` rather than hardcoding Docker, since the
default backend is now a VM backend. 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
availability and Python version mocked.
- 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.