diff --git a/manage.sh b/manage.sh new file mode 100755 index 0000000..1fc484b --- /dev/null +++ b/manage.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# manage.sh — Peregrine CLI wrapper +# A single entry point for all common operations. +# Usage: ./manage.sh [options] +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' +info() { echo -e "${BLUE}[peregrine]${NC} $*"; } +success() { echo -e "${GREEN}[peregrine]${NC} $*"; } +warn() { echo -e "${YELLOW}[peregrine]${NC} $*"; } +error() { echo -e "${RED}[peregrine]${NC} $*" >&2; exit 1; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +PROFILE="${PROFILE:-remote}" + +# ── Usage ──────────────────────────────────────────────────────────────────── +usage() { + echo "" + echo -e " ${BLUE}Peregrine by Circuit Forge LLC${NC}" + echo -e " ${YELLOW}\"Don't be evil, for real and forever.\"${NC}" + echo "" + echo " Usage: ./manage.sh [--profile PROFILE]" + echo "" + echo " Commands:" + echo -e " ${GREEN}setup${NC} Install Docker/Podman + NVIDIA toolkit" + echo -e " ${GREEN}start${NC} Start Peregrine (preflight → up)" + echo -e " ${GREEN}stop${NC} Stop all services" + echo -e " ${GREEN}restart${NC} Restart all services" + echo -e " ${GREEN}status${NC} Show running containers" + echo -e " ${GREEN}logs [service]${NC} Tail logs (default: app)" + echo -e " ${GREEN}update${NC} Pull latest images + rebuild app" + echo -e " ${GREEN}preflight${NC} Check ports + resources; write .env" + echo -e " ${GREEN}test${NC} Run test suite" + echo -e " ${GREEN}prepare-training${NC} Extract cover letters → training JSONL" + echo -e " ${GREEN}finetune${NC} Run LoRA fine-tune (needs GPU profile)" + echo -e " ${GREEN}clean${NC} Remove containers, images, volumes (DESTRUCTIVE)" + echo -e " ${GREEN}open${NC} Open the web UI in your browser" + echo "" + echo " Profiles (set via --profile or PROFILE env var):" + echo " remote API-only, no local inference (default)" + echo " cpu Local Ollama inference on CPU" + echo " single-gpu Ollama + Vision on GPU 0" + echo " dual-gpu Ollama + Vision + vLLM on GPU 0+1" + echo "" + echo " Examples:" + echo " ./manage.sh start" + echo " ./manage.sh start --profile cpu" + echo " ./manage.sh logs ollama" + echo " PROFILE=single-gpu ./manage.sh restart" + echo "" +} + +# ── Parse args ─────────────────────────────────────────────────────────────── +CMD="${1:-help}" +shift || true + +while [[ $# -gt 0 ]]; do + case "$1" in + --profile|-p) PROFILE="$2"; shift 2 ;; + --help|-h) usage; exit 0 ;; + *) break ;; + esac +done + +SERVICE="${1:-app}" # used by `logs` command + +# ── Commands ───────────────────────────────────────────────────────────────── +case "$CMD" in + + setup) + info "Running dependency installer..." + bash setup.sh + ;; + + preflight) + info "Running preflight checks (PROFILE=${PROFILE})..." + make preflight PROFILE="$PROFILE" + ;; + + start) + info "Starting Peregrine (PROFILE=${PROFILE})..." + make start PROFILE="$PROFILE" + PORT="$(python3 scripts/preflight.py --service streamlit 2>/dev/null || echo 8501)" + success "Peregrine is up → http://localhost:${PORT}" + ;; + + stop) + info "Stopping all services..." + make stop + success "Stopped." + ;; + + restart) + info "Restarting (PROFILE=${PROFILE})..." + make restart PROFILE="$PROFILE" + PORT="$(python3 scripts/preflight.py --service streamlit 2>/dev/null || echo 8501)" + success "Peregrine restarted → http://localhost:${PORT}" + ;; + + status) + # Auto-detect compose engine same way Makefile does + COMPOSE="$(command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1 \ + && echo "docker compose" \ + || (command -v podman >/dev/null 2>&1 && echo "podman compose" || echo "podman-compose"))" + $COMPOSE ps + ;; + + logs) + COMPOSE="$(command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1 \ + && echo "docker compose" \ + || (command -v podman >/dev/null 2>&1 && echo "podman compose" || echo "podman-compose"))" + info "Tailing logs for: ${SERVICE}" + $COMPOSE logs -f "$SERVICE" + ;; + + update) + info "Pulling latest images and rebuilding app..." + COMPOSE="$(command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1 \ + && echo "docker compose" \ + || (command -v podman >/dev/null 2>&1 && echo "podman compose" || echo "podman-compose"))" + $COMPOSE pull searxng ollama 2>/dev/null || true + $COMPOSE build app + success "Update complete. Run './manage.sh restart' to apply." + ;; + + test) + info "Running test suite..." + make test + ;; + + prepare-training) + info "Extracting training data from cover letter corpus..." + make prepare-training + ;; + + finetune) + info "Starting fine-tune (PROFILE=${PROFILE})..." + make finetune PROFILE="$PROFILE" + ;; + + clean) + warn "This will remove ALL Peregrine containers, images, and volumes." + read -rp "Type 'yes' to confirm: " confirm + [[ "$confirm" == "yes" ]] || { info "Cancelled."; exit 0; } + make clean + ;; + + open) + PORT="$(python3 scripts/preflight.py --service streamlit 2>/dev/null || echo 8501)" + URL="http://localhost:${PORT}" + info "Opening ${URL}" + if command -v xdg-open &>/dev/null; then + xdg-open "$URL" + elif command -v open &>/dev/null; then + open "$URL" + else + echo "$URL" + fi + ;; + + help|--help|-h) + usage + ;; + + *) + error "Unknown command: ${CMD}. Run './manage.sh help' for usage." + ;; + +esac