Haciendo del Desarrollo y la Arquitectura Web, ciencia y pasión.

Notificaciones de cambio de IP via Telegram

Hoy vamos a hacer un pequeño articulo sobre un problema que tengo hace mucho tiempo. Resulta que aleatoriamente se va la luz donde tengo el servidor. Sí, le puse una SAI y bueno la verdad es que va bastante bien, el tiempo entre caidas, o como dicen los muy leídos, el MTBF (Mean Time Between Failures), tiempo medio entre fallos ha aumentado considerablemente. La cuestión es que a veces se iba la luz y ya no podía averiguar cual era la IP y era un trastorno.

La solucion era bien fácil, originalmente tenía un sistema que detectaba el cambio de IP y actualizaba directamente el DNS de mi registrador de dominios (no diré cual), hasta que decidió que para los clientes pequeños no había API :^(

Hace tiempo traté de automatizar esta comunicacion del cambio de IP a traves de WhatsApp, pero parece ser que su API solo está disponible para empresas, así que probé con Telegram y estupendamente. Vamos al lío.

Lo primero es ir a Telegram y crear un bot, entramos en en el movil o en la web y buscar un usuario que se llama @BotFather, que es oficial, podrás comprobar que tiene check.

Lo primero es enviar el comando /newbot, elige un nombre y un nombre de usuario para tu bot (que termine en _bot), y BotFather te dará un token que necesitas para programarlo. Luego, puedes usar este token para construir tu bot, nosotros lo haremos con python, y empezar a interactuar con él escribiendo /start en el chat de tu nuevo bot para probarlo.

Para empezar a comunicarnos con Telegram necesitamos un BOT_TOKEN y un chat_id. En el momento de crear el bot nos suministrará el token. Para poder obtener el chat id tenemos que ir a

https://api.telegram.org/bot<TU_BOT_TOKEN>/getUpdates

Podemos ir a traves del navegdor o hacerle un curl. Este nos devolverá un json con una estructura parecida a:

"chat":{"id":5940089577,"first_name":"daniel","type":"private"}

Una vez que ya disponemos de chat id y de BOT_TOKEN podemos empezar a escribir.

El codigo se apoyará en un servicio de obtencion de IP, como Amazon o ifconfig.io. Pondremos varios servicios para hacerlo más robusto, por si falla alguno de ellos, en concreto:

IP_SERVICES = [ 
       "https://api.ipify.org",
       "https://checkip.amazonaws.com",
       "https://ifconfig.me/ip",
       "https://ipinfo.io/ip"]


Puesto que el script se va a ejecutar via crontab para que haga una inspeccion del estado de la IP, no valdrán las variables de entorno, y tampoco cargará el .bashrc o .zshrc, asi que he optado por escribir las variables en un archivo .env y cargarlo con load_dotenv('path/.env'), acordarse de hacer pip install python-dotenv.

En el archivo .env tendriamos las variables:

TELEGRAM_CHAT_ID=XXXXXXXXx
TELEGRAM_BOT_TOKEN=YYYYYYYY:ZZZZZZZZZZZZZZZ-zzzzzz

finalmente editar el crontab con un crontab -e y añadir una entrada

*/10 * * * * /usr/bin/python3 /ruta/ipcheck.py

Opcionalmente se podria haber añadido las variables de entorno en la propia llamada

*/10 * * * * TELEGRAM_BOT_TOKEN=xxx TELEGRAM_CHAT_ID=yyy /usr/bin/python3 /ruta/ipcheck.py

Y finalmente elegir la granularidad del checkeo, como mi servicio no es muy critico, 10 minutos bastarán.

Ya tenemos preparado el entorno y disponemos de los token pertinentes, asi que añadimos el código

import requests
import os
import argparse
from datetime import datetime
from typing import Optional

# ======================
# CONFIGURACIóN
# ======================

from dotenv import load_dotenv
load_dotenv("/home/daniel/proyectos/ipcheck/.env")

BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")

IP_FILE = "last_ip.txt"

IP_SERVICES = [
    "https://api.ipify.org",
    "https://checkip.amazonaws.com",
    "https://ifconfig.me/ip",
    "https://ipinfo.io/ip",
]

# ======================
# FUNCIONES
# ======================
def obtener_ip_publica() -> str:
    for url in IP_SERVICES:
        try:
            r = requests.get(url, timeout=4)
            r.raise_for_status()
            ip = r.text.strip()
            if ip:
                return ip
        except Exception:
            continue

    raise RuntimeError("No se pudo obtener la IP pública desde ningún servicio")


def leer_ip_guardada() -> Optional[str]:
    if not os.path.exists(IP_FILE):
        return None
    with open(IP_FILE, "r") as f:
        return f.read().strip() or None


def guardar_ip(ip: str) -> None:
    with open(IP_FILE, "w") as f:
        f.write(ip)


def enviar_telegram(mensaje: str) -> None:
    if not BOT_TOKEN or not CHAT_ID:
        raise RuntimeError("Variables TELEGRAM_BOT_TOKEN o TELEGRAM_CHAT_ID no definidas")

    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    payload = {
        "chat_id": CHAT_ID,
        "text": mensaje
    }

    r = requests.post(url, json=payload, timeout=10)
    r.raise_for_status()


# ======================
# MAIN
# ======================
def main(force: bool) -> None:
    ip_actual = obtener_ip_publica()
    ip_anterior = leer_ip_guardada()

    if force or ip_actual != ip_anterior:
        guardar_ip(ip_actual)

        timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
        motivo = "FORZADO" if force else "CAMBIO DETECTADO"

        mensaje = (
            " IP pública\n"
            f" IP: {ip_actual}\n"
            f" Motivo: {motivo}\n"
            f" {timestamp}"
        )

        enviar_telegram(mensaje)


# ======================
# ENTRYPOINT
# ======================
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Monitor de IP pública con aviso por Telegram")
    parser.add_argument(
        "--force",
        action="store_true",
        help="Fuerza el envío de la IP aunque no haya cambiado"
    )

    args = parser.parse_args()
    main(force=args.force)

Adicionalmente el script permite una serie de paso de parametros por linea de comandos para hacer envío manual:

daniel@dockerserver  ~/proyectos/ipcheck  python3 ipchecker2.py --force

Y finalmente, si vamos a nuestra cuenta de Telegram veremos que hay una entrada nueva:

telegram entry