File size: 4,679 Bytes
54a6728
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94e4c89
54a6728
 
213c212
 
 
 
 
 
 
 
 
 
 
 
 
 
54a6728
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6093073
 
 
 
 
 
 
 
 
 
54a6728
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a8fb550
2b69294
54a6728
 
2b69294
54a6728
 
 
 
 
 
 
6093073
54a6728
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/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