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:
