Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin cuentas ni claves. Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper comparten tarjeta y solo caben porque estan dimensionados para ello. El RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve de una cache de frases. Incluye instalador (install.sh), requisitos, y documentacion del stack, del manejo de root y de las acciones. Los apuntes indexados y el diario NO se incluyen: son privados y el .gitignore los bloquea.
135 lines
5.4 KiB
Bash
Executable file
135 lines
5.4 KiB
Bash
Executable file
#!/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
|