-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathstart.sh
More file actions
executable file
·318 lines (293 loc) · 11.6 KB
/
Copy pathstart.sh
File metadata and controls
executable file
·318 lines (293 loc) · 11.6 KB
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#!/bin/bash
# Use PYTHON_PATH environment variable if set, otherwise default to python3
PYTHON=${PYTHON_PATH:-python3}
# ---------------------------------------------------------------------------
# Pretty output helpers (colors disabled when not a TTY or NO_COLOR is set)
# ---------------------------------------------------------------------------
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
C_RESET=$'\033[0m'
C_BOLD=$'\033[1m'
C_DIM=$'\033[2m'
C_RED=$'\033[31m'
C_GREEN=$'\033[32m'
C_YELLOW=$'\033[33m'
C_BLUE=$'\033[34m'
C_CYAN=$'\033[36m'
else
C_RESET='' C_BOLD='' C_DIM='' C_RED='' C_GREEN='' C_YELLOW='' C_BLUE='' C_CYAN=''
fi
section() { printf '\n%s%s=== %s ===%s\n' "$C_BOLD" "$C_BLUE" "$1" "$C_RESET"; }
step() { printf '%s▸ %s%s\n' "$C_CYAN" "$1" "$C_RESET"; }
info() { printf ' %s\n' "$1"; }
detail() { printf ' %s%s%s\n' "$C_DIM" "$1" "$C_RESET"; }
ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$1"; }
warn() { printf ' %s⚠️%s %s\n' "$C_YELLOW" "$C_RESET" "$1"; }
fail() { printf ' %s✗%s %s\n' "$C_RED" "$C_RESET" "$1"; }
prompt() { printf '%s? %s%s ' "$C_CYAN" "$1" "$C_RESET"; }
# Detect no-argument mode: use defaults and skip all interactive prompts
if [ $# -eq 0 ]; then
NO_INPUT=true
else
NO_INPUT=false
fi
# Function to check if Python is available
check_python() {
if ! command -v "$PYTHON" &> /dev/null; then
fail "Python not found at '$PYTHON'"
info "Please set the PYTHON_PATH environment variable to your Python executable."
info "Example: export PYTHON_PATH=/usr/bin/python3.11"
exit 1
else
ok "Python found: $($PYTHON --version) at $(which $PYTHON)"
fi
}
# Function to check if pip is installed
check_pip() {
if ! $PYTHON -m pip --version &> /dev/null; then
warn "pip is not installed for $PYTHON."
if [ "$NO_INPUT" = true ]; then
step "Installing pip..."
$PYTHON -m ensurepip --upgrade
if ! $PYTHON -m pip --version &> /dev/null; then
fail "pip installation failed."
exit 1
fi
ok "pip installed successfully!"
else
read -p "$(prompt "Would you like to install pip? (y/n):")" install_pip
if [[ "$install_pip" =~ ^[Yy]$ ]]; then
step "Installing pip..."
$PYTHON -m ensurepip --upgrade
if ! $PYTHON -m pip --version &> /dev/null; then
fail "pip installation failed."
exit 1
fi
ok "pip installed successfully!"
else
fail "pip is required to install dependencies."
exit 1
fi
fi
else
ok "pip found: $($PYTHON -m pip --version)"
fi
}
# Function to install requirements
install_requirements() {
if [ -f "requirements.txt" ]; then
info "Found requirements.txt"
if [ "$NO_INPUT" = true ]; then
step "Installing dependencies..."
$PYTHON -m pip install -r requirements.txt
if [ $? -eq 0 ]; then
ok "Dependencies installed successfully!"
else
warn "Some dependencies may have failed to install. Continuing anyway."
fi
else
read -p "$(prompt "Would you like to install dependencies from requirements.txt? (y/n):")" install_deps
if [[ "$install_deps" =~ ^[Yy]$ ]]; then
step "Installing dependencies..."
$PYTHON -m pip install -r requirements.txt
if [ $? -eq 0 ]; then
ok "Dependencies installed successfully!"
else
warn "Some dependencies may have failed to install."
read -p "$(prompt "Do you want to continue anyway? (y/n):")" continue_anyway
if [[ ! "$continue_anyway" =~ ^[Yy]$ ]]; then
exit 1
fi
fi
else
info "Skipping dependency installation."
fi
fi
else
warn "No requirements.txt found in current directory."
fi
}
# Check and install prerequisites
section "Checking Prerequisites"
check_python
check_pip
install_requirements
ok "Prerequisites check complete"
# Set defaults when no arguments provided
if [ "$NO_INPUT" = true ]; then
START_PORT=5000
END_PORT=5200
WORKSPACE=workspace
else
# Validate arguments
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
fail "Expected 2-3 arguments"
info "Usage: $0 <start_port> <end_port> [workspace]"
info "Example: $0 5000 5200"
info "Example: $0 5000 5200 /path/to/workspace"
exit 1
fi
# Check if arguments are valid integers
if ! [[ "$1" =~ ^[0-9]+$ ]] || ! [[ "$2" =~ ^[0-9]+$ ]]; then
fail "Arguments must be valid port numbers"
exit 1
fi
START_PORT=$1
END_PORT=$2
WORKSPACE=${3:-workspace/}
# Validate port range
if [ "$START_PORT" -gt "$END_PORT" ]; then
fail "Start port must be less than or equal to end port"
exit 1
fi
if [ "$START_PORT" -lt 1 ] || [ "$END_PORT" -gt 65535 ]; then
fail "Ports must be in range 1-65535"
exit 1
fi
fi
# Ensure the docker CLI talks to the local system daemon, which has GPU
# support. Other engines (e.g. the Docker Desktop VM, selected via the
# desktop-linux context) have no NVIDIA runtime and fail with:
# could not select device driver "nvidia" with capabilities: [[gpu]]
section "Docker Context"
if command -v docker &> /dev/null; then
CURRENT_CONTEXT=$(docker context show 2>/dev/null || echo "unknown")
if [ "$CURRENT_CONTEXT" = "default" ]; then
ok "Docker context is 'default' (system daemon)"
elif docker context use default > /dev/null 2>&1; then
ok "Switched docker context: '$CURRENT_CONTEXT' → 'default'"
else
warn "Could not switch docker context to 'default' (current: '$CURRENT_CONTEXT')"
warn "GPU-enabled services may fail with: could not select device driver \"nvidia\""
fi
else
warn "docker CLI not found - skipping context check"
fi
# Check for processes using ports
step "Checking for processes using ports $START_PORT-$END_PORT..."
PROCESSES_FOUND=false
PYTHON_PROCESSES_FOUND=false
declare -a BLOCKING_PIDS
declare -a BLOCKING_PORTS
declare -a BLOCKING_COMMANDS
declare -a PYTHON_PIDS
declare -a PYTHON_PORTS
declare -a PYTHON_COMMANDS
for ((port=$START_PORT; port<=$END_PORT; port++)); do
PID=$(lsof -ti :$port 2>/dev/null)
if [ -n "$PID" ]; then
# Get the full command path using ps
FULL_CMD=$(ps -p "$PID" -o command= 2>/dev/null)
warn "Port $port is being used by (PID: $PID):"
detail "$FULL_CMD"
PROCESSES_FOUND=true
BLOCKING_PIDS+=("$PID")
BLOCKING_PORTS+=("$port")
BLOCKING_COMMANDS+=("$FULL_CMD")
# Check if it's a Python process
if [[ "$FULL_CMD" == *python* ]]; then
PYTHON_PROCESSES_FOUND=true
PYTHON_PIDS+=("$PID")
PYTHON_PORTS+=("$port")
PYTHON_COMMANDS+=("$FULL_CMD")
fi
fi
done
if [ "$PROCESSES_FOUND" = false ]; then
ok "No processes found on ports $START_PORT-$END_PORT"
fi
# If Python processes found, ask user if they want to kill them (skipped in no-input mode)
if [ "$PYTHON_PROCESSES_FOUND" = true ]; then
echo ""
warn "The following processes are on the required ports range:"
for i in "${!PYTHON_PIDS[@]}"; do
info "Port ${PYTHON_PORTS[$i]} (PID: ${PYTHON_PIDS[$i]}):"
detail "${PYTHON_COMMANDS[$i]}"
done
echo ""
info "ℹ️ To reload a Toolomics MCP server (e.g. after modifying it), kill its Python process listed above:"
detail "if its deployment is still running, the supervisor restarts it automatically with the new code;"
detail "if not (orphaned process), re-run this script after killing it."
detail "note: kills count as crashes — many rapid kills trip the crash-loop guard and abandon the server."
if [ "$NO_INPUT" = false ]; then
read -p "$(prompt "Would you like to kill these Python processes? (y/n):")" kill_processes
if [[ "$kill_processes" =~ ^[Yy]$ ]]; then
for pid in "${PYTHON_PIDS[@]}"; do
step "Killing process $pid..."
kill -9 "$pid" 2>/dev/null
if [ $? -eq 0 ]; then
ok "Process $pid killed successfully"
else
fail "Failed to kill process $pid (may require sudo)"
fi
done
echo ""
else
info "Python processes not killed. Some ports may be unavailable."
echo ""
fi
else
info "Skipping port cleanup (no-argument mode)."
echo ""
fi
elif [ "$PROCESSES_FOUND" = true ]; then
echo ""
info "Note: Non-Python processes are using ports but will not be killed automatically."
echo ""
fi
# Calculate instance ID from workspace path (same logic as deploy.py)
# This gives us the config filename that will be used
WORKSPACE_ABS=$(cd "$WORKSPACE" 2>/dev/null && pwd || echo "$WORKSPACE")
INSTANCE_ID=$($PYTHON -c "import hashlib; import os; ws = os.path.abspath('$WORKSPACE'); print(hashlib.md5(ws.encode()).hexdigest()[:8])" 2>/dev/null || echo "unknown")
INSTANCE_CONFIG="config_${INSTANCE_ID}.json"
# Check if workspace is new (doesn't exist)
if [ ! -d "$WORKSPACE" ]; then
section "New Workspace Detected"
info "Workspace directory '$WORKSPACE' does not exist."
info "This appears to be a new use case with a fresh workspace."
echo ""
step "Resetting configuration to allow fresh MCP server setup..."
# Create the workspace directory
mkdir -p "$WORKSPACE"
ok "Created workspace directory: $WORKSPACE"
fi
section "Instance Configuration"
info "Instance ID: $C_BOLD$INSTANCE_ID$C_RESET"
info "Config File: $C_BOLD$INSTANCE_CONFIG$C_RESET"
info "Workspace: $C_BOLD$WORKSPACE$C_RESET"
section "Deploying MCP Servers"
info "Crash monitoring is active: crashed Python MCP servers are restarted automatically"
info "with backoff (crash-looping servers are abandoned after repeated failures)."
info "Docker services auto-restart via Docker's 'restart: unless-stopped' policy,"
info "so they keep running after Ctrl+C. To tear everything down:"
detail "./stop.sh $WORKSPACE"
if [ "$NO_INPUT" = true ]; then
$PYTHON deploy.py --config config.json --mcp-dir mcp_host --host_port_min "$START_PORT" --host_port_max "$END_PORT" --workspace $WORKSPACE --enable-all &
else
$PYTHON deploy.py --config config.json --mcp-dir mcp_host --host_port_min "$START_PORT" --host_port_max "$END_PORT" --workspace $WORKSPACE &
fi
HOST_PID=$!
wait $HOST_PID
DEPLOY_EXIT_CODE=$?
echo ""
if [ $DEPLOY_EXIT_CODE -ne 0 ]; then
section "DEPLOYMENT FAILED"
fail "deploy.py exited with status $DEPLOY_EXIT_CODE"
echo ""
info "Instance-specific config file: $C_BOLD$INSTANCE_CONFIG$C_RESET"
info "If this was a first run, enable the MCP services you want in that file"
info "and rerun this command."
echo ""
exit $DEPLOY_EXIT_CODE
fi
# After deployment, show the config file location
section "DEPLOYMENT COMPLETE"
ok "Instance deployed successfully"
echo ""
warn "IMPORTANT: Your instance-specific config file is: $C_BOLD$INSTANCE_CONFIG$C_RESET"
info "Edit this file to enable/disable MCP services:"
info " 1. Edit $INSTANCE_CONFIG"
info " 2. Change 'enabled': false to 'enabled': true for services you want"
info " 3. Restart the deployment to apply changes"
echo ""
info "To stop this instance completely (incl. Docker services): ${C_BOLD}./stop.sh $WORKSPACE${C_RESET}"
echo ""