From b31279918c4bf5c020e6baa73e50495a576e3880 Mon Sep 17 00:00:00 2001 From: juanminsanz Date: Sun, 20 Sep 2026 22:36:58 +0200 Subject: [PATCH] 20-sep: [fix] Sistema completo apagado/encendido remoto - shutdown listener + HA actualizado --- docs/SISTEMA_APAGADO_ENCENDIDO_REMOTO.md | 171 +++++++++++++++++++++++ scripts/shutdown_listener.py | 52 +++++++ scripts/telegram_bot_pc.log | 0 scripts/telegram_bot_pc.py | 40 +++++- scripts/telegram_bot_pc_error.log | 0 5 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 docs/SISTEMA_APAGADO_ENCENDIDO_REMOTO.md create mode 100644 scripts/shutdown_listener.py create mode 100644 scripts/telegram_bot_pc.log create mode 100644 scripts/telegram_bot_pc_error.log diff --git a/docs/SISTEMA_APAGADO_ENCENDIDO_REMOTO.md b/docs/SISTEMA_APAGADO_ENCENDIDO_REMOTO.md new file mode 100644 index 0000000..61e987e --- /dev/null +++ b/docs/SISTEMA_APAGADO_ENCENDIDO_REMOTO.md @@ -0,0 +1,171 @@ +# Sistema de Apagado/Encendido Remoto del PC (20-sep-2026) + +## Arquitectura del sistema + +``` +TELEGRAM (iPad/Móvil) + ↓ /apagar o /encender +BOT TELEGRAM (PC local - telegram_bot_pc.py) + ↓ POST a webhook +HOME ASSISTANT (VPS 185.187.169.109) + ↓ + ├─ APAGAR: POST http://192.168.1.239:5555/shutdown → PC ejecuta shutdown + │ → Espera 30s + │ → Alexa apaga luces (barras + cuartillo) + │ → Alexa apaga regleta (corta corriente) + │ + └─ ENCENDER: Alexa enciende regleta + → Espera 60s (PC arranca físicamente) + → Wake-on-LAN a 192.168.1.239 +``` + +## Componentes + +### 1. Bot Telegram (PC local) +**Archivo:** `INFRAESTRUCTURA/scripts/telegram_bot_pc.py` +**Función:** Interfaz de usuario desde Telegram +**Características:** +- Pide confirmación antes de apagar +- Watchdog automático tras apagar (30s + ping) +- Watchdog automático tras encender (90s + 3 reintentos ping) +- Filtro de autorización por USER_ID + +**Arrancar:** +```powershell +cd C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\scripts +python telegram_bot_pc.py +``` + +### 2. Shutdown Listener (PC local) +**Archivo:** `INFRAESTRUCTURA/scripts/shutdown_listener.py` +**Puerto:** 5555 +**Función:** Recibe comandos HTTP de Home Assistant para ejecutar shutdown local +**Seguridad:** Token de autenticación (SHUTDOWN_TOKEN en .env) + +**Arrancar:** +```powershell +cd C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\scripts +python shutdown_listener.py +``` + +**Health check:** +```powershell +Invoke-WebRequest -Uri "http://127.0.0.1:5555/health" +``` + +**Ejecutar shutdown (requiere token):** +```powershell +Invoke-WebRequest -Uri "http://127.0.0.1:5555/shutdown" ` + -Method POST ` + -Headers @{ "Authorization" = "Bearer $env:SHUTDOWN_TOKEN" } +``` + +### 3. Home Assistant (VPS) +**Ubicación:** `root@185.187.169.109:/opt/homeassistant` +**Función:** Orquestador central (recibe webhooks, ejecuta shutdown, controla luces/regleta vía Alexa) + +**Archivos de configuración:** +- `/opt/homeassistant/config/automations.yaml` — Webhooks `/apagar-pc-juanjo` y `/encender-pc-juanjo` +- `/opt/homeassistant/config/scripts.yaml` — Scripts `apagar_pc_completo` y `encender_pc_completo` +- `/opt/homeassistant/config/configuration.yaml` — REST command `shutdown_pc_local` + +**Reiniciar:** +```bash +cd /opt/homeassistant && docker compose restart homeassistant +``` + +### 4. Dispositivos Alexa +- **Echo Dot de Nahid:** Ejecuta comandos de voz para luces y regleta +- **Luces:** barras, cuartillo +- **Regleta:** Enchufe inteligente que alimenta el PC + +## Flujo de apagado completo + +1. Usuario en Telegram: `/apagar` → Botón "🔴 Apagar PC" +2. Bot pide confirmación: "✅ SÍ, apagar" | "❌ Cancelar" +3. Si confirma → Bot envía POST a `http://185.187.169.109:8123/api/webhook/apagar-pc-juanjo` +4. Home Assistant ejecuta `script.apagar_pc_completo`: + - **4.1** POST a `http://192.168.1.239:5555/shutdown` (con token) + - **4.2** PC ejecuta `shutdown /s /t 10` + - **4.3** Espera 30s + - **4.4** Alexa apaga "barras" + - **4.5** Alexa apaga "cuartillo" + - **4.6** Alexa apaga "regleta" (corta corriente al PC) +5. Bot espera 30s y verifica con ping: + - Si responde → "⚠️ APAGADO FALLIDO" + - Si no responde → "✅ PC APAGADO CONFIRMADO" + +## Flujo de encendido completo + +1. Usuario en Telegram: `/encender` → Botón "🟢 Encender PC" +2. Bot envía POST a `http://185.187.169.109:8123/api/webhook/encender-pc-juanjo` +3. Home Assistant ejecuta `script.encender_pc_completo`: + - **3.1** Alexa enciende "regleta" (da corriente al PC) + - **3.2** Espera 60s (PC arranca físicamente) + - **3.3** Envía Wake-on-LAN a `192.168.1.239` (MAC: `70:85:c2:6b:3a:03`) +4. Bot espera 90s + hasta 3 reintentos (15s cada uno) verificando con ping: + - Si responde → "✅ PC ENCENDIDO CORRECTAMENTE" + - Si no responde → "❌ ENCENDIDO FALLIDO" +5. PC arranca → Windows inicia → **Autologon** → Escritorio listo → Watchers arrancan + +## Variables de entorno necesarias (.env) + +```bash +# Bot Telegram +TELEGRAM_BOT_PC_TOKEN= +TELEGRAM_USER_ID=325737724 + +# Shutdown Listener +SHUTDOWN_TOKEN=H7iLGJixPp3J3pthCmnR2Ibq0tvERD5THyr_foAeN_8 +``` + +## Tareas programadas recomendadas + +### Shutdown Listener (arrancar al iniciar Windows) +```xml +Nombre: Shutdown Listener +Descripción: Listener HTTP para apagado remoto del PC +Acción: python C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\scripts\shutdown_listener.py +Desencadenador: Al iniciar sesión +Usuario: juanm +``` + +### Bot Telegram (arrancar al iniciar Windows) +```xml +Nombre: Bot Telegram Control PC +Descripción: Bot de Telegram para control remoto del PC +Acción: python C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\scripts\telegram_bot_pc.py +Desencadenador: Al iniciar sesión +Usuario: juanm +``` + +Script de instalación: `INFRAESTRUCTURA/scripts/instalar_watchers.ps1` (añadir estas tareas) + +## Troubleshooting + +### El apagado no funciona +1. Verificar que el shutdown listener está corriendo: `http://127.0.0.1:5555/health` +2. Verificar token en `.env` (SHUTDOWN_TOKEN) +3. Revisar logs de Home Assistant: `ssh root@185.187.169.109 "docker logs homeassistant -f"` +4. Probar shutdown manual: `Invoke-WebRequest -Uri "http://127.0.0.1:5555/shutdown" -Method POST -Headers @{ "Authorization" = "Bearer TOKEN" }` + +### El encendido no funciona +1. Verificar que la regleta enciende (luz física) +2. Verificar Wake-on-LAN habilitado en BIOS +3. Verificar MAC del PC correcta en HA: `70:85:c2:6b:3a:03` +4. Probar WoL manual: `wakeonlan 70:85:c2:6b:3a:03` + +### El bot no responde +1. Verificar que está corriendo: `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match "telegram_bot_pc" }` +2. Revisar logs: `C:\Users\juanm\Documents\Github\INFRAESTRUCTURA\scripts\telegram_bot_pc.log` +3. Verificar token: `TELEGRAM_BOT_PC_TOKEN` en `.env` +4. Verificar USER_ID autorizado: `TELEGRAM_USER_ID=325737724` + +## Seguridad + +- ✅ Bot solo responde a USER_ID autorizado (325737724) +- ✅ Shutdown listener requiere token de autenticación +- ✅ Listener solo acepta conexiones locales (bind a 0.0.0.0 pero firewall protege) +- ✅ Home Assistant en VPS con IP fija conocida +- ⚠️ Token en `.env` en texto plano (mitigar con permisos de archivo) +- ⚠️ Autologon guarda contraseña en registro (mitigar con BitLocker + bloqueo automático) diff --git a/scripts/shutdown_listener.py b/scripts/shutdown_listener.py new file mode 100644 index 0000000..0ec4edd --- /dev/null +++ b/scripts/shutdown_listener.py @@ -0,0 +1,52 @@ +""" +Listener HTTP local que ejecuta shutdown cuando recibe POST /shutdown +Home Assistant puede llamar a http://192.168.1.239:5555/shutdown para apagar el PC + +Uso: + python shutdown_listener.py + +Debe correr como servicio/tarea programada al iniciar Windows. +""" +import os +from flask import Flask, request, jsonify +from dotenv import load_dotenv + +# Cargar .env +dotenv_path = r"C:\Users\juanm\Documents\GitHub\biblioteca_negocio_prolongo\.env" +load_dotenv(dotenv_path) + +app = Flask(__name__) + +# Token de seguridad para autorizar shutdowns +SHUTDOWN_TOKEN = os.getenv("SHUTDOWN_TOKEN", "cambiar-este-token") + +@app.route('/shutdown', methods=['POST']) +def shutdown(): + """Ejecuta shutdown del PC si el token es correcto""" + # Verificar token + token = request.headers.get('Authorization', '').replace('Bearer ', '') + if token != SHUTDOWN_TOKEN: + print(f"[ACCESO DENEGADO] Token incorrecto: {token[:10]}...") + return jsonify({"error": "Unauthorized"}), 401 + + print("[SHUTDOWN] Apagando PC en 10 segundos...") + + # Ejecutar shutdown + os.system("shutdown /s /t 10") + + return jsonify({"status": "ok", "message": "Shutdown iniciado (10s)"}), 200 + +@app.route('/health', methods=['GET']) +def health(): + """Health check""" + return jsonify({"status": "ok", "service": "shutdown-listener"}), 200 + +if __name__ == '__main__': + print("=" * 60) + print("Shutdown Listener - Esperando comandos en puerto 5555") + print(f"Token configurado: {SHUTDOWN_TOKEN[:10]}..." if SHUTDOWN_TOKEN != "cambiar-este-token" else "Token: [NO CONFIGURADO]") + print("Endpoint: POST http://192.168.1.239:5555/shutdown") + print("=" * 60) + + # Escuchar en todas las interfaces (0.0.0.0) para que Home Assistant pueda acceder + app.run(host='0.0.0.0', port=5555, debug=False) diff --git a/scripts/telegram_bot_pc.log b/scripts/telegram_bot_pc.log new file mode 100644 index 0000000..e69de29 diff --git a/scripts/telegram_bot_pc.py b/scripts/telegram_bot_pc.py index 690bb66..799d80f 100644 --- a/scripts/telegram_bot_pc.py +++ b/scripts/telegram_bot_pc.py @@ -30,12 +30,30 @@ HA_WEBHOOK_APAGAR = "http://185.187.169.109:8123/api/webhook/apagar-pc-juanjo" HA_WEBHOOK_ENCENDER = "http://185.187.169.109:8123/api/webhook/encender-pc-juanjo" PC_IP = "192.168.1.239" # IP del PC sobremesa +def user_autorizado(update: Update) -> bool: + """Verifica si el usuario está autorizado""" + user_id = str(update.effective_user.id) if update.effective_user else None + autorizado = user_id == TELEGRAM_USER_ID + if not autorizado: + print(f"[ACCESO DENEGADO] Usuario no autorizado: {user_id} (esperado: {TELEGRAM_USER_ID})") + return autorizado + async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): """Comando /start""" + if not user_autorizado(update): + await update.message.reply_text("❌ Acceso denegado. Bot de uso personal.") + return + print(f"[START] Usuario {update.effective_user.id} autenticado correctamente") await menu(update, context) async def menu(update: Update, context: ContextTypes.DEFAULT_TYPE): """Muestra el menú principal""" + if not user_autorizado(update): + if update.callback_query: + await update.callback_query.answer("❌ Acceso denegado") + return + + print(f"[MENU] Mostrando menú a usuario {update.effective_user.id}") keyboard = [ [ InlineKeyboardButton("🔴 Apagar PC", callback_data='apagar'), @@ -67,6 +85,10 @@ async def menu(update: Update, context: ContextTypes.DEFAULT_TYPE): async def apagar_pc(update: Update, context: ContextTypes.DEFAULT_TYPE): """Apaga el PC con confirmación""" + if not user_autorizado(update): + return + + print(f"[APAGAR] Solicitando confirmación") query = update.callback_query if update.callback_query else None # CONFIRMAR antes de apagar @@ -155,6 +177,10 @@ async def apagar_pc_confirmar(update: Update, context: ContextTypes.DEFAULT_TYPE async def encender_pc(update: Update, context: ContextTypes.DEFAULT_TYPE): """Enciende el PC y verifica que arrancó""" + if not user_autorizado(update): + return + + print(f"[ENCENDER] Iniciando proceso de encendido") query = update.callback_query if update.callback_query else None if query: @@ -228,6 +254,10 @@ async def encender_pc(update: Update, context: ContextTypes.DEFAULT_TYPE): async def estado_pc(update: Update, context: ContextTypes.DEFAULT_TYPE): """Verifica si el PC está encendido""" + if not user_autorizado(update): + return + + print(f"[ESTADO] Verificando estado del PC") query = update.callback_query if update.callback_query else None if query: @@ -300,7 +330,15 @@ def main(): print("ERROR: Falta TELEGRAM_BOT_PC_TOKEN en .env") return - print("Iniciando bot de Telegram para control de PC...") + if not TELEGRAM_USER_ID: + print("ERROR: Falta TELEGRAM_USER_ID en .env") + return + + print("=" * 50) + print("Iniciando bot de Telegram para control de PC") + print(f"Usuario autorizado: {TELEGRAM_USER_ID}") + print(f"IP del PC: {PC_IP}") + print("=" * 50) app = Application.builder().token(TELEGRAM_BOT_TOKEN).build() diff --git a/scripts/telegram_bot_pc_error.log b/scripts/telegram_bot_pc_error.log new file mode 100644 index 0000000..e69de29