cloudronin's picture
push build context (uofa source + packs + space app)
a28ec65 verified
Raw
History Blame Contribute Delete
10.7 kB
"""uofa setup β€” install / verify the LLM extract runtime (REQ-DIST-003+)."""
from __future__ import annotations
import argparse
import sys
import time
from pathlib import Path
from uofa_cli import setup_bundle, setup_install, setup_state, setup_uninstall, setup_verify
from uofa_cli.output import error, info, step_header
HELP = "install or verify the LLM extract runtime (Ollama + qwen3.5:4b)"
_DEFAULT_MODEL = "qwen3.5:4b"
def add_arguments(parser: argparse.ArgumentParser) -> None:
# Top-level options that select install variants without requiring a
# subcommand keyword. `--bundle <path>` and `--create-bundle <path>`
# mirror the spec wording in REQ-DIST-004.
parser.add_argument(
"--bundle", type=Path, default=None,
help="install from an offline bundle (REQ-DIST-004 air-gapped path)",
)
parser.add_argument(
"--create-bundle", type=Path, default=None, dest="create_bundle",
help="package the current install into a tar.gz for an offline machine",
)
parser.add_argument(
"--model", default=_DEFAULT_MODEL,
help=f"model tag to pull (default: {_DEFAULT_MODEL})",
)
parser.add_argument(
"--port", type=int, default=11434,
help="port for the managed Ollama daemon (default: 11434)",
)
parser.add_argument(
"--no-byo", action="store_true",
help="skip BYO Ollama detection; install a UofA-managed copy",
)
parser.add_argument(
"--yes", "-y", action="store_true",
help="skip confirmation prompts (downloads, uninstall)",
)
parser.add_argument(
"--no-verify", action="store_true",
help="skip the post-install verify step",
)
sub = parser.add_subparsers(dest="setup_cmd", required=False)
sub.add_parser(
"verify",
help="run a known extraction against a fixture and assert F1 >= 0.95",
)
sub.add_parser(
"uninstall",
help="remove the UofA-managed Ollama runtime + model (REQ-DIST-007)",
)
def run(args: argparse.Namespace) -> int:
cmd = getattr(args, "setup_cmd", None)
if cmd == "verify":
return _run_verify(args)
if cmd == "uninstall":
return _run_uninstall(args)
# No subcommand: pick install path based on flags.
if getattr(args, "create_bundle", None) is not None:
return _run_create_bundle(args)
if getattr(args, "bundle", None) is not None:
return _run_install_from_bundle(args)
return _run_install(args)
# ── install ────────────────────────────────────────────────────
def _run_install(args: argparse.Namespace) -> int:
step_header("uofa setup β€” install LLM extract runtime")
prefer_byo = not args.no_byo
byo = setup_state.detect_byo_ollama() if prefer_byo else None
_print_install_summary(byo, args.model, args.port)
if not args.yes and not _confirm("Proceed with install? [y/N] "):
info("Aborted.")
return 1
started = time.monotonic()
progress_state = {"last_label": ""}
def on_status(msg: str) -> None:
info(msg)
def on_progress(done: int, total: int) -> None:
if not total:
return
pct = (done / total) * 100
mb_done = done / (1024 * 1024)
mb_total = total / (1024 * 1024)
label = f" {pct:5.1f}% {mb_done:7.1f} / {mb_total:7.1f} MB"
if label != progress_state["last_label"]:
print(label, file=sys.stderr)
progress_state["last_label"] = label
try:
cfg = setup_install.install(
prefer_byo=prefer_byo,
model_tag=args.model,
port=args.port,
on_status=on_status,
on_progress=on_progress,
)
except Exception as e:
error(f"Install failed: {e}")
return 1
elapsed = time.monotonic() - started
_print_install_success(cfg, elapsed)
if args.no_verify:
return 0
info("")
info("Running verify to confirm install...")
verify_result = setup_verify.verify(cfg, on_status=on_status)
if not verify_result.ok:
error(f"Verify failed: {verify_result.diagnostic}")
info("Try `uofa setup verify` again, or `uofa setup` to repair.")
return 1
info(f"βœ“ {verify_result.diagnostic} (in {verify_result.elapsed_seconds:.1f}s)")
return 0
def _run_verify(args: argparse.Namespace) -> int:
step_header("uofa setup verify β€” end-to-end smoke test")
cfg = setup_state.load_config()
if cfg is None:
error("No install detected. Run `uofa setup` first.")
return 1
info(f"Using {cfg.mode} Ollama at {cfg.ollama_binary} (port {cfg.ollama_port}).")
result = setup_verify.verify(cfg, on_status=info)
if result.ok:
info(f"βœ“ {result.diagnostic} (in {result.elapsed_seconds:.1f}s)")
info(f" Try next: uofa extract <your-pdf>")
return 0
error(f"βœ— {result.diagnostic} (after {result.elapsed_seconds:.1f}s)")
return 1
# ── create-bundle / install-from-bundle (REQ-DIST-004) ─────────
def _run_create_bundle(args: argparse.Namespace) -> int:
step_header("uofa setup --create-bundle β€” package install for offline transport")
cfg = setup_state.load_config()
if cfg is None:
error("No install to bundle. Run `uofa setup` first.")
return 1
output = args.create_bundle
if output.is_dir():
output = output / setup_bundle.default_bundle_filename()
info(f" Bundle target: {output}")
info(f" Source binary: {cfg.ollama_binary}")
info(f" Source models: {cfg.ollama_models_dir or '~/.ollama/models'}")
info(f" Model: {cfg.model_tag}")
if not args.yes and not _confirm("Proceed with bundle creation? [y/N] "):
info("Aborted.")
return 1
try:
result_path = setup_bundle.create_bundle(
output, cfg=cfg, model_tag=cfg.model_tag,
)
except FileNotFoundError as e:
error(str(e))
return 1
size_mb = result_path.stat().st_size / (1024 * 1024)
info(f"βœ“ Wrote {result_path} ({size_mb:.1f} MB)")
info("Transfer this file to the air-gapped machine and run:")
info(f" uofa setup --bundle {result_path.name}")
return 0
def _run_install_from_bundle(args: argparse.Namespace) -> int:
step_header("uofa setup --bundle β€” install from offline bundle")
bundle_path = args.bundle
if not bundle_path.is_file():
error(f"Bundle not found: {bundle_path}")
return 1
info(f"Reading {bundle_path}")
try:
cfg = setup_bundle.consume_bundle(bundle_path, on_status=info)
except setup_bundle.PlatformMismatchError as e:
error(str(e))
return 1
except (FileNotFoundError, ValueError) as e:
error(f"Bundle install failed: {e}")
return 1
info("")
info("Running verify against the unpacked install...")
result = setup_verify.verify(cfg, on_status=info)
if result.ok:
info(f"βœ“ {result.diagnostic} (in {result.elapsed_seconds:.1f}s)")
info(f" Try next: uofa extract <your-pdf>")
return 0
error(f"βœ— Verify failed: {result.diagnostic}")
return 1
# ── uninstall (REQ-DIST-007) ───────────────────────────────────
def _run_uninstall(args: argparse.Namespace) -> int:
step_header("uofa setup uninstall β€” remove managed runtime + model")
cfg = setup_state.load_config()
plan = setup_uninstall.plan_uninstall(cfg)
if not plan.targets:
info("Nothing to remove.")
return 0
info("Will remove:")
for target in plan.targets:
info(f" {target}")
info(f"Disk space to free: {plan.mb_to_free:.1f} MB")
if cfg is not None and cfg.mode == "byo":
info(f"Will NOT touch BYO Ollama at {cfg.ollama_binary}")
if not args.yes and not _confirm("Proceed with uninstall? [y/N] "):
info("Aborted.")
return 1
result = setup_uninstall.uninstall(cfg, on_status=info)
info(f"βœ“ Removed {len(result.removed)} target(s); freed {result.bytes_freed / (1024*1024):.1f} MB")
if result.skipped:
for s in result.skipped:
info(f" (skipped {s})")
return 0
# ── helpers ────────────────────────────────────────────────────
def _print_install_summary(byo: Path | None, model: str, port: int) -> None:
info("")
if byo is not None:
info(f"Detected existing Ollama: {byo}")
info(f" β†’ will register and pull {model} into its model store")
info(" β†’ no UofA-managed binary will be downloaded")
info(" β†’ estimated additional disk: ~3 GB (model only)")
else:
platform_tag = setup_install.detect_wheel_platform_tag()
try:
entry = setup_install.resolve_platform_entry(platform_tag)
url = entry.url
ver = entry.version
info(f"Will install Ollama {ver} for {platform_tag}")
info(f" Source: {url}")
except Exception:
info(f"Will install Ollama for {platform_tag} (manifest lookup failed)")
info(f"Model: {model}")
info(f"Daemon port: {port}")
info(f"Install location: {setup_state.uofa_data_dir()}")
info("Estimated disk usage: ~5 GB total (Ollama runtime + model)")
def _confirm(prompt: str) -> bool:
try:
answer = input(prompt).strip().lower()
except (EOFError, KeyboardInterrupt):
return False
return answer in ("y", "yes")
def _print_install_success(cfg: setup_state.SetupConfig, elapsed: float) -> None:
info("")
info("══════════════════════════════════════════════════════")
info("βœ“ uofa setup complete")
info(f" Mode: {cfg.mode}")
info(f" Ollama: {cfg.ollama_binary}")
info(f" Model: {cfg.model_tag}")
info(f" Port: {cfg.ollama_port}")
info(f" Config: {setup_state.config_path()}")
info(f" Elapsed: {elapsed:.1f}s")
info("")
info("Try next:")
info(" uofa extract <your-pdf-or-directory>")
info("══════════════════════════════════════════════════════")