05-sep: [infra] configurar Raspberry Pi y documentar flota
This commit is contained in:
@@ -83,6 +83,7 @@ Start-Process powershell -WindowStyle Hidden -ArgumentList '-Command', 'python C
|
|||||||
| `consumo` | Abrir https://opencode.ai/auth, esperar captura del panel Go y analizarla con image-vision |
|
| `consumo` | Abrir https://opencode.ai/auth, esperar captura del panel Go y analizarla con image-vision |
|
||||||
| `memoria` | Protocolo completo: git pull, actualizar ficheros de memoria (MEMORIA_ABSOLUTA, ESTADO_DIARIO, AGENTS, DOCUMENTACION, README), Engram local + cloud, Obsidian sync, **trello-sync**, git push |
|
| `memoria` | Protocolo completo: git pull, actualizar ficheros de memoria (MEMORIA_ABSOLUTA, ESTADO_DIARIO, AGENTS, DOCUMENTACION, README), Engram local + cloud, Obsidian sync, **trello-sync**, git push |
|
||||||
| `recordatorios` | Consultar API recordatorios en VPS: GET /todo (calendarios + recordatorios webhook) |
|
| `recordatorios` | Consultar API recordatorios en VPS: GET /todo (calendarios + recordatorios webhook) |
|
||||||
|
| `raspberry` | Diagnosticar y mantener las Raspberry Pi del laboratorio |
|
||||||
| `idd-procesar` o `procesar IDD` | Fetch manual de mensajes pendientes del canal IDD Telegram via Bot API, guardarlos en Google Drive y clasificarlos localmente |
|
| `idd-procesar` o `procesar IDD` | Fetch manual de mensajes pendientes del canal IDD Telegram via Bot API, guardarlos en Google Drive y clasificarlos localmente |
|
||||||
| `trello-sync` | Sincronizar pendientes con Trello: local→Trello (crea/actualiza cards) + Trello→local (elimina tareas archivadas en Trello de la base de conocimiento) |
|
| `trello-sync` | Sincronizar pendientes con Trello: local→Trello (crea/actualiza cards) + Trello→local (elimina tareas archivadas en Trello de la base de conocimiento) |
|
||||||
| `pelicula <nombre>` | Buscar pelicula en el videoclub y descargar a NAS |
|
| `pelicula <nombre>` | Buscar pelicula en el videoclub y descargar a NAS |
|
||||||
|
|||||||
80
docs/raspberry_pi.md
Normal file
80
docs/raspberry_pi.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Raspberry Pi del laboratorio
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Centralizar la configuracion, el acceso y el mantenimiento de una flota de Raspberry Pi. La primera unidad es `minipc`; el mismo procedimiento debe servir para las cinco unidades previstas.
|
||||||
|
|
||||||
|
## Inventario actual
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|---|---|
|
||||||
|
| Hostname | `minipc` |
|
||||||
|
| Sistema | Raspbian GNU/Linux 13 (trixie) |
|
||||||
|
| Ethernet | `192.168.50.187` |
|
||||||
|
| Wi-Fi | `192.168.50.251` |
|
||||||
|
| Usuario operativo | `pi` |
|
||||||
|
| Codigo | `/home/pi/minipc/` |
|
||||||
|
| Servicio bot | `pi-bot.service` |
|
||||||
|
| Servicio tunel | `autossh-tunel.service` |
|
||||||
|
| Zona horaria | `Europe/Madrid` |
|
||||||
|
| Teclado | Espanol (`es`) |
|
||||||
|
|
||||||
|
No se documentan contrasenas, tokens ni claves privadas en este fichero.
|
||||||
|
|
||||||
|
## Comando unico de diagnostico
|
||||||
|
|
||||||
|
En la Raspberry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash /home/pi/minipc/vigilancia/raspberry_info.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
El comando muestra identidad, red, teclado, locale, hora, servicios y recursos sin revelar secretos.
|
||||||
|
|
||||||
|
Desde el PC, pasando por el VPS:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ssh -i C:\Users\juanm\Documents\GitHub\contabo root@185.187.169.109 "ssh -i /root/.ssh/pi_deploy -p 2222 pi@127.0.0.1 'bash /home/pi/minipc/vigilancia/raspberry_info.sh'"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuracion aplicada en minipc
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo raspi-config nonint do_configure_keyboard es
|
||||||
|
sudo localectl set-x11-keymap es pc105
|
||||||
|
sudo locale-gen es_ES.UTF-8
|
||||||
|
sudo localectl set-locale LANG=es_ES.UTF-8
|
||||||
|
sudo timedatectl set-timezone Europe/Madrid
|
||||||
|
sudo timedatectl set-ntp true
|
||||||
|
```
|
||||||
|
|
||||||
|
El cambio de idioma puede requerir cerrar la sesion y volver a entrar. El teclado de consola queda definido en `/etc/default/keyboard` con `XKBLAYOUT="es"`.
|
||||||
|
|
||||||
|
## Conectividad
|
||||||
|
|
||||||
|
- Ethernet es la ruta preferida y usa la IP `192.168.50.187`.
|
||||||
|
- Wi-Fi queda disponible como respaldo en `192.168.50.251`.
|
||||||
|
- El tunel inverso publica SSH de la Pi en el VPS como `127.0.0.1:2222`.
|
||||||
|
- El acceso remoto usa la clave privada del VPS configurada para el tunel; nunca se copia ni documenta aqui.
|
||||||
|
- Si la IP cambia, consultar `hostname -I` en la Pi y actualizar el inventario.
|
||||||
|
|
||||||
|
## Despliegue
|
||||||
|
|
||||||
|
La Pi no tiene Git operativo para este flujo. El despliegue se hace por el VPS y el tunel inverso:
|
||||||
|
|
||||||
|
1. Copiar el fichero al VPS.
|
||||||
|
2. Copiarlo por SSH a `pi@127.0.0.1:2222`.
|
||||||
|
3. Validar con `python3 -m py_compile` si es Python.
|
||||||
|
4. Reiniciar el servicio correspondiente.
|
||||||
|
5. Verificar `systemctl is-active` y los logs.
|
||||||
|
|
||||||
|
## Escalado a cinco Raspberry
|
||||||
|
|
||||||
|
Cada unidad debe tener un registro con hostname, MAC, IP Ethernet, IP Wi-Fi, servicios, funcion y ubicacion. Se recomienda:
|
||||||
|
|
||||||
|
- Reservas DHCP por MAC para evitar cambios de IP.
|
||||||
|
- Ethernet como red principal.
|
||||||
|
- Un hostname unico por unidad: `minipc-01`, `minipc-02`, etc.
|
||||||
|
- Un servicio `autossh-tunel` por unidad con un puerto de tunel distinto.
|
||||||
|
- Una clave SSH de despliegue independiente por unidad.
|
||||||
|
- Un script de diagnostico identico en todas las unidades.
|
||||||
35
documentacion/bitacora-dashboard-telegram-2026-09-05.md
Normal file
35
documentacion/bitacora-dashboard-telegram-2026-09-05.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Bitacora: Dashboard de descargas en Telegram
|
||||||
|
|
||||||
|
**Fecha:** 2026-09-05 · **Equipo:** sobremesa · **Agentes:** OpenCode
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Registro en tiempo real
|
||||||
|
|
||||||
|
### [14:35] Diseño inicial
|
||||||
|
- **Que:** Crear una vista de descargas dentro de Telegram.
|
||||||
|
- **Como:** Reutilizar la consulta existente a Download Station y editar un unico mensaje con botones inline.
|
||||||
|
- **Resultado:** OK
|
||||||
|
- **Decisiones:** No se crea una web externa ni una Mini App en esta primera version.
|
||||||
|
- **Gotchas/Lecciones:** El bot usa directamente la Bot API mediante `requests` y actualmente solo procesa mensajes normales.
|
||||||
|
|
||||||
|
### [14:42] Implementacion
|
||||||
|
- **Que:** Añadir panel editable, botones inline, callbacks y refresco automatico.
|
||||||
|
- **Como:** `pi_bot.py` mantiene un mensaje por chat y lo actualiza cada 20 segundos; `nas_dl.py` genera la barra Unicode usando el porcentaje de piezas descargadas.
|
||||||
|
- **Resultado:** OK
|
||||||
|
- **Decisiones:** `descargas` y `descargar` sin argumento abren el panel; `descargar <enlace>` mantiene la funcion de añadir tareas.
|
||||||
|
- **Gotchas/Lecciones:** Telegram necesita recibir y responder `callback_query` para que los botones funcionen correctamente.
|
||||||
|
|
||||||
|
### [14:47] Verificacion local
|
||||||
|
- **Que:** Comprobar sintaxis, barra de progreso y callbacks sin llamar a Telegram.
|
||||||
|
- **Como:** `py_compile`, prueba de extremos de `progress_bar` y callbacks simulados.
|
||||||
|
- **Resultado:** OK
|
||||||
|
- **Decisiones:** No se enviaron mensajes reales ni se modificaron tareas del NAS durante la prueba.
|
||||||
|
- **Gotchas/Lecciones:** La Pi no acepta conexiones SSH desde este equipo en `192.168.50.187:22`; el despliegue queda pendiente.
|
||||||
|
|
||||||
|
### [15:43] Configuracion Raspberry Pi y flota
|
||||||
|
- **Que:** Recuperar el acceso por clave, configurar teclado/locale espanoles, zona horaria y NTP, y centralizar el mantenimiento de Raspberry Pi.
|
||||||
|
- **Como:** Se uso el salto PC -> VPS -> tunel inverso -> Pi; se aplico `raspi-config` para teclado, `localectl` para locale/X11 y `timedatectl` para hora.
|
||||||
|
- **Resultado:** OK
|
||||||
|
- **Decisiones:** Ethernet queda como ruta preferida, Wi-Fi como respaldo; se crea `raspberry_info.sh` como comando unico de diagnostico sin secretos.
|
||||||
|
- **Gotchas/Lecciones:** El servicio correcto es `autossh-tunel.service`; la Pi tiene Raspbian 13 y dos IPs activas (`192.168.50.187` y `192.168.50.251`).
|
||||||
@@ -149,6 +149,13 @@ def human_size(n):
|
|||||||
return f"{n:.1f}PB"
|
return f"{n:.1f}PB"
|
||||||
|
|
||||||
|
|
||||||
|
def progress_bar(percent, width=12):
|
||||||
|
"""Genera una barra de progreso compacta para mensajes de Telegram."""
|
||||||
|
percent = max(0.0, min(100.0, float(percent or 0)))
|
||||||
|
filled = int(round(percent * width / 100))
|
||||||
|
return "█" * filled + "░" * (width - filled)
|
||||||
|
|
||||||
|
|
||||||
def listar_descargas(hablar_solo_activas=True, session=None):
|
def listar_descargas(hablar_solo_activas=True, session=None):
|
||||||
"""Devuelve (lineas_texto, activas). Lista las tareas de Download Station
|
"""Devuelve (lineas_texto, activas). Lista las tareas de Download Station
|
||||||
con estado, porcentaje de progreso, tamaño descargado y velocidad.
|
con estado, porcentaje de progreso, tamaño descargado y velocidad.
|
||||||
@@ -181,7 +188,7 @@ def listar_descargas(hablar_solo_activas=True, session=None):
|
|||||||
size = transfer.get("size_downloaded") or 0
|
size = transfer.get("size_downloaded") or 0
|
||||||
speed = transfer.get("speed_download") or 0
|
speed = transfer.get("speed_download") or 0
|
||||||
if estado == "downloading":
|
if estado == "downloading":
|
||||||
activas.append(f"▶️ <b>{pct:.1f}%</b> {human_size(size)} ⬇️{speed // 1024} KB/s\n"
|
activas.append(f"▶️ <b>{progress_bar(pct)} {pct:.1f}%</b> {human_size(size)} ⬇️{speed // 1024} KB/s\n"
|
||||||
f" {titulo}")
|
f" {titulo}")
|
||||||
else:
|
else:
|
||||||
resto.append((estado, pct, size, titulo))
|
resto.append((estado, pct, size, titulo))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import json
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ ultima_titulos = {} # chat_id -> {'query':..., 'results':[...]}
|
|||||||
ultimos_torrents = {} # chat_id -> {'titulo':..., 'torrents':[...]}
|
ultimos_torrents = {} # chat_id -> {'titulo':..., 'torrents':[...]}
|
||||||
flujo_genero = {} # chat_id -> 'esperando_tipo' | 'esperando_genero' | None
|
flujo_genero = {} # chat_id -> 'esperando_tipo' | 'esperando_genero' | None
|
||||||
flujo_genero_tipo = {} # chat_id -> 'movie' | 'series' (tipo ya elegido)
|
flujo_genero_tipo = {} # chat_id -> 'movie' | 'series' (tipo ya elegido)
|
||||||
|
panel_descargas = {} # chat_id -> estado del panel editable
|
||||||
|
|
||||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||||
LOG = os.path.join(BASE, "bot.log")
|
LOG = os.path.join(BASE, "bot.log")
|
||||||
@@ -100,6 +102,148 @@ def enviar(texto, chat_id):
|
|||||||
return ultimo_id
|
return ultimo_id
|
||||||
|
|
||||||
|
|
||||||
|
def teclado_descargas():
|
||||||
|
return {
|
||||||
|
"inline_keyboard": [
|
||||||
|
[
|
||||||
|
{"text": "🔄 Actualizar", "callback_data": "dl:refresh"},
|
||||||
|
{"text": "▶️ Activas", "callback_data": "dl:active"},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"text": "📁 Todas", "callback_data": "dl:all"},
|
||||||
|
{"text": "✖️ Cerrar", "callback_data": "dl:close"},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def enviar_panel_descargas(texto, chat_id):
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
f"https://api.telegram.org/bot{TOKEN}/sendMessage",
|
||||||
|
data={
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"text": texto,
|
||||||
|
"parse_mode": "HTML",
|
||||||
|
"reply_markup": json.dumps(teclado_descargas()),
|
||||||
|
},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
d = r.json()
|
||||||
|
if d.get("ok"):
|
||||||
|
return d["result"].get("message_id")
|
||||||
|
log(f"Telegram panel send error: {d.get('description', d)}")
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Telegram panel send exception: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def editar_panel_descargas(chat_id, message_id, texto):
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
f"https://api.telegram.org/bot{TOKEN}/editMessageText",
|
||||||
|
data={
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"message_id": message_id,
|
||||||
|
"text": texto,
|
||||||
|
"parse_mode": "HTML",
|
||||||
|
"reply_markup": json.dumps(teclado_descargas()),
|
||||||
|
},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
d = r.json()
|
||||||
|
if not d.get("ok") and "not modified" not in d.get("description", "").lower():
|
||||||
|
log(f"Telegram panel edit error: {d.get('description', d)}")
|
||||||
|
return d.get("ok", False)
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Telegram panel edit exception: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def responder_callback(callback_id):
|
||||||
|
try:
|
||||||
|
requests.post(
|
||||||
|
f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
|
||||||
|
data={"callback_query_id": callback_id},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Telegram callback error: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def texto_panel_descargas(modo="active"):
|
||||||
|
import nas_dl
|
||||||
|
lineas, activas = nas_dl.listar_descargas(
|
||||||
|
hablar_solo_activas=(modo == "active")
|
||||||
|
)
|
||||||
|
encabezado = "📥 <b>DESCARGAS DEL NAS</b>"
|
||||||
|
if modo == "active":
|
||||||
|
encabezado += " · ACTIVAS"
|
||||||
|
texto = encabezado + "\n\n" + "\n".join(lineas)
|
||||||
|
texto += f"\n\n🕒 Actualizado: <code>{time.strftime('%H:%M:%S')}</code>"
|
||||||
|
return texto, activas
|
||||||
|
|
||||||
|
|
||||||
|
def abrir_panel_descargas(chat_id):
|
||||||
|
anterior = panel_descargas.pop(chat_id, None)
|
||||||
|
if anterior:
|
||||||
|
anterior["stop"].set()
|
||||||
|
try:
|
||||||
|
texto, _activas = texto_panel_descargas()
|
||||||
|
message_id = enviar_panel_descargas(texto, chat_id)
|
||||||
|
if not message_id:
|
||||||
|
enviar("❌ No se pudo abrir el panel de descargas.", chat_id)
|
||||||
|
return
|
||||||
|
estado_panel = {
|
||||||
|
"message_id": message_id,
|
||||||
|
"modo": "active",
|
||||||
|
"stop": threading.Event(),
|
||||||
|
}
|
||||||
|
panel_descargas[chat_id] = estado_panel
|
||||||
|
threading.Thread(
|
||||||
|
target=monitor_panel_descargas,
|
||||||
|
args=(chat_id, estado_panel),
|
||||||
|
daemon=True,
|
||||||
|
).start()
|
||||||
|
except Exception as e:
|
||||||
|
log(f"panel descargas error: {e}")
|
||||||
|
enviar(f"❌ Error en el panel de descargas: {e}", chat_id)
|
||||||
|
|
||||||
|
|
||||||
|
def monitor_panel_descargas(chat_id, estado_panel):
|
||||||
|
while not estado_panel["stop"].wait(20):
|
||||||
|
try:
|
||||||
|
texto, activas = texto_panel_descargas(estado_panel["modo"])
|
||||||
|
editar_panel_descargas(chat_id, estado_panel["message_id"], texto)
|
||||||
|
if not activas:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log(f"monitor panel descargas error: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def procesar_callback(callback):
|
||||||
|
callback_id = callback.get("id")
|
||||||
|
mensaje = callback.get("message") or {}
|
||||||
|
chat_id = (mensaje.get("chat") or {}).get("id")
|
||||||
|
accion = (callback.get("data") or "").split(":", 1)[-1]
|
||||||
|
responder_callback(callback_id)
|
||||||
|
estado_panel = panel_descargas.get(chat_id)
|
||||||
|
if not estado_panel:
|
||||||
|
return
|
||||||
|
if accion == "close":
|
||||||
|
estado_panel["stop"].set()
|
||||||
|
panel_descargas.pop(chat_id, None)
|
||||||
|
borrar(chat_id, estado_panel["message_id"])
|
||||||
|
return
|
||||||
|
if accion in ("active", "all"):
|
||||||
|
estado_panel["modo"] = "active" if accion == "active" else "all"
|
||||||
|
try:
|
||||||
|
texto, _activas = texto_panel_descargas(estado_panel["modo"])
|
||||||
|
editar_panel_descargas(chat_id, estado_panel["message_id"], texto)
|
||||||
|
except Exception as e:
|
||||||
|
log(f"callback panel descargas error: {e}")
|
||||||
|
|
||||||
|
|
||||||
def borrar(chat_id, message_id):
|
def borrar(chat_id, message_id):
|
||||||
try:
|
try:
|
||||||
requests.post(
|
requests.post(
|
||||||
@@ -213,6 +357,8 @@ def procesar(texto, chat_id):
|
|||||||
else:
|
else:
|
||||||
ok = mandar_youtube(url)
|
ok = mandar_youtube(url)
|
||||||
enviar("🎬 YouTube lanzado en la TV ✅" if ok else "❌ No se pudo lanzar YouTube", chat_id)
|
enviar("🎬 YouTube lanzado en la TV ✅" if ok else "❌ No se pudo lanzar YouTube", chat_id)
|
||||||
|
elif t in ("descargas", "/descargas") or (comando == "descargar" and not argumento):
|
||||||
|
abrir_panel_descargas(chat_id)
|
||||||
elif comando == "descargar":
|
elif comando == "descargar":
|
||||||
try:
|
try:
|
||||||
import nas_dl
|
import nas_dl
|
||||||
@@ -220,11 +366,6 @@ def procesar(texto, chat_id):
|
|||||||
ok = nas_dl.add_torrent(argumento)
|
ok = nas_dl.add_torrent(argumento)
|
||||||
enviar("📥 Añadido a Download Station del NAS ✅" if ok
|
enviar("📥 Añadido a Download Station del NAS ✅" if ok
|
||||||
else "❌ No se pudo añadir al NAS. Revisa <code>bot.log</code>.", chat_id)
|
else "❌ No se pudo añadir al NAS. Revisa <code>bot.log</code>.", chat_id)
|
||||||
else:
|
|
||||||
# Incluye también pendientes, pausadas y terminadas para que
|
|
||||||
# el usuario pueda ver el estado real de cada torrent.
|
|
||||||
lineas, _act = nas_dl.listar_descargas(hablar_solo_activas=False)
|
|
||||||
enviar("\n".join(lineas), chat_id)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"descargar error: {e}")
|
log(f"descargar error: {e}")
|
||||||
enviar(f"❌ Error en Download Station: {e}", chat_id)
|
enviar(f"❌ Error en Download Station: {e}", chat_id)
|
||||||
@@ -269,8 +410,8 @@ def procesar(texto, chat_id):
|
|||||||
"<b>7.</b> <code>novedades</code> — últimas novedades del catálogo\n"
|
"<b>7.</b> <code>novedades</code> — últimas novedades del catálogo\n"
|
||||||
" ✏️ Ej: <code>/novedades</code>\n\n"
|
" ✏️ Ej: <code>/novedades</code>\n\n"
|
||||||
"━━━ 🖥️ NAS / DESCARGAS ━━━\n"
|
"━━━ 🖥️ NAS / DESCARGAS ━━━\n"
|
||||||
"<b>8.</b> <code>descargar</code> — torrents descargándose y su progreso\n"
|
"<b>8.</b> <code>descargas</code> — panel con progreso y botones\n"
|
||||||
" ✏️ Ej: <code>/descargar</code>\n"
|
" ✏️ Ej: <code>/descargas</code> o <code>/descargar</code>\n"
|
||||||
"<b>9.</b> <code>descargar <enlace></code> — añade a Download Station\n"
|
"<b>9.</b> <code>descargar <enlace></code> — añade a Download Station\n"
|
||||||
" ✏️ Ej: <code>/descargar magnet:?xt=urn:btih:...</code>\n\n"
|
" ✏️ Ej: <code>/descargar magnet:?xt=urn:btih:...</code>\n\n"
|
||||||
"━━━ 📺 TV ━━━\n"
|
"━━━ 📺 TV ━━━\n"
|
||||||
@@ -646,6 +787,9 @@ def main():
|
|||||||
offset = upd["update_id"] + 1
|
offset = upd["update_id"] + 1
|
||||||
guardar_offset(offset)
|
guardar_offset(offset)
|
||||||
log(f"UPDATE {upd.get('update_id')}")
|
log(f"UPDATE {upd.get('update_id')}")
|
||||||
|
if upd.get("callback_query"):
|
||||||
|
procesar_callback(upd["callback_query"])
|
||||||
|
continue
|
||||||
msg = upd.get("message") or upd.get("channel_post")
|
msg = upd.get("message") or upd.get("channel_post")
|
||||||
if not msg:
|
if not msg:
|
||||||
continue
|
continue
|
||||||
|
|||||||
36
vigilancia/raspberry_info.sh
Normal file
36
vigilancia/raspberry_info.sh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Diagnostico seguro de una Raspberry Pi del laboratorio.
|
||||||
|
# No muestra credenciales, tokens ni contenido de ficheros .env.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "== IDENTIDAD =="
|
||||||
|
hostname
|
||||||
|
cat /etc/os-release | sed -n 's/^\(PRETTY_NAME\)=/\1=/p'
|
||||||
|
|
||||||
|
echo "== RED =="
|
||||||
|
hostname -I
|
||||||
|
ip -br addr
|
||||||
|
ip route
|
||||||
|
|
||||||
|
echo "== TECLADO Y LOCALE =="
|
||||||
|
localectl status 2>&1
|
||||||
|
if [ -f /etc/default/keyboard ]; then
|
||||||
|
sed -n '/^XKBMODEL=/p;/^XKBLAYOUT=/p;/^XKBVARIANT=/p;/^XKBOPTIONS=/p' /etc/default/keyboard
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== HORA =="
|
||||||
|
timedatectl status 2>&1
|
||||||
|
|
||||||
|
echo "== SERVICIOS =="
|
||||||
|
for service in ssh pi-bot autossh-tunel tv-api; do
|
||||||
|
printf '%-16s' "$service"
|
||||||
|
systemctl is-active "$service" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== RECURSOS =="
|
||||||
|
uptime
|
||||||
|
free -h
|
||||||
|
df -h / | tail -n 1
|
||||||
|
if command -v vcgencmd >/dev/null 2>&1; then
|
||||||
|
vcgencmd measure_temp
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user