#!/usr/bin/env bash # Lanzador del JARVIS naranja: el nucleo, fuera del flatpak. # # arranca.sh levanta ollama si hace falta, abre el panel y escucha # arranca.sh --panel solo el panel, sin microfono # arranca.sh --stop lo para y libera la grafica # arranca.sh --estado dice si esta vivo # # Comparado con el lanzador del JARVIS azul, aqui sobra casi todo: no hay que # adivinar la rama del flatpak, ni leer gsettings con escapado de GVariant, ni # preguntarle a la aplicacion que modelo tiene puesto. El nucleo lo sabe porque # es suyo. # # Lo que SI se conserva, porque en esta maquina importa: al parar se le dice a # ollama que suelte el modelo. Son 2,1 GB de una tarjeta de 4, y sin esto whisper # se queda sin sitio la proxima vez. set -uo pipefail RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd) PYTHON=$RAIZ/venv/bin/python [ -x "$PYTHON" ] || PYTHON=$(command -v python3) OLLAMA_URL=${JARVIS_OLLAMA:-http://127.0.0.1:11434} REGISTRO=$RAIZ/jarvis.log PUERTO=${JARVIS_PUERTO:-8790} # Fichero de PID, no pgrep. # # El primer intento fue marcar el proceso con una variable de entorno y buscarla # con `pgrep -f`. No funciona: pgrep mira la LINEA DE ORDENES, y `env VAR=1 cmd` # no deja la variable ahi. El sintoma fue peor que un simple "no lo encuentra": # el lanzador creia que no habia nada corriendo, arrancaba un segundo, y ese # moria con "address already in use" mientras el primero seguia vivo. # # Y buscar por el nombre del script tiene el problema de siempre: el patron # aparece en la linea de ordenes del propio pgrep. Un fichero de PID no se # equivoca. PID=${XDG_RUNTIME_DIR:-/tmp}/jarvis-nucleo.pid aviso() { command -v notify-send >/dev/null && \ notify-send -i "$RAIZ/jarvis-naranja.svg" "JARVIS" "$1"; } modelo() { grep -oP 'MODELO = os\.environ\.get\("JARVIS_MODELO", "\K[^"]+' \ "$RAIZ/cerebro/ollama.py" 2>/dev/null || echo "qwen3.5:4b-jarvis" } # Vivo = hay un pid apuntado Y ese proceso existe. Un pid huerfano de un # reinicio no puede hacernos creer que sigue en marcha. vivo() { [ -f "$PID" ] || return 1 local p; p=$(cat "$PID" 2>/dev/null) [ -n "$p" ] && kill -0 "$p" 2>/dev/null } ollama_up() { curl -s --max-time 2 "$OLLAMA_URL/api/version" >/dev/null 2>&1; } puerto_ocupado() { curl -s --max-time 2 "http://127.0.0.1:$PUERTO/" >/dev/null 2>&1; } case "${1:-arranca}" in --stop) # Tambien lo que este ocupando el puerto sin fichero de pid: si no, un # huerfano impide arrancar de nuevo y --stop parece no hacer nada. if ! vivo && puerto_ocupado && command -v fuser >/dev/null; then fuser -k "$PUERTO/tcp" >/dev/null 2>&1 fi if vivo; then kill "$(cat "$PID")" 2>/dev/null # margen para que suelte el puerto antes de que nadie lo reclame for _ in 1 2 3 4 5 6 7 8 9 10; do vivo || break; sleep 0.3; done vivo && kill -9 "$(cat "$PID")" 2>/dev/null fi rm -f "$PID" if ollama_up; then # keep_alive 0: suelta el modelo ya, no dentro de cinco minutos curl -s "$OLLAMA_URL/api/generate" \ -d "{\"model\":\"$(modelo)\",\"keep_alive\":0}" >/dev/null 2>&1 aviso "Detenido, grafica liberada" else aviso "Detenido" fi exit 0 ;; --estado) # Se mira el pid Y el puerto. Con solo el pid, una instancia arrancada a # mano —o superviviente de un lanzador que murio— daba "parado" mientras # el panel contestaba tan tranquilo, que es la peor respuesta posible: # la que hace dudar de si el problema es el programa o quien pregunta. if vivo; then echo "JARVIS naranja: vivo" exit 0 fi if puerto_ocupado; then echo "JARVIS naranja: vivo, pero arrancado por fuera de este lanzador" echo " (no hay $PID; para pararlo: fuser -k $PUERTO/tcp)" exit 0 fi echo "JARVIS naranja: parado" exit 1 ;; esac if vivo || puerto_ocupado; then # Ya esta abierto: en vez de levantar otro —que moriria con "address already # in use" y dejaria un rastro confuso— se trae el panel al frente. aviso "Ya estaba en marcha" # La MISMA ventana que abre el arranque normal. Antes esto era xdg-open, que # abre el navegador POR DEFECTO en una pestaña: segun si JARVIS ya estaba # abierto o no, el mismo icono daba una aplicacion o una pestaña de Firefox. setsid "$RAIZ/ventana.sh" "http://127.0.0.1:$PUERTO/" >/dev/null 2>&1 & exit 0 fi # ollama, si no esta ya. En segundo plano y sin esperar: el catalogo contesta # las ordenes conocidas sin tocar el modelo, asi que JARVIS es util desde el # primer segundo aunque ollama tarde en calentar. if ! ollama_up && command -v ollama >/dev/null; then setsid ollama serve >/dev/null 2>&1 & fi # El indice de COFRE, si no esta. En segundo plano: JARVIS arranca sin el y el # cerebro lo usa en cuanto este listo. Reconstruirlo a mano: saber/indexa.py if [ ! -f "$RAIZ/datos/saber.jsonl" ]; then setsid "$PYTHON" "$RAIZ/saber/indexa.py" >/dev/null 2>&1 & fi : > "$REGISTRO" 2>/dev/null || REGISTRO=/dev/null aviso "Arrancando" MODO=(--panel --escucha) [ "${1:-}" = "--panel" ] && MODO=(--panel) # El pid se apunta ANTES del exec y es el de este mismo shell, que con exec # pasa a ser el de Python: asi el fichero nunca apunta a un proceso que no es. echo $$ > "$PID" trap 'rm -f "$PID"' EXIT exec "$PYTHON" -u "$RAIZ/jarvis.py" "${MODO[@]}" >>"$REGISTRO" 2>&1