JARVIS: asistente de voz local para Linux
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.
This commit is contained in:
commit
8e4bc8ad94
125 changed files with 25033 additions and 0 deletions
135
nucleo/arranca.sh
Executable file
135
nucleo/arranca.sh
Executable file
|
|
@ -0,0 +1,135 @@
|
|||
#!/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
|
||||
Loading…
Add table
Add a link
Reference in a new issue