2025-10-14 12:06:17 -04:00
#!/bin/bash
# Common setup script for node and npx
# This script sets up hermit and node.js environment
# Enable strict mode to exit on errors and unset variables
set -euo pipefail
# Set log file
LOG_FILE = "/tmp/mcp.log"
# Clear the log file at the start
2025-12-17 00:13:28 +06:00
> " ${ LOG_FILE } "
2025-10-14 12:06:17 -04:00
# Function for logging
log( ) {
2025-12-17 00:13:28 +06:00
local MESSAGE = " ${ 1 } "
echo " $( date +'%Y-%m-%d %H:%M:%S' ) - ${ MESSAGE } " | tee -a " ${ LOG_FILE } " >& 2
2025-10-14 12:06:17 -04:00
}
# Trap errors and log them before exiting
trap 'log "An error occurred. Exiting with status $?."' ERR
log "Starting node setup (common)."
2026-06-22 13:35:14 -07:00
# GUI-launched macOS apps inherit a minimal PATH that omits the system sbin
# directories, so Hermit's bootstrap cannot find tools like `chown` (which lives
# in /usr/sbin on macOS) and aborts with exit 127. Ensure they are reachable;
# this is harmless on Linux, where these paths are typically already present.
export PATH = " /usr/sbin:/sbin: ${ PATH } "
2026-03-09 19:06:19 +03:00
if [ -n " ${ GOOSE_PATH_ROOT :- } " ] ; then
RESOLVED_GOOSE_CONFIG_DIR = " ${ GOOSE_PATH_ROOT } /config "
elif [ -n " ${ GOOSE_CONFIG_DIR :- } " ] ; then
log "GOOSE_CONFIG_DIR is deprecated for desktop shims; prefer GOOSE_PATH_ROOT."
RESOLVED_GOOSE_CONFIG_DIR = " ${ GOOSE_CONFIG_DIR } "
else
RESOLVED_GOOSE_CONFIG_DIR = " ${ HOME } /.config/goose "
fi
MCP_HERMIT_DIR = " ${ RESOLVED_GOOSE_CONFIG_DIR } /mcp-hermit "
2026-06-16 13:51:05 -05:00
mkdir -p " ${ RESOLVED_GOOSE_CONFIG_DIR } "
HERMIT_SETUP_LOCK_DIR = " ${ RESOLVED_GOOSE_CONFIG_DIR } /.mcp-hermit-setup.lock "
HERMIT_SETUP_LOCK_TIMEOUT = 300
HERMIT_SETUP_LOCK_STARTED_AT = $( date +%s)
while ! mkdir " ${ HERMIT_SETUP_LOCK_DIR } " 2>/dev/null; do
if [ $(( $( date +%s) - HERMIT_SETUP_LOCK_STARTED_AT )) -ge " ${ HERMIT_SETUP_LOCK_TIMEOUT } " ] ; then
log " Timed out waiting for ${ HERMIT_SETUP_LOCK_DIR } ; removing stale lock. "
rm -rf " ${ HERMIT_SETUP_LOCK_DIR } "
HERMIT_SETUP_LOCK_STARTED_AT = $( date +%s)
fi
sleep 0.1
done
trap 'rm -rf "${HERMIT_SETUP_LOCK_DIR}"; log "An error occurred. Exiting with status $?."' ERR
trap 'rm -rf "${HERMIT_SETUP_LOCK_DIR}"' EXIT
2026-03-09 19:06:19 +03:00
2025-11-17 14:04:36 -06:00
# One-time cleanup for existing Linux users to fix locking issues
2026-03-09 19:06:19 +03:00
CLEANUP_MARKER = " ${ RESOLVED_GOOSE_CONFIG_DIR } /.mcp-hermit-cleanup-v1 "
2025-12-17 00:13:28 +06:00
if [ [ " $( uname -s) " = = "Linux" ] ] && [ ! -f " ${ CLEANUP_MARKER } " ] ; then
2025-11-17 14:04:36 -06:00
log "Performing one-time cleanup of old mcp-hermit directory to fix locking issues."
2026-03-09 19:06:19 +03:00
if [ -d " ${ MCP_HERMIT_DIR } " ] ; then
2026-06-16 13:51:05 -05:00
STALE_MCP_HERMIT_DIR = " ${ MCP_HERMIT_DIR } .stale. $$ "
mv " ${ MCP_HERMIT_DIR } " " ${ STALE_MCP_HERMIT_DIR } "
rm -rf " ${ STALE_MCP_HERMIT_DIR } "
2025-11-17 14:04:36 -06:00
log "Removed old mcp-hermit directory."
fi
2025-12-17 00:13:28 +06:00
touch " ${ CLEANUP_MARKER } "
2025-11-17 14:04:36 -06:00
log "Cleanup completed. Marker file created."
fi
2026-03-09 19:06:19 +03:00
# Ensure mcp-hermit/bin exists
log " Creating directory ${ MCP_HERMIT_DIR } /bin if it does not exist. "
mkdir -p " ${ MCP_HERMIT_DIR } /bin "
2025-10-14 12:06:17 -04:00
2026-03-09 19:06:19 +03:00
# Change to the mcp-hermit directory
log " Changing to directory ${ MCP_HERMIT_DIR } . "
cd " ${ MCP_HERMIT_DIR } "
2025-10-14 12:06:17 -04:00
2026-07-21 08:47:59 -04:00
download_hermit_binary( ) {
local HERMIT_TMP
HERMIT_TMP = $( mktemp " ${ MCP_HERMIT_DIR } /bin/hermit.XXXXXX " )
if curl -fsSL " https://github.com/cashapp/hermit/releases/download/stable/hermit- $( uname -s | tr '[:upper:]' '[:lower:]' ) - $( uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/' ) .gz " \
| gzip -dc > " ${ HERMIT_TMP } " && chmod +x " ${ HERMIT_TMP } " ; then
mv " ${ HERMIT_TMP } " " ${ MCP_HERMIT_DIR } /bin/hermit "
else
rm -f " ${ HERMIT_TMP } "
return 1
fi
}
activate_hermit_environment( ) {
if ! HERMIT_ENV = $( hermit env --shell= bash --activate 2>> " ${ LOG_FILE } " ) ; then
log "Hermit does not support bash activation. Updating hermit binary."
download_hermit_binary
HERMIT_ENV = $( hermit env --shell= bash --activate 2>> " ${ LOG_FILE } " )
fi
eval " ${ HERMIT_ENV } " >> " ${ LOG_FILE } " 2>& 1
}
2025-10-14 12:06:17 -04:00
# Check if hermit binary exists and download if not
2026-03-09 19:06:19 +03:00
if [ ! -f " ${ MCP_HERMIT_DIR } /bin/hermit " ] ; then
2025-10-14 12:06:17 -04:00
log "Hermit binary not found. Downloading hermit binary."
2026-07-21 08:47:59 -04:00
download_hermit_binary
2025-10-14 12:06:17 -04:00
log "Hermit binary downloaded and made executable."
else
log "Hermit binary already exists. Skipping download."
fi
log "setting hermit cache to be local for MCP servers"
2026-03-09 19:06:19 +03:00
mkdir -p " ${ MCP_HERMIT_DIR } /cache "
export HERMIT_STATE_DIR = " ${ MCP_HERMIT_DIR } /cache "
2025-10-14 12:06:17 -04:00
# Update PATH
2026-03-09 19:06:19 +03:00
export PATH = " ${ MCP_HERMIT_DIR } /bin: ${ PATH } "
log " Updated PATH to include ${ MCP_HERMIT_DIR } /bin. "
2025-10-14 12:06:17 -04:00
# Verify hermit installation
log "Checking for hermit in PATH."
2025-12-17 00:13:28 +06:00
which hermit >> " ${ LOG_FILE } "
2025-10-14 12:06:17 -04:00
2025-11-03 11:37:25 -06:00
# Check if hermit environment is already initialized (only run init on first setup)
2025-12-17 00:13:28 +06:00
if [ ! -f "bin/activate-hermit" ] ; then
2025-11-03 11:37:25 -06:00
log "Hermit environment not yet initialized. Setting up hermit."
# Fix hermit self-update lock issues on Linux by using temp binary for init only
if [ [ " $( uname -s) " = = "Linux" ] ] ; then
log "Creating temp dir with bin subdirectory for hermit copy to avoid self-update locks."
HERMIT_TMP_DIR = " /tmp/hermit_tmp_ $$ /bin "
2025-12-17 00:13:28 +06:00
mkdir -p " ${ HERMIT_TMP_DIR } "
2026-03-09 19:06:19 +03:00
cp " ${ MCP_HERMIT_DIR } /bin/hermit " " ${ HERMIT_TMP_DIR } /hermit "
2025-12-17 00:13:28 +06:00
chmod +x " ${ HERMIT_TMP_DIR } /hermit "
2026-06-16 13:51:05 -05:00
HERMIT_ORIGINAL_PATH = " ${ PATH } "
2025-12-17 00:13:28 +06:00
export PATH = " ${ HERMIT_TMP_DIR } : ${ PATH } "
2025-11-03 11:37:25 -06:00
HERMIT_CLEANUP_DIR = " /tmp/hermit_tmp_ $$ "
fi
# Initialize hermit
log "Initializing hermit."
2025-12-17 00:13:28 +06:00
hermit init >> " ${ LOG_FILE } "
2025-11-03 11:37:25 -06:00
# Clean up temp dir if it was created
if [ [ -n " ${ HERMIT_CLEANUP_DIR :- } " ] ] ; then
log "Cleaning up temporary hermit binary directory."
2026-06-16 13:51:05 -05:00
export PATH = " ${ HERMIT_ORIGINAL_PATH } "
2025-12-17 00:13:28 +06:00
rm -rf " ${ HERMIT_CLEANUP_DIR } "
2025-11-03 11:37:25 -06:00
fi
else
log "Hermit environment already initialized. Skipping init."
fi
2026-06-22 13:35:14 -07:00
# Activate the environment with output redirected to log.
# Activation must run on every platform: macOS GUI apps otherwise never get the
# hermit-managed node/npx onto PATH, so STDIO extensions fail with
2026-07-21 08:47:59 -04:00
# "env: node: No such file or directory".
2026-06-22 13:35:14 -07:00
log "Activating hermit environment."
2026-07-21 08:47:59 -04:00
activate_hermit_environment
2025-10-14 12:06:17 -04:00
# Install Node.js using hermit
log "Installing Node.js with hermit."
2025-12-17 00:13:28 +06:00
hermit install node >> " ${ LOG_FILE } "
2026-07-21 08:47:59 -04:00
activate_hermit_environment
2025-10-14 12:06:17 -04:00
# Verify installations
log "Verifying installation locations:"
log " hermit: $( which hermit) "
log " node: $( which node) "
log " npx: $( which npx) "
2026-06-16 13:51:05 -05:00
rm -rf " ${ HERMIT_SETUP_LOCK_DIR } "
trap 'log "An error occurred. Exiting with status $?."' ERR
trap - EXIT
2025-10-14 12:06:17 -04:00
log "Checking for GOOSE_NPM_REGISTRY and GOOSE_NPM_CERT environment variables for custom npm registry setup..."
# Check if GOOSE_NPM_REGISTRY is set and accessible
2025-12-17 00:13:28 +06:00
if [ -n " ${ GOOSE_NPM_REGISTRY :- } " ] && curl -s --head --fail " ${ GOOSE_NPM_REGISTRY } " > /dev/null; then
log " Checking custom goose registry availability: ${ GOOSE_NPM_REGISTRY } "
log " ${ GOOSE_NPM_REGISTRY } is accessible. Using it for npm registry. "
export NPM_CONFIG_REGISTRY = " ${ GOOSE_NPM_REGISTRY } "
2025-10-14 12:06:17 -04:00
# Check if GOOSE_NPM_CERT is set and accessible
2025-12-17 00:13:28 +06:00
if [ -n " ${ GOOSE_NPM_CERT :- } " ] && curl -s --head --fail " ${ GOOSE_NPM_CERT } " > /dev/null; then
log " Downloading certificate from: ${ GOOSE_NPM_CERT } "
2026-03-09 19:06:19 +03:00
curl -sSL -o " ${ MCP_HERMIT_DIR } /cert.pem " " ${ GOOSE_NPM_CERT } "
2025-10-14 12:06:17 -04:00
if [ $? -eq 0 ] ; then
log "Certificate downloaded successfully."
2026-03-09 19:06:19 +03:00
export NODE_EXTRA_CA_CERTS = " ${ MCP_HERMIT_DIR } /cert.pem "
2025-10-14 12:06:17 -04:00
else
log "Unable to download the certificate. Skipping certificate setup."
fi
else
log "GOOSE_NPM_CERT is either not set or not accessible. Skipping certificate setup."
fi
else
log "GOOSE_NPM_REGISTRY is either not set or not accessible. Falling back to default npm registry."
export NPM_CONFIG_REGISTRY = "https://registry.npmjs.org/"
fi
log "Node setup (common) completed successfully."