Skip to content

Commit bf33359

Browse files
authored
Update run-gunicorn.sh script, closes #397 closes #430 (#608)
* Update run-gunicorn.sh script, closes #397 closes #430 Signed-off-by: Mihai Criveti <[email protected]> * Remove db migration from script, now part of main code Signed-off-by: Mihai Criveti <[email protected]> * Remove db migration from script, now part of main code Signed-off-by: Mihai Criveti <[email protected]> --------- Signed-off-by: Mihai Criveti <[email protected]>
1 parent 1a37247 commit bf33359

File tree

3 files changed

+512
-83
lines changed

3 files changed

+512
-83
lines changed

mcpgateway/static/admin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4414,7 +4414,7 @@ async function handleEditToolFormSubmit(event) {
44144414
}
44154415

44164416
// // Save CodeMirror editors' contents if present
4417-
4417+
44184418
if (window.editToolHeadersEditor) window.editToolHeadersEditor.save();
44194419
if (window.editToolSchemaEditor) window.editToolSchemaEditor.save();
44204420

run-gunicorn-v2.sh

Lines changed: 152 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,32 @@
1414
# - Optional TLS/SSL support for secure connections
1515
# - Database initialization before server start
1616
# - Comprehensive error handling and user feedback
17+
# - Process lock to prevent duplicate instances
18+
# - Auto-detection of optimal worker count based on CPU cores
19+
# - Support for preloading application code (memory optimization)
1720
#
1821
# Environment Variables:
1922
# PYTHON : Path to Python interpreter (optional)
2023
# VIRTUAL_ENV : Path to active virtual environment (auto-detected)
21-
# GUNICORN_WORKERS : Number of worker processes (default: 2 × CPU cores + 1)
24+
# GUNICORN_WORKERS : Number of worker processes (default: 2, or "auto")
2225
# GUNICORN_TIMEOUT : Worker timeout in seconds (default: 600)
2326
# GUNICORN_MAX_REQUESTS : Max requests per worker before restart (default: 1000)
2427
# GUNICORN_MAX_REQUESTS_JITTER : Random jitter for max requests (default: 100)
28+
# GUNICORN_PRELOAD_APP : Preload app before forking workers (default: false)
29+
# GUNICORN_DEV_MODE : Enable developer mode with hot reload (default: false)
2530
# SSL : Enable TLS/SSL (true/false, default: false)
2631
# CERT_FILE : Path to SSL certificate (default: certs/cert.pem)
2732
# KEY_FILE : Path to SSL private key (default: certs/key.pem)
33+
# SKIP_DB_INIT : Skip database initialization (default: false)
34+
# FORCE_START : Force start even if another instance is running (default: false)
2835
#
2936
# Usage:
3037
# ./run-gunicorn.sh # Run with defaults
3138
# SSL=true ./run-gunicorn.sh # Run with TLS enabled
3239
# GUNICORN_WORKERS=16 ./run-gunicorn.sh # Run with 16 workers
40+
# GUNICORN_PRELOAD_APP=true ./run-gunicorn.sh # Preload app for memory optimization
41+
# GUNICORN_DEV_MODE=true ./run-gunicorn.sh # Run in developer mode with hot reload
42+
# FORCE_START=true ./run-gunicorn.sh # Force start (bypass lock check)
3343
#───────────────────────────────────────────────────────────────────────────────
3444

3545
# Exit immediately on error, undefined variable, or pipe failure
@@ -49,7 +59,62 @@ cd "${SCRIPT_DIR}" || {
4959
}
5060

5161
#────────────────────────────────────────────────────────────────────────────────
52-
# SECTION 2: Virtual Environment Activation
62+
# SECTION 2: Process Lock Check
63+
# Prevent multiple instances from running simultaneously unless forced
64+
#────────────────────────────────────────────────────────────────────────────────
65+
LOCK_FILE="/tmp/mcpgateway-gunicorn.lock"
66+
FORCE_START=${FORCE_START:-false}
67+
68+
check_existing_process() {
69+
if [[ -f "${LOCK_FILE}" ]]; then
70+
local pid
71+
pid=$(<"${LOCK_FILE}")
72+
73+
# Check if the process is actually running
74+
if kill -0 "${pid}" 2>/dev/null; then
75+
echo "⚠️ WARNING: Another instance of MCP Gateway appears to be running (PID: ${pid})"
76+
77+
# Check if it's actually gunicorn
78+
if ps -p "${pid}" -o comm= | grep -q gunicorn; then
79+
if [[ "${FORCE_START}" != "true" ]]; then
80+
echo "❌ FATAL: MCP Gateway is already running!"
81+
echo " To stop it: kill ${pid}"
82+
echo " To force start anyway: FORCE_START=true $0"
83+
exit 1
84+
else
85+
echo "⚠️ Force starting despite existing process..."
86+
fi
87+
else
88+
echo "🔧 Lock file exists but process ${pid} is not gunicorn. Cleaning up..."
89+
rm -f "${LOCK_FILE}"
90+
fi
91+
else
92+
echo "🔧 Stale lock file found. Cleaning up..."
93+
rm -f "${LOCK_FILE}"
94+
fi
95+
fi
96+
}
97+
98+
# Create cleanup function
99+
cleanup() {
100+
# Only clean up if we're the process that created the lock
101+
if [[ -f "${LOCK_FILE}" ]] && [[ "$(<"${LOCK_FILE}")" == "$" ]]; then
102+
rm -f "${LOCK_FILE}"
103+
echo "🔧 Cleaned up lock file"
104+
fi
105+
}
106+
107+
# Set up signal handlers for cleanup (but not EXIT - let gunicorn manage that)
108+
trap cleanup INT TERM
109+
110+
# Check for existing process
111+
check_existing_process
112+
113+
# Create lock file with current PID (will be updated with gunicorn PID later)
114+
echo $$ > "${LOCK_FILE}"
115+
116+
#────────────────────────────────────────────────────────────────────────────────
117+
# SECTION 3: Virtual Environment Activation
53118
# Check if a virtual environment is already active. If not, try to activate one
54119
# from known locations. This ensures dependencies are properly isolated.
55120
#────────────────────────────────────────────────────────────────────────────────
@@ -83,7 +148,7 @@ else
83148
fi
84149

85150
#────────────────────────────────────────────────────────────────────────────────
86-
# SECTION 3: Python Interpreter Detection
151+
# SECTION 4: Python Interpreter Detection
87152
# Locate a suitable Python interpreter with the following precedence:
88153
# 1. User-provided PYTHON environment variable
89154
# 2. 'python' binary in active virtual environment
@@ -136,7 +201,7 @@ if ! "${PYTHON}" -c "import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)"
136201
fi
137202

138203
#────────────────────────────────────────────────────────────────────────────────
139-
# SECTION 4: Display Application Banner
204+
# SECTION 5: Display Application Banner
140205
# Show a fancy ASCII art banner for the MCP Gateway
141206
#────────────────────────────────────────────────────────────────────────────────
142207
cat <<'EOF'
@@ -149,14 +214,18 @@ cat <<'EOF'
149214
EOF
150215

151216
#────────────────────────────────────────────────────────────────────────────────
152-
# SECTION 5: Configure Gunicorn Settings
217+
# SECTION 6: Configure Gunicorn Settings
153218
# Set up Gunicorn parameters with sensible defaults that can be overridden
154219
# via environment variables for different deployment scenarios
155220
#────────────────────────────────────────────────────────────────────────────────
156221

157222
# Number of worker processes (adjust based on CPU cores and expected load)
158-
# Default: 2 × CPU cores + 1 (automatically detected)
223+
# Default: 2 (safe default for most systems)
224+
# Set to "auto" for automatic detection based on CPU cores
159225
if [[ -z "${GUNICORN_WORKERS:-}" ]]; then
226+
# Default to 2 workers if not specified
227+
GUNICORN_WORKERS=2
228+
elif [[ "${GUNICORN_WORKERS}" == "auto" ]]; then
160229
# Try to detect CPU count
161230
if command -v nproc &>/dev/null; then
162231
CPU_COUNT=$(nproc)
@@ -165,7 +234,13 @@ if [[ -z "${GUNICORN_WORKERS:-}" ]]; then
165234
else
166235
CPU_COUNT=4 # Fallback to reasonable default
167236
fi
168-
GUNICORN_WORKERS=$((CPU_COUNT * 2 + 1))
237+
238+
# Use a more conservative formula: min(2*CPU+1, 16) to avoid too many workers
239+
CALCULATED_WORKERS=$((CPU_COUNT * 2 + 1))
240+
GUNICORN_WORKERS=$((CALCULATED_WORKERS > 16 ? 16 : CALCULATED_WORKERS))
241+
242+
echo "🔧 Auto-detected CPU cores: ${CPU_COUNT}"
243+
echo " Calculated workers: ${CALCULATED_WORKERS} → Capped at: ${GUNICORN_WORKERS}"
169244
fi
170245

171246
# Worker timeout in seconds (increase for long-running requests)
@@ -177,13 +252,27 @@ GUNICORN_MAX_REQUESTS=${GUNICORN_MAX_REQUESTS:-1000}
177252
# Random jitter for max requests (prevents all workers restarting simultaneously)
178253
GUNICORN_MAX_REQUESTS_JITTER=${GUNICORN_MAX_REQUESTS_JITTER:-100}
179254

255+
# Preload application before forking workers (saves memory but slower reload)
256+
GUNICORN_PRELOAD_APP=${GUNICORN_PRELOAD_APP:-false}
257+
258+
# Developer mode with hot reload (disables preload, enables file watching)
259+
GUNICORN_DEV_MODE=${GUNICORN_DEV_MODE:-false}
260+
261+
# Check for conflicting options
262+
if [[ "${GUNICORN_DEV_MODE}" == "true" && "${GUNICORN_PRELOAD_APP}" == "true" ]]; then
263+
echo "⚠️ WARNING: Developer mode disables application preloading"
264+
GUNICORN_PRELOAD_APP="false"
265+
fi
266+
180267
echo "📊 Gunicorn Configuration:"
181268
echo " Workers: ${GUNICORN_WORKERS}"
182269
echo " Timeout: ${GUNICORN_TIMEOUT}s"
183270
echo " Max Requests: ${GUNICORN_MAX_REQUESTS}${GUNICORN_MAX_REQUESTS_JITTER})"
271+
echo " Preload App: ${GUNICORN_PRELOAD_APP}"
272+
echo " Developer Mode: ${GUNICORN_DEV_MODE}"
184273

185274
#────────────────────────────────────────────────────────────────────────────────
186-
# SECTION 6: Configure TLS/SSL Settings
275+
# SECTION 7: Configure TLS/SSL Settings
187276
# Handle optional TLS configuration for secure HTTPS connections
188277
#────────────────────────────────────────────────────────────────────────────────
189278

@@ -226,32 +315,44 @@ else
226315
fi
227316

228317
#────────────────────────────────────────────────────────────────────────────────
229-
# SECTION 7: Database Initialization
318+
# SECTION 8: Database Initialization
230319
# Run database setup/migrations before starting the server
231320
#────────────────────────────────────────────────────────────────────────────────
232-
echo "🗄️ Initializing database..."
233-
if ! "${PYTHON}" -m mcpgateway.db; then
234-
echo "❌ FATAL: Database initialization failed!"
235-
echo " Please check your database configuration and connection."
236-
exit 1
321+
SKIP_DB_INIT=${SKIP_DB_INIT:-false}
322+
323+
if [[ "${SKIP_DB_INIT}" != "true" ]]; then
324+
echo "🗄️ Initializing database..."
325+
if ! "${PYTHON}" -m mcpgateway.db; then
326+
echo "❌ FATAL: Database initialization failed!"
327+
echo " Please check your database configuration and connection."
328+
echo " To skip DB initialization: SKIP_DB_INIT=true $0"
329+
exit 1
330+
fi
331+
echo "✓ Database initialized successfully"
332+
else
333+
echo "⚠️ Skipping database initialization (SKIP_DB_INIT=true)"
237334
fi
238-
echo "✓ Database initialized successfully"
239335

240336
#────────────────────────────────────────────────────────────────────────────────
241-
# SECTION 8: Launch Gunicorn Server
242-
# Start the Gunicorn server with all configured options
243-
# Using 'exec' replaces this shell process with Gunicorn for cleaner process management
337+
# SECTION 9: Verify Gunicorn Installation
338+
# Check that gunicorn is available before attempting to start
244339
#────────────────────────────────────────────────────────────────────────────────
245-
echo "🚀 Starting Gunicorn server..."
246-
echo "─────────────────────────────────────────────────────────────────────"
247-
248-
# Check if gunicorn is available
249340
if ! command -v gunicorn &> /dev/null; then
250341
echo "❌ FATAL: gunicorn command not found!"
251342
echo " Please install it with: pip install gunicorn"
252343
exit 1
253344
fi
254345

346+
echo "✓ Gunicorn found: $(command -v gunicorn)"
347+
348+
#────────────────────────────────────────────────────────────────────────────────
349+
# SECTION 10: Launch Gunicorn Server
350+
# Start the Gunicorn server with all configured options
351+
# Using 'exec' replaces this shell process with Gunicorn for cleaner process management
352+
#────────────────────────────────────────────────────────────────────────────────
353+
echo "🚀 Starting Gunicorn server..."
354+
echo "─────────────────────────────────────────────────────────────────────"
355+
255356
# Build command array to handle spaces in paths properly
256357
cmd=(
257358
gunicorn
@@ -263,8 +364,29 @@ cmd=(
263364
--max-requests-jitter "${GUNICORN_MAX_REQUESTS_JITTER}"
264365
--access-logfile -
265366
--error-logfile -
367+
--forwarded-allow-ips="*"
368+
--pid "${LOCK_FILE}" # Use lock file as PID file
266369
)
267370

371+
# Add developer mode flags if enabled
372+
if [[ "${GUNICORN_DEV_MODE}" == "true" ]]; then
373+
cmd+=( --reload --reload-extra-file gunicorn.config.py )
374+
echo "🔧 Developer mode enabled - hot reload active"
375+
echo " Watching for changes in Python files and gunicorn.config.py"
376+
377+
# In dev mode, reduce workers to 1 for better debugging
378+
if [[ "${GUNICORN_WORKERS}" -gt 2 ]]; then
379+
echo " Reducing workers to 2 for developer mode (was ${GUNICORN_WORKERS})"
380+
cmd[5]=2 # Update the workers argument
381+
fi
382+
fi
383+
384+
# Add preload flag if enabled (and not in dev mode)
385+
if [[ "${GUNICORN_PRELOAD_APP}" == "true" && "${GUNICORN_DEV_MODE}" != "true" ]]; then
386+
cmd+=( --preload )
387+
echo "✓ Application preloading enabled"
388+
fi
389+
268390
# Add SSL arguments if enabled
269391
if [[ "${SSL}" == "true" ]]; then
270392
cmd+=( --certfile "${CERT_FILE}" --keyfile "${KEY_FILE}" )
@@ -273,5 +395,13 @@ fi
273395
# Add the application module
274396
cmd+=( "mcpgateway.main:app" )
275397

398+
# Display final command for debugging
399+
echo "📋 Command: ${cmd[*]}"
400+
echo "─────────────────────────────────────────────────────────────────────"
401+
276402
# Launch Gunicorn with all configured options
403+
# Remove EXIT trap before exec - let gunicorn handle its own cleanup
404+
trap - EXIT
405+
# exec replaces this shell with gunicorn, so cleanup trap won't fire on normal exit
406+
# The PID file will be managed by gunicorn itself
277407
exec "${cmd[@]}"

0 commit comments

Comments
 (0)