#!/bin/bash # basecamp — launch AI Agent + Ollama in a container # Auto-discovers and connects to any local AI services on the network. # # SAFETY: this script only ever talks to Docker. All basecamp state lives in # named volumes (basecamp-hermes, basecamp-ollama). It never writes to your # host's ~/.hermes or any other host config. # # Usage: # basecamp # interactive Hermes shell with connect screen # basecamp "ask this" # one-shot prompt to Hermes # basecamp tavern status # check discovered services # basecamp tavern chat "hello" # chat with discovered inference # basecamp tavern search "AI" # search via discovered SearXNG # basecamp tavern mcp # list discovered MCP tools # basecamp tavern models # list discovered models # basecamp ollama list # run ollama command inside # basecamp bash # drop into shell inside container # basecamp rediscover # re-scan for services IMAGE="${BASECAMP_IMAGE:-jpanasuk/basecamp:latest}" CONTAINER="${BASECAMP_CONTAINER:-basecamp}" # Always pull the latest image so the user gets the newest build (new # features, recipes, fixes) every boot. Falls back to the cached image # when offline so the box still works without network. if [ -z "${BASECAMP_NO_PULL:-}" ]; then echo "[basecamp] Checking for the latest image..." if docker pull "$IMAGE" 2>/tmp/basecamp-pull-err; then echo "[basecamp] Using the latest image: $IMAGE" else echo "[basecamp] Pull failed (offline?) — using cached image: $IMAGE" head -2 /tmp/basecamp-pull-err 2>/dev/null rm -f /tmp/basecamp-pull-err fi fi # Interactive flags: only use -it when stdin is a TTY (cron/CI-safe) TTY_FLAG="" if [ -t 0 ]; then TTY_FLAG="-it" fi # GPU: only request GPUs when the host actually has the NVIDIA runtime GPU_FLAG="" if command -v nvidia-smi > /dev/null 2>&1; then GPU_FLAG="--gpus all" fi # Find any Docker network that has AI services NET_FLAG="" for net in $(docker network ls --format '{{.Name}}' 2>/dev/null); do if echo "$net" | grep -qi "ai\|tavern\|llm\|inference\|tabby\|ollama"; then NET_FLAG="--network $net" echo "[basecamp] Connected to network: $net" break fi done if [ -z "$NET_FLAG" ]; then echo "[basecamp] Standalone mode (no AI network found)" fi # Virtual docker root — OPT-IN (BASECAMP_DOCKER=1): mount the docker socket # so the box can run read-only docker tools (ps/logs/inspect/stats/network) # over its OWN containers. Scoped power, not host root — the MCP server # only exposes read-only commands. Leave unset for zero host access. DOCKER_FLAG="" if [ "${BASECAMP_DOCKER:-0}" = "1" ]; then DOCKER_FLAG="-v /var/run/docker.sock:/var/run/docker.sock" echo "[basecamp] Virtual docker root ENABLED (read-only docker tools)" fi # If a stopped container with our name lingers, clear it (named volumes persist) if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$" \ && ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then echo "[basecamp] Removing stale container '$CONTAINER' (volumes are kept)" docker rm -f "$CONTAINER" > /dev/null 2>&1 || true fi # If container already running, exec into it if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then if [ $# -gt 0 ]; then case "$1" in tavern) shift; docker exec $TTY_FLAG "$CONTAINER" tavern "$@";; ollama) shift; docker exec $TTY_FLAG "$CONTAINER" ollama "$@";; bash|shell) docker exec $TTY_FLAG "$CONTAINER" bash;; rediscover) docker exec $TTY_FLAG "$CONTAINER" bash -c "rm -f /root/.hermes/basecamp_config.json && python3 /opt/basecamp/discover.py serve";; wire) docker exec $TTY_FLAG "$CONTAINER" python3 /opt/basecamp/discover.py wire;; *) docker exec $TTY_FLAG "$CONTAINER" hermes -s basecamp-stack "$@";; esac else docker exec $TTY_FLAG "$CONTAINER" hermes -s basecamp-stack fi else # Run new container (state stays in named volumes; nothing touches the host) docker run $TTY_FLAG --rm \ --name "$CONTAINER" \ $GPU_FLAG \ $NET_FLAG \ $DOCKER_FLAG \ -p 11436:11434 \ -v basecamp-ollama:/root/.ollama \ -v basecamp-hermes:/root/.hermes \ -e BASECAMP_MODEL="${BASECAMP_MODEL:-llama3.1:8b}" \ -e BASECAMP_CONFIG="${BASECAMP_CONFIG:-/root/.hermes/basecamp_config.json}" \ -e BASECAMP_DISCOVERY="${BASECAMP_DISCOVERY:-/root/.hermes/discovery.json}" \ "$IMAGE" "$@" fi