Root a demanda, lanzador de escritorio, captura nueva y ajustes
- Root: no se pide la contraseña al arrancar (echaba para atras). Se pide solo la primera vez que una accion necesita sudo, y se cachea. --candado para el modo seguro. Aviso de root prominente en el README. - install.sh crea un lanzador de escritorio (jarvis.desktop) con el icono naranja: aparece en el menu de aplicaciones para arrancar con un clic. - Captura del panel reemplazada por una mas limpia (solo la ventana). - Quitado "Probado en ..." del README (informacion del equipo). - ventanas.sh: aviso claro segun el compositor en Wayland.
This commit is contained in:
parent
a0b9afe2f9
commit
3a2a1bb111
8 changed files with 118 additions and 33 deletions
34
README.md
34
README.md
|
|
@ -59,11 +59,6 @@ Solo si vas MUY justo de VRAM y el cerebro y whisper se pelean por ella: fijar
|
||||||
comentarios del propio `Modelfile`. En una torre con una tarjeta normal **no
|
comentarios del propio `Modelfile`. En una torre con una tarjeta normal **no
|
||||||
hace falta tocar nada**.
|
hace falta tocar nada**.
|
||||||
|
|
||||||
### Probado en
|
|
||||||
|
|
||||||
NVIDIA T1200 (4 GB), 62 GB RAM, Debian 12 con GNOME/X11. Es el equipo del autor,
|
|
||||||
el caso más ajustado; en una torre con más VRAM sobra sitio.
|
|
||||||
|
|
||||||
## Instalación
|
## Instalación
|
||||||
|
|
||||||
Necesita, en la máquina: [Ollama](https://ollama.com), whisper.cpp (compilado
|
Necesita, en la máquina: [Ollama](https://ollama.com), whisper.cpp (compilado
|
||||||
|
|
@ -109,21 +104,26 @@ preguntas de pentesting, la recuperación sube de **hit@1 48 % → 82 %** y
|
||||||
**hit@5 77 % → 95 %**. El método, el pipeline y esos números están en
|
**hit@5 77 % → 95 %**. El método, el pipeline y esos números están en
|
||||||
[docs/rag.md](docs/rag.md).
|
[docs/rag.md](docs/rag.md).
|
||||||
|
|
||||||
## Órdenes con root
|
## Órdenes con root — importante
|
||||||
|
|
||||||
Algunas acciones necesitan `sudo`. El manejo es explícito y con límites, no un
|
Algunas órdenes que gestionan el sistema (instalar un paquete, reiniciar un
|
||||||
"root para siempre":
|
servicio, montar un disco) necesitan `sudo`. Conviene saber cómo se maneja,
|
||||||
|
porque es donde un asistente de voz puede hacer daño:
|
||||||
|
|
||||||
- La contraseña **nunca se escribe en disco**. Vive en memoria del proceso solo
|
- **No pide la contraseña al arrancar.** Molestar de entrada echa para atrás, y
|
||||||
si activas el permiso de sesión, y se pasa a `sudo -S` por la entrada estándar.
|
la mayoría de lo que se le pide no necesita root. Arranca y funciona sin nada.
|
||||||
- El permiso de sesión **caduca a los 15 minutos** de la última orden, y también
|
- **Se pide solo cuando hace falta, y una vez.** La primera vez que una orden
|
||||||
al cerrar el panel.
|
necesita `sudo`, aparece un diálogo pidiendo tu contraseña. La tecleas, la
|
||||||
- **Cada** orden con root muestra su comando exacto en un diálogo antes de
|
repites, y a partir de ahí el permiso queda abierto para la sesión.
|
||||||
ejecutarlo. El permiso de sesión solo ahorra re-teclear la contraseña; nunca
|
- **Caduca a los 15 minutos** de inactividad, y al cerrar el panel. No es un
|
||||||
salta el ver y aprobar. **La voz no puede autoaprobar root.**
|
"root para siempre".
|
||||||
|
- La contraseña **nunca se escribe en disco**: vive solo en la memoria del
|
||||||
|
proceso mientras dura la sesión, y se pasa a `sudo -S` por la entrada estándar.
|
||||||
|
- **La voz no puede autoaprobar root.** El permiso de sesión ahorra re-teclear
|
||||||
|
la contraseña, nunca salta el pedirla la primera vez.
|
||||||
|
|
||||||
Con el candado echado, el asistente no obedece órdenes que toquen el sistema:
|
Si prefieres el modo seguro (bloquear el panel y pedir la contraseña al abrir,
|
||||||
las pide desbloquear primero.
|
para que nadie que pase por delante dé órdenes), arranca con `--candado`.
|
||||||
|
|
||||||
## Privacidad
|
## Privacidad
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@
|
||||||
# recompilar el flatpak por cada ajuste. Aqui se edita y ya esta, igual que los
|
# recompilar el flatpak por cada ajuste. Aqui se edita y ya esta, igual que los
|
||||||
# envoltorios de voz.
|
# envoltorios de voz.
|
||||||
#
|
#
|
||||||
# Requiere X11 (en Wayland no hay forma de mover ventanas ajenas) y xdotool.
|
# Motor X11 (xdotool). En Wayland, mover ventanas AJENAS depende del compositor
|
||||||
|
# —cada uno tiene su via (swaymsg, hyprctl...) o directamente lo prohibe por su
|
||||||
|
# modelo de seguridad (GNOME/KDE), y los tiling ni tienen "mitad izquierda"—, asi
|
||||||
|
# que no hay una via universal. Aqui se detecta el entorno y se avisa claro; el
|
||||||
|
# resto de JARVIS funciona igual, solo estas ordenes de ventanas quedan fuera en
|
||||||
|
# Wayland. Soporte por compositor: pendiente (ver el roadmap del README).
|
||||||
#
|
#
|
||||||
# ventanas.sh pantallas lista las pantallas
|
# ventanas.sh pantallas lista las pantallas
|
||||||
# ventanas.sh listar lista las ventanas movibles
|
# ventanas.sh listar lista las ventanas movibles
|
||||||
|
|
@ -26,8 +31,21 @@ set -uo pipefail
|
||||||
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
|
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
|
||||||
ALIAS_FICHERO=${JARVIS_ALIAS_PANTALLAS:-$HOME/.config/jarvis-pantallas.json}
|
ALIAS_FICHERO=${JARVIS_ALIAS_PANTALLAS:-$HOME/.config/jarvis-pantallas.json}
|
||||||
|
|
||||||
hay_x11() { [ -n "${DISPLAY:-}" ] && command -v xdotool >/dev/null; }
|
hay_x11() { [ -n "${DISPLAY:-}" ] && command -v xdotool >/dev/null 2>&1; }
|
||||||
hay_x11 || { echo "no hay X11 o falta xdotool"; exit 1; }
|
if ! hay_x11; then
|
||||||
|
# Diagnostico segun el entorno, no un "no X11" seco.
|
||||||
|
if [ "${XDG_SESSION_TYPE:-}" = "wayland" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
|
||||||
|
echo "las acciones de ventanas usan xdotool (X11) y estas en Wayland" \
|
||||||
|
"(${XDG_CURRENT_DESKTOP:-tu compositor}). En Wayland mover ventanas" \
|
||||||
|
"ajenas depende del compositor; por ahora estas ordenes quedan fuera." \
|
||||||
|
"El resto de JARVIS funciona igual."
|
||||||
|
elif [ -n "${DISPLAY:-}" ]; then
|
||||||
|
echo "estas en X11 pero falta xdotool. Instalalo con tu gestor de paquetes."
|
||||||
|
else
|
||||||
|
echo "no hay sesion grafica (ni DISPLAY ni WAYLAND_DISPLAY)."
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------- pantallas
|
# ---------------------------------------------------------------- pantallas
|
||||||
|
|
||||||
|
|
|
||||||
BIN
docs/panel.png
BIN
docs/panel.png
Binary file not shown.
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 222 KiB |
39
install.sh
39
install.sh
|
|
@ -73,7 +73,38 @@ else
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# ── 4. Siguiente paso ───────────────────────────────────────────────────────
|
# ── 4. Lanzador de escritorio (icono en el menu de aplicaciones) ────────────
|
||||||
echo "Hecho. Para arrancar:"
|
echo "4. Lanzador de escritorio"
|
||||||
echo " ./nucleo/saber/indexa.py # (opcional) indexa tus apuntes para el RAG"
|
APPS="$HOME/.local/share/applications"
|
||||||
echo " ./nucleo/arranca.sh # el asistente y el panel en 127.0.0.1"
|
ICONO="$AQUI/jarvis-icon.png"
|
||||||
|
[ -f "$ICONO" ] || ICONO="$AQUI/jarvis-icon.svg"
|
||||||
|
mkdir -p "$APPS"
|
||||||
|
cat > "$APPS/jarvis.desktop" <<DESKTOP
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=JARVIS
|
||||||
|
GenericName=Asistente de voz local
|
||||||
|
Comment=Asistente de voz 100% local
|
||||||
|
Exec=$AQUI/nucleo/arranca.sh
|
||||||
|
Icon=$ICONO
|
||||||
|
Terminal=false
|
||||||
|
Categories=Utility;Accessibility;
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass=jarvis-nucleo
|
||||||
|
Keywords=jarvis;asistente;voz;
|
||||||
|
Actions=detener;
|
||||||
|
|
||||||
|
[Desktop Action detener]
|
||||||
|
Name=Detener JARVIS
|
||||||
|
Exec=$AQUI/nucleo/arranca.sh --stop
|
||||||
|
DESKTOP
|
||||||
|
chmod +x "$APPS/jarvis.desktop" 2>/dev/null
|
||||||
|
command -v update-desktop-database >/dev/null && update-desktop-database "$APPS" 2>/dev/null
|
||||||
|
ok "creado $APPS/jarvis.desktop — busca «JARVIS» en el menu de aplicaciones"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ── 5. Siguiente paso ───────────────────────────────────────────────────────
|
||||||
|
echo "Hecho. Ya puedes:"
|
||||||
|
echo " · buscar JARVIS en el menu de aplicaciones y pulsar el icono, o"
|
||||||
|
echo " · ./nucleo/arranca.sh # arrancar a mano (panel en 127.0.0.1)"
|
||||||
|
echo " · ./nucleo/saber/indexa.py # (opcional) indexar tus apuntes para el RAG"
|
||||||
|
|
|
||||||
BIN
jarvis-icon.png
Normal file
BIN
jarvis-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
|
|
@ -246,6 +246,11 @@ class Panel:
|
||||||
# (accion, para_que, hasta_cuando). para_que es "argumento" o "confirmar".
|
# (accion, para_que, hasta_cuando). para_que es "argumento" o "confirmar".
|
||||||
self.pendiente = None
|
self.pendiente = None
|
||||||
|
|
||||||
|
# Root a demanda: las manos avisan aqui cuando una accion necesita sudo
|
||||||
|
# y no hay sesion. En vez de pedir la contraseña al arrancar, se pide
|
||||||
|
# solo entonces, una vez, y se cachea. Ver _pide_root.
|
||||||
|
self.manos.al_pedir_root = self._pide_root
|
||||||
|
|
||||||
# -------------------------------------------------------------- empujar
|
# -------------------------------------------------------------- empujar
|
||||||
|
|
||||||
def arranca(self):
|
def arranca(self):
|
||||||
|
|
@ -528,6 +533,22 @@ class Panel:
|
||||||
self.tareas.lanza(accion, arg)
|
self.tareas.lanza(accion, arg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _pide_root(self, comando: str = ""):
|
||||||
|
"""Muestra el dialogo de contraseña porque una accion necesita root.
|
||||||
|
|
||||||
|
No es el candado del arranque —eso pedia la contraseña de primeras y
|
||||||
|
echaba para atras—: esto es a demanda, la PRIMERA vez que hace falta
|
||||||
|
sudo en la sesion. La contraseña que se teclee la valida y la cachea
|
||||||
|
_intenta_desbloquear (por el mismo canal "desbloquear"), asi que a
|
||||||
|
partir de ahi el root ya esta abierto y no se vuelve a pedir hasta que
|
||||||
|
caduque. Corre desde el hilo de las manos, asi que solo manda al HUD.
|
||||||
|
"""
|
||||||
|
que = (comando or "").strip()[:60]
|
||||||
|
self.cara.manda("bloqueo", {
|
||||||
|
"mensaje": f"Contraseña para: {que}" if que
|
||||||
|
else "Contraseña de administrador",
|
||||||
|
"root": True})
|
||||||
|
|
||||||
def _intenta_desbloquear(self, clave: str):
|
def _intenta_desbloquear(self, clave: str):
|
||||||
if not clave:
|
if not clave:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,10 @@ def escucha(boca: Boca, modelo="small", fuente=None, callado=False,
|
||||||
def panel(boca: Boca, cerebro=None, escuchando=False, modelo="small",
|
def panel(boca: Boca, cerebro=None, escuchando=False, modelo="small",
|
||||||
fuente=None, abre=True, centinela=None, sin_candado=False) -> int:
|
fuente=None, abre=True, centinela=None, sin_candado=False) -> int:
|
||||||
"""Levanta el panel y, si se pide, ademas escucha el microfono."""
|
"""Levanta el panel y, si se pide, ademas escucha el microfono."""
|
||||||
manos = Manos(catalogo())
|
# Un SOLO Manos, compartido con el cerebro: asi hay una unica sesion de root
|
||||||
|
# y un unico sitio donde pedir la contraseña. Antes el cerebro tenia su Manos
|
||||||
|
# y el panel otro, con roots separados: desbloquear en uno no abria el otro.
|
||||||
|
manos = cerebro.manos if (cerebro and getattr(cerebro, "manos", None)) else Manos(catalogo())
|
||||||
cara = Cara()
|
cara = Cara()
|
||||||
p = Panel(cara, boca, manos, cerebro, candado=not sin_candado,
|
p = Panel(cara, boca, manos, cerebro, candado=not sin_candado,
|
||||||
centinela=centinela)
|
centinela=centinela)
|
||||||
|
|
@ -273,8 +276,10 @@ def main() -> int:
|
||||||
help="levantar el panel pero no abrir el navegador")
|
help="levantar el panel pero no abrir el navegador")
|
||||||
p.add_argument("--palabra", action="store_true",
|
p.add_argument("--palabra", action="store_true",
|
||||||
help=f"exigir que le llamen «{NOMBRE}» antes de cada orden")
|
help=f"exigir que le llamen «{NOMBRE}» antes de cada orden")
|
||||||
p.add_argument("--sin-candado", action="store_true",
|
p.add_argument("--candado", action="store_true",
|
||||||
help="no pedir la contraseña al abrir (solo para probar)")
|
help="pedir la contraseña al abrir y bloquear el panel. Por "
|
||||||
|
"defecto NO: no molesta al arrancar, y el root se pide "
|
||||||
|
"solo la primera vez que una accion lo necesita")
|
||||||
a = p.parse_args()
|
a = p.parse_args()
|
||||||
|
|
||||||
boca = Boca(a.voz or "davefx")
|
boca = Boca(a.voz or "davefx")
|
||||||
|
|
@ -322,7 +327,7 @@ def main() -> int:
|
||||||
if a.panel:
|
if a.panel:
|
||||||
return panel(boca, cerebro, a.escucha, a.modelo, a.fuente,
|
return panel(boca, cerebro, a.escucha, a.modelo, a.fuente,
|
||||||
abre=not a.sin_abrir, centinela=centinela,
|
abre=not a.sin_abrir, centinela=centinela,
|
||||||
sin_candado=a.sin_candado)
|
sin_candado=not a.candado)
|
||||||
|
|
||||||
if a.escucha:
|
if a.escucha:
|
||||||
return escucha(boca, a.modelo, a.fuente, a.callado, cerebro,
|
return escucha(boca, a.modelo, a.fuente, a.callado, cerebro,
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,14 @@ class Manos:
|
||||||
def __init__(self, catalogo=None, al_ejecutar=None):
|
def __init__(self, catalogo=None, al_ejecutar=None):
|
||||||
self.catalogo = catalogo or Catalogo([PROPIO, CATALOGO])
|
self.catalogo = catalogo or Catalogo([PROPIO, CATALOGO])
|
||||||
self.al_ejecutar = al_ejecutar
|
self.al_ejecutar = al_ejecutar
|
||||||
# Root para la sesion: cuando el usuario desbloquea el candado con su
|
# Root para la sesion: la primera vez que una accion necesita sudo se
|
||||||
# contraseña, esa misma queda aqui y el cerebro puede hacer sudo hasta
|
# pide la contraseña, y queda aqui hasta que caduque (15 min). No se
|
||||||
# que caduque. Sin desbloquear, no hay root: el candado es la puerta.
|
# pide al arrancar: solo cuando de verdad hace falta.
|
||||||
from manos.sudo_root import GestorRoot
|
from manos.sudo_root import GestorRoot
|
||||||
self.root = GestorRoot([])
|
self.root = GestorRoot([])
|
||||||
|
# El panel conecta aqui como pedir la contraseña de root cuando haga
|
||||||
|
# falta (muestra el dialogo). Sin panel (modo terminal) se queda en None.
|
||||||
|
self.al_pedir_root = None
|
||||||
|
|
||||||
# ------------------------------------------------------------- catalogo
|
# ------------------------------------------------------------- catalogo
|
||||||
|
|
||||||
|
|
@ -93,8 +96,15 @@ class Manos:
|
||||||
# hay contraseña: el root queda cerrado hasta que el usuario desbloquee.
|
# hay contraseña: el root queda cerrado hasta que el usuario desbloquee.
|
||||||
if necesita_root(comando):
|
if necesita_root(comando):
|
||||||
if not self.root.sesion_activa():
|
if not self.root.sesion_activa():
|
||||||
return ("eso necesita root y el candado no esta abierto. "
|
# No hay root todavia: se pide la contraseña AHORA (una vez), no
|
||||||
"Desbloquee con su contraseña y lo repito.")
|
# al arrancar. El panel muestra el dialogo; el usuario la teclea
|
||||||
|
# y repite la orden, que ya correra con la sesion abierta.
|
||||||
|
if self.al_pedir_root:
|
||||||
|
self.al_pedir_root(comando)
|
||||||
|
return ("Esto necesita permiso de administrador, señor. "
|
||||||
|
"Introduzca su contraseña y repitamelo.")
|
||||||
|
return ("eso necesita permiso de administrador y no hay panel "
|
||||||
|
"para pedir la contraseña.")
|
||||||
try:
|
try:
|
||||||
r = self.root.ejecuta(comando, timeout=60)
|
r = self.root.ejecuta(comando, timeout=60)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue