diff --git a/README.md b/README.md index 71c50ca..7446ab5 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,24 @@ openclaw gateway status ## Respaldo y recuperación +Para ejecutar un diagnóstico independiente y de solo lectura del servidor usa +`ubuntu_server_diagnostic.sh`. Comprueba Ubuntu, hardware, servicios, puertos, +Ollama, LiteLLM, OpenClaw y UFW: + +```bash +chmod +x ubuntu_server_diagnostic.sh +sudo ./ubuntu_server_diagnostic.sh --report /tmp/ubuntu-server-test.txt +``` + +Puedes cambiar los endpoints o el modelo esperado mediante variables de entorno: + +```bash +OLLAMA_URL=http://127.0.0.1:11434 \ +LITELLM_URL=http://127.0.0.1:8000 \ +MODEL_NAME=qwen2.5-coder:14b \ +sudo -E ./ubuntu_server_diagnostic.sh +``` + Para guardar la configuración del servidor antes de realizar cambios, ejecuta el script como `root`: diff --git a/ubuntu_server_diagnostic.sh b/ubuntu_server_diagnostic.sh new file mode 100755 index 0000000..862d882 --- /dev/null +++ b/ubuntu_server_diagnostic.sh @@ -0,0 +1,310 @@ +#!/usr/bin/env bash + +set -uo pipefail + +readonly SCRIPT_NAME="$(basename "$0")" +readonly DEFAULT_OLLAMA_URL="${OLLAMA_URL:-http://127.0.0.1:11434}" +readonly DEFAULT_LITELLM_URL="${LITELLM_URL:-http://127.0.0.1:8000}" +readonly MODEL_NAME="${MODEL_NAME:-qwen2.5-coder:14b}" +readonly OPENCLAW_CONFIG="${OPENCLAW_CONFIG:-${HOME}/.openclaw/openclaw.json}" + +passed=0 +failed=0 +warnings=0 +report_file="" + +show_help() { + cat <&2; exit 2; } + report_file="$2" + shift 2 + ;; + -h|--help) + show_help + exit 0 + ;; + *) + echo "Opción desconocida: $1" >&2 + show_help >&2 + exit 2 + ;; + esac +done + +if [[ -n "$report_file" ]]; then + mkdir -p "$(dirname "$report_file")" + : > "$report_file" || { echo "No se puede escribir $report_file" >&2; exit 1; } + exec > >(tee -a "$report_file") 2>&1 +fi + +pass() { + printf '[ OK ] %s\n' "$1" + passed=$((passed + 1)) +} + +fail() { + printf '[FAIL] %s\n' "$1" + failed=$((failed + 1)) +} + +warn() { + printf '[WARN] %s\n' "$1" + warnings=$((warnings + 1)) +} + +section() { + printf '\n=== %s ===\n' "$1" +} + +has_command() { + command -v "$1" >/dev/null 2>&1 +} + +check_command() { + local command_name="$1" + local description="$2" + + if has_command "$command_name"; then + pass "$description: $command_name" + else + fail "$description: falta $command_name" + fi +} + +check_service() { + local service="$1" + + if ! has_command systemctl; then + warn "No se puede consultar $service: falta systemctl" + return + fi + + if systemctl is-active --quiet "$service"; then + pass "Servicio activo: $service" + elif systemctl is-enabled --quiet "$service" 2>/dev/null; then + warn "Servicio habilitado pero no activo: $service" + else + fail "Servicio no activo: $service" + fi +} + +check_file() { + local file_path="$1" + local description="$2" + + if [[ -e "$file_path" || -L "$file_path" ]]; then + pass "$description: $file_path" + else + warn "$description no encontrado: $file_path" + fi +} + +http_check() { + local url="$1" + local description="$2" + local status + + if ! has_command curl; then + fail "$description: falta curl" + return + fi + + status="$(curl --silent --show-error --max-time 10 --output /dev/null --write-out '%{http_code}' "$url" 2>/dev/null)" + if [[ "$status" =~ ^2[0-9][0-9]$ ]]; then + pass "$description: HTTP $status ($url)" + else + fail "$description: HTTP ${status:-sin respuesta} ($url)" + fi +} + +section "Sistema" +printf 'Fecha: %s\n' "$(date --iso-8601=seconds 2>/dev/null || date)" +printf 'Host: %s\n' "$(hostname 2>/dev/null || echo desconocido)" +printf 'Usuario: %s\n' "$(id -un 2>/dev/null || echo desconocido)" + +if [[ -r /etc/os-release ]]; then + . /etc/os-release + printf 'Sistema: %s %s\n' "${NAME:-desconocido}" "${VERSION_ID:-desconocida}" + if [[ "${ID:-}" == ubuntu ]]; then + pass "Sistema operativo Ubuntu" + else + warn "El sistema operativo detectado no es Ubuntu: ${ID:-desconocido}" + fi +else + fail "No se puede leer /etc/os-release" +fi + +check_command curl "Herramienta HTTP" +check_command systemctl "Gestor de servicios" +check_command ss "Inspección de puertos" + +if has_command free; then + free -h +fi +if has_command df; then + df -hT / 2>/dev/null || true +fi +if has_command swapon; then + if swapon --show --noheadings | grep -q .; then + pass "Swap activa" + else + warn "No hay swap activa" + fi +fi + +section "Hardware" +if has_command nvidia-smi; then + if nvidia-smi --query-gpu=name,driver_version,memory.total,memory.used --format=csv,noheader 2>/dev/null; then + pass "NVIDIA disponible" + else + fail "nvidia-smi no puede consultar la GPU" + fi +else + warn "nvidia-smi no está instalado o la GPU NVIDIA no está disponible" +fi + +if [[ -r /proc/sys/vm/swappiness ]]; then + printf 'vm.swappiness: %s\n' "$(cat /proc/sys/vm/swappiness)" +fi + +section "Archivos de configuración" +check_file /etc/systemd/system/ollama.service.d/override.conf "Override de Ollama" +check_file /etc/systemd/system/litellm.service "Servicio de LiteLLM" +check_file /etc/systemd/system/nvidia-power-limit.service "Servicio de límite NVIDIA" +check_file /etc/ufw "Configuración UFW" +check_file /etc/netplan "Configuración Netplan" +check_file "$OPENCLAW_CONFIG" "Configuración OpenClaw" + +if [[ -f "$OPENCLAW_CONFIG" ]]; then + if grep -Eq '"(apiKey|token|password|secret)"[[:space:]]*:' "$OPENCLAW_CONFIG"; then + warn "La configuración OpenClaw contiene campos sensibles; no se mostrarán" + fi + if grep -q 'litellm/qwen2.5-coder:14b' "$OPENCLAW_CONFIG"; then + pass "OpenClaw apunta al modelo LiteLLM esperado" + else + warn "No se encontró el modelo LiteLLM esperado en OpenClaw" + fi +fi + +for config_path in \ + /etc/litellm_config.yaml \ + /root/litellm_config.yaml \ + "$HOME/litellm_config.yaml" +do + if [[ -f "$config_path" ]]; then + printf 'LiteLLM config detectada: %s\n' "$config_path" + if grep -q "$MODEL_NAME" "$config_path"; then + pass "LiteLLM contiene el modelo $MODEL_NAME" + else + warn "LiteLLM no contiene el modelo esperado: $MODEL_NAME" + fi + fi +done + +section "Servicios" +check_service ollama +check_service litellm +if has_command openclaw; then + printf 'El Gateway de OpenClaw se validará con su propio comando de estado.\n' +else + warn "OpenClaw no está instalado en PATH" +fi + +section "Puertos" +if has_command ss; then + printf 'Puertos escuchando relevantes:\n' + ss -ltnp 2>/dev/null | awk 'NR == 1 || /:8000|:11434|:18789/' || true + if ss -ltn 2>/dev/null | grep -Eq ':11434[[:space:]]'; then + pass "Ollama escucha en el puerto 11434" + else + fail "No se detecta Ollama escuchando en el puerto 11434" + fi + if ss -ltn 2>/dev/null | grep -Eq ':8000[[:space:]]'; then + pass "LiteLLM escucha en el puerto 8000" + else + fail "No se detecta LiteLLM escuchando en el puerto 8000" + fi +fi + +section "Ollama" +http_check "$DEFAULT_OLLAMA_URL/api/tags" "API de Ollama" +if has_command ollama; then + if ollama list 2>/dev/null | awk 'NR > 1 {print $1}' | grep -Fxq "$MODEL_NAME"; then + pass "Modelo Ollama instalado: $MODEL_NAME" + else + fail "Modelo Ollama no encontrado: $MODEL_NAME" + fi +else + warn "Comando ollama no disponible" +fi + +section "LiteLLM" +http_check "$DEFAULT_LITELLM_URL/health/liveliness" "Salud de LiteLLM" +if has_command curl; then + models_response="$(curl --silent --show-error --max-time 10 "$DEFAULT_LITELLM_URL/v1/models" 2>/dev/null || true)" + if [[ "$models_response" == *"$MODEL_NAME"* ]]; then + pass "LiteLLM publica el modelo $MODEL_NAME" + else + fail "LiteLLM no publica el modelo $MODEL_NAME en /v1/models" + fi +fi + +section "OpenClaw" +if has_command openclaw; then + openclaw --version 2>/dev/null || true + if openclaw doctor 2>&1; then + pass "openclaw doctor terminó correctamente" + else + fail "openclaw doctor reportó errores" + fi + if openclaw gateway status 2>&1; then + pass "Estado del Gateway consultado correctamente" + else + warn "No se pudo consultar correctamente el Gateway" + fi +else + warn "No se puede probar OpenClaw porque no está instalado" +fi + +section "Firewall" +if has_command ufw; then + ufw status verbose 2>/dev/null || true + if ufw status 2>/dev/null | grep -qi active; then + pass "UFW está activo" + else + warn "UFW no está activo" + fi +else + warn "UFW no está instalado" +fi + +section "Resumen" +printf 'Correctos: %d | Fallos: %d | Advertencias: %d\n' "$passed" "$failed" "$warnings" +if [[ -n "$report_file" ]]; then + printf 'Reporte: %s\n' "$report_file" +fi + +if [[ "$failed" -gt 0 ]]; then + exit 1 +fi +exit 0