#!/usr/bin/env sh # Instalador de koryfi-cpanel-mcp (macOS / Linux). UN comando: # curl -fsSL https://mcplocal.koryfi.com/install.sh | sh # Descarga el paquete, VERIFICA su SHA-256, lo instala con npm (sin sudo) y lo # registra en tus clientes de IA (Claude Code/Desktop, Antigravity, Codex). # El paquete lleva dentro sus dependencias: no se descarga nada mas de internet. # Variables opcionales: CPANEL_MCP_BASE_URL, CPANEL_MCP_PACKAGE, CPANEL_MCP_SHA256, # CPANEL_MCP_NO_REGISTER=1 (solo instalar), CPANEL_MCP_NO_CONNECT=1, # CPANEL_MCP_ASSUME_YES=1, CPANEL_MCP_PREFIX (carpeta de instalacion), # CPANEL_MCP_CLIENTS=claude-code (solo en esas IA, separadas por comas) set -eu # Todo va dentro de main() y se llama en la ultima linea: si la descarga se corta a la # mitad, el interprete no ejecuta un script a medias. main() { BASE_URL="${CPANEL_MCP_BASE_URL:-https://mcplocal.koryfi.com}" PKG="${CPANEL_MCP_PACKAGE:-koryfi-cpanel-mcp-latest.tgz}" EXPECTED="${CPANEL_MCP_SHA256:-}" say() { printf '%s\n' "$*"; } die() { printf 'Error: %s\n' "$*" >&2; exit 1; } # Solo https (http se admite unicamente contra este mismo equipo, para pruebas). case "$BASE_URL" in https://*) PROTO='=https' ;; http://127.0.0.1:*|http://localhost:*) PROTO='=http' ;; *) die "la direccion de descarga debe empezar por https://" ;; esac case "$PKG" in */*|*\\*|*..*) die "nombre de paquete no valido" ;; esac fetch() { curl --proto "$PROTO" -fsSL "$1" -o "$2"; } # /dev/tty puede existir sin poder abrirse (CI, contenedores, sin terminal): hay que probar a abrirlo. # (en un subshell: en POSIX un error de redirección sobre un comando especial como ":" mataria todo el script) has_tty() { ( : < /dev/tty > /dev/tty ) 2>/dev/null; } ask_yes() { [ "${CPANEL_MCP_ASSUME_YES:-}" = "1" ] && return 0 # Lo ejecuta una IA (CPANEL_MCP_CLIENTS): no se pregunta nada; su terminal puede tener /dev/tty pero nadie la mira. [ -n "${CPANEL_MCP_CLIENTS:-}" ] && return 1 if has_tty; then printf '%s [s/N] ' "$1" > /dev/tty read -r ans < /dev/tty || return 1 case "$ans" in s|S|si|Si|SI|y|Y|yes|YES) return 0 ;; esac fi return 1 } node_ok() { command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 && node -e 'const [a,b]=process.versions.node.split(".").map(Number); process.exit(a>=20?0:1)' 2>/dev/null } if ! node_ok; then say "Node.js 20 o superior no esta instalado (o es demasiado antiguo)." case "$(uname -s)" in Darwin) if command -v brew >/dev/null 2>&1 && ask_yes "Instalarlo ahora con Homebrew (brew install node)?"; then brew install node || die "brew no pudo instalar Node.js" hash -r 2>/dev/null || true fi node_ok || die "Instala Node.js: entra a https://nodejs.org, descarga el instalador para macOS (boton LTS), abrelo y sigue los pasos. Despues vuelve a ejecutar este comando." ;; MINGW*|MSYS*|CYGWIN*) die "En Windows usa PowerShell: irm $BASE_URL/install.ps1 | iex" ;; *) die "Instala Node.js LTS (https://nodejs.org, nvm o el gestor de paquetes de tu sistema) y vuelve a ejecutar este comando." ;; esac fi tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT trap 'exit 130' INT TERM say "Descargando $BASE_URL/$PKG ..." fetch "$BASE_URL/$PKG" "$tmp/$PKG" || die "no se pudo descargar el paquete" if [ -z "$EXPECTED" ]; then fetch "$BASE_URL/SHA256SUMS.txt" "$tmp/SHA256SUMS.txt" || die "no se pudo descargar SHA256SUMS.txt" EXPECTED="$(awk -v f="$PKG" '$2==f {print $1}' "$tmp/SHA256SUMS.txt")" fi [ -n "$EXPECTED" ] || die "no hay hash publicado para $PKG" if command -v sha256sum >/dev/null 2>&1; then ACTUAL="$(sha256sum "$tmp/$PKG" | awk '{print $1}')" elif command -v shasum >/dev/null 2>&1; then ACTUAL="$(shasum -a 256 "$tmp/$PKG" | awk '{print $1}')" else die "no hay sha256sum ni shasum para verificar la descarga" fi EXPECTED="$(printf '%s' "$EXPECTED" | tr 'A-F' 'a-f')" ACTUAL="$(printf '%s' "$ACTUAL" | tr 'A-F' 'a-f')" [ "$ACTUAL" = "$EXPECTED" ] || die "el SHA-256 no coincide (esperado $EXPECTED, obtenido $ACTUAL). No se instala nada." say "SHA-256 verificado: $ACTUAL" say "Instalando (puede tardar un minuto) ..." # --ignore-scripts: ningun paquete ejecuta codigo durante la instalacion. PREFIX="${CPANEL_MCP_PREFIX:-}" if [ -z "$PREFIX" ]; then if ! npm install -g --ignore-scripts "$tmp/$PKG" > "$tmp/npm.log" 2>&1; then # Sin permisos en la carpeta global de npm (lo habitual con el instalador de nodejs.org en Mac): # se instala en la carpeta personal, sin sudo. PREFIX="$HOME/.koryfi-cpanel-mcp" say "npm no puede escribir en su carpeta global; se instala en tu carpeta personal ($PREFIX)." fi fi if [ -n "$PREFIX" ]; then mkdir -p "$PREFIX" if ! npm install -g --ignore-scripts --prefix "$PREFIX" "$tmp/$PKG" > "$tmp/npm.log" 2>&1; then tail -n 15 "$tmp/npm.log" >&2 || true die "npm no pudo instalar el paquete. Copia este mensaje y envialo a soporte de Koryfi." fi ROOT="$(npm root -g --prefix "$PREFIX")" else ROOT="$(npm root -g)" fi ENTRY="$ROOT/koryfi-cpanel-mcp/dist/src/server.js" [ -f "$ENTRY" ] || die "la instalacion termino pero no encuentro $ENTRY" say "Instalado: $(node "$ENTRY" --version)" say "" # Como invocarlo despues: por su nombre si quedo en el PATH; si no, con la ruta completa. if command -v koryfi-cpanel-mcp >/dev/null 2>&1 && [ -z "$PREFIX" ]; then CMD="koryfi-cpanel-mcp" else CMD="node \"$ENTRY\"" fi if [ "${CPANEL_MCP_NO_REGISTER:-}" = "1" ]; then say "Registro automatico omitido. Cuando quieras: $CMD --install" else # Con mas de una IA instalada y una terminal delante, se pregunta en cuales registrar (con una sola, no). target="" skip=0 # Si lo instala una IA, registra solo en ella (CPANEL_MCP_CLIENTS) y no en las demas sin preguntar. if [ -n "${CPANEL_MCP_CLIENTS:-}" ]; then case "$CPANEL_MCP_CLIENTS" in *[!a-z,-]*) die "CPANEL_MCP_CLIENTS solo admite nombres como claude-code o claude-code,codex" ;; esac target="$CPANEL_MCP_CLIENTS" fi node "$ENTRY" --detect > "$tmp/detected" 2>/dev/null || : > "$tmp/detected" n=$(grep -c . "$tmp/detected" || true) if [ -z "$target" ] && [ "$n" -ge 2 ] && [ "${CPANEL_MCP_ASSUME_YES:-}" != "1" ] && has_tty; then say "Se detectaron varias IA en este equipo:" cut -f2 "$tmp/detected" | sed 's/^/ - /' > /dev/tty printf '%s [S/n] ' "Registrar Koryfi cPanel MCP en todas?" > /dev/tty ans="" read -r ans < /dev/tty || ans="" case "$ans" in n|N|no|NO|No) tab="$(printf '\t')" while IFS="$tab" read -r id label; do [ -n "$id" ] || continue if ask_yes "Registrar en $label?"; then target="${target:+$target,}$id"; fi done < "$tmp/detected" if [ -z "$target" ]; then say "No se registro en ninguna IA. Cuando quieras: $CMD --install " skip=1 fi ;; esac fi if [ "$skip" -eq 0 ]; then if [ -n "$target" ]; then # Si se pidio una IA concreta (la que esta instalando), no registrarla es un fallo: no se dice "COMPLETADA". if [ -n "${CPANEL_MCP_CLIENTS:-}" ]; then node "$ENTRY" --install "$target" || die "el MCP quedo instalado, pero no se pudo registrar en $target. Mira el detalle arriba o usa: $CMD --print-config $target" else node "$ENTRY" --install "$target" || say "No se pudo registrar en algun cliente; mira el detalle arriba o usa: $CMD --print-config " fi else node "$ENTRY" --install || say "No se pudo registrar en algun cliente; mira el detalle arriba o usa: $CMD --print-config " fi fi fi # Conectar la cuenta aqui mismo: se abre el area de clientes de Koryfi y el token llega directo, sin pasar por el chat. if [ "${CPANEL_MCP_NO_CONNECT:-}" != "1" ] && has_tty; then printf '\n%s ' "Conectamos ahora tu cuenta? Se abrira tu area de clientes de Koryfi para que elijas el hosting. Pulsa Enter para seguir (o escribe n para hacerlo despues):" > /dev/tty ans="" read -r ans < /dev/tty || ans="n" case "$ans" in n|N|no|NO|No) say "Cuando quieras: $CMD connect" ;; *) node "$ENTRY" connect < /dev/tty > /dev/tty 2>&1 || say "Puedes repetirlo con: $CMD connect" ;; esac else say "" say "Ultimo paso: cierra tu IA por completo, vuelve a abrirla y pidele: conecta mi cuenta de Koryfi" say "(o, en esta terminal: $CMD connect)" fi say "" say "INSTALACION COMPLETADA. Comando para los siguientes pasos: $CMD" } main "$@"