Anexo I: OpenClaw (Openbot) + Telegram (Abacombot) - De 0 a Avanzado

Asistente Sysadmin por chat con seguridad y sesiones aisladas

Author

Diego Saavedra

Published

February 2, 2026

Anexo I: OpenClaw (Openbot) + Telegram (Abacombot) - De 0 a Avanzado

Introduccion

Este mini-curso te guia para montar desde cero un bot en Telegram que actua como un Sysadmin: recibe instrucciones por chat y responde con diagnosticos (y, opcionalmente, acciones controladas) para facilitar actividades del curso.

La ruta recomendada usa OpenClaw (Gateway + canales + sesiones + skills) y su postura de seguridad (pairing, allowlists, auditoria, aislamiento de sesiones). Al final se incluye una ruta alternativa: un bot Python simple (sin OpenClaw).

El bot se llama por defecto Abacombot, pero el nombre es configurable.

Enfoque de seguridad: por defecto el bot trabaja en modo read-only. Las acciones que cambian el sistema (reiniciar servicios, aplicar updates, etc.) deben habilitarse de forma explicita con una lista blanca y reglas de sudo muy acotadas.

Ubuntu Server LTS recomendado: 22.04 o 24.04.


Objetivos

  • Crear un bot de Telegram (token) y conectarlo a un Gateway.
  • Implementar un bot Sysadmin con comandos del curso (CPU/RAM, disco, red, servicios, logs, Docker).
  • Asegurar el bot: token fuera del codigo, pairing/allowlists, mention gating en grupos, auditoria.
  • Desplegar como servicio (daemon) con logs y troubleshooting.
  • Extender a modo avanzado: skills propias + politicas de sesiones (aislamiento DM).

Arquitectura (vista rapida)

  1. Telegram: usuario envia un comando (ej: /status).
  2. Abacombot: valida chat permitido + rate limit.
  3. Runner: ejecuta un comando del sistema (solo allowlist) con timeout.
  4. Respuesta: devuelve salida resumida al chat.

Requisitos

En Telegram

  • Una cuenta de Telegram.
  • Token del bot creado con @BotFather.

En el servidor (Ubuntu Server LTS)

  • Acceso SSH con un usuario con permisos administrativos (para instalacion).
  • Python 3.
  • systemd (por defecto en Ubuntu).
  • Firewall basico (ufw) recomendado.

Paso 1: Crear el bot en Telegram (BotFather)

  1. Abre Telegram y busca @BotFather.
  2. Ejecuta /newbot y define:
    • Nombre: Abacombot (o el que prefieras)
    • Username: debe terminar en bot (ej: abacom_sysadmin_bot)
  3. BotFather te entregara un token tipo:
    • 123456789:AA...REDACTED...xyz
WarningADVERTENCIA CRITICA

No pegues el token en repositorios ni capturas publicas.

Lo que podria salir mal: - Un atacante toma control del bot. - Se filtran datos del servidor por chat.

Como prevenirlo: 1. Guarda el token como secreto (archivo root-only en /etc o un secret manager). 2. Usa allowlist de chat IDs. 3. Mantén el bot en modo read-only por defecto.


Ruta recomendada: OpenClaw (Gateway) + Telegram

Esta ruta usa OpenClaw para:

  • controlar acceso (pairing/allowlists)
  • aislar sesiones
  • auditar seguridad con openclaw security audit
  • administrar el bot como daemon

1.1 Instalar OpenClaw

BASH
1$ curl -fsSL https://openclaw.ai/install.sh | bash
1
curl … | bash instala el CLI de OpenClaw usando el instalador oficial.

1.2 Ejecutar el onboarding wizard (y daemon)

BASH
1$ openclaw onboard --install-daemon
1
openclaw onboard –install-daemon configura gateway/canales y deja OpenClaw corriendo como servicio.

1.3 Configurar Telegram (token) en OpenClaw

Config minima (recomendada) para Telegram:

TEXT
{
  channels: {
    telegram: {
      enabled: true,
      botToken: "REDACTED",
      dmPolicy: "pairing",
      groups: { "*": { requireMention: true } },
    },
  },
}
1
dmPolicy: pairing + requireMention evita que un grupo/DM desconocido active el bot sin aprobacion.

1.4 Verificar salud y postura de seguridad

BASH
1$ openclaw gateway status
2$ openclaw health
3$ openclaw security audit --deep

1
openclaw gateway status confirma que el gateway esta arriba.
2
openclaw health entrega un snapshot de salud (utile para pegar como evidencia).
3
openclaw security audit –deep detecta footguns (exposicion, allowlists, permisos) y puede sugerir fixes.

1.5 Aprobar pairing (primer DM)

Por defecto, los DMs entran en modo pairing: el bot entrega un codigo y no procesa hasta aprobar.

BASH
1$ openclaw pairing list telegram
2$ openclaw pairing approve telegram <CODE>
1
openclaw pairing list telegram lista solicitudes pendientes.
2
openclaw pairing approve aprueba el DM y habilita la conversacion.

1.6 Aislamiento de sesiones DM (multi-usuario)

Si mas de una persona puede escribirle al bot por DM, evita fuga de contexto usando session.dmScope.

TEXT
{ session: { dmScope: "per-channel-peer" } }

Referencias

  1. dmScope: per-channel-peer separa sesiones por canal+persona (recomendado para inbox compartido).

Ruta alternativa (sin OpenClaw): bot Python + Telegram

Esta ruta implementa un bot minimalista en Python. Es util como laboratorio para entender:

  • allowlist de chats
  • rate limit
  • ejecucion segura (sin shell)
  • despliegue con systemd

Si quieres las protecciones y ergonomia de OpenClaw (pairing, auditoria, sesiones, skills), usa la ruta recomendada.


Paso 2 (alternativa): Preparar el servidor (usuario, paquetes, firewall)

2.1 Crear un usuario dedicado

BASH
1$ sudo adduser --disabled-password --gecos "" openbot
Adding user `openbot' ...
Adding new group `openbot' (1001) ...
Adding new user `openbot' (1001) with group `openbot' ...
Creating home directory `/home/openbot' ...
1
adduser openbot crea un usuario sin password para ejecutar el servicio con permisos minimos.

2.2 Instalar dependencias

BASH
1$ sudo apt update
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Reading package lists... Done


2$ sudo apt install -y python3 python3-venv python3-pip ufw
Setting up python3-venv ...
Setting up ufw ...
1
apt update actualiza el indice de paquetes.
2
apt install instala Python + venv + ufw para el despliegue.

2.3 Firewall (UFW)

BASH
1$ sudo ufw allow OpenSSH
Rules updated
Rules updated (v6)


2$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
1
ufw allow OpenSSH evita bloquear tu acceso SSH.
2
ufw enable activa el firewall.

Paso 3: Estructura del proyecto (Openbot)

Vamos a instalar el proyecto en /opt/abacombot.

BASH
1$ sudo mkdir -p /opt/abacombot
2$ sudo chown -R openbot:openbot /opt/abacombot
1
mkdir -p /opt/abacombot crea el directorio del servicio.
2
chown asigna ownership al usuario del servicio.

Estructura sugerida:

TEXT
/opt/abacombot/
  venv/
  abacombot/
    __init__.py
    main.py
    security.py
    commands.py
    plugins/
      __init__.py
      read_only.py
  requirements.txt

Paso 4: Instalar el bot (Python + venv)

4.1 Crear venv e instalar dependencias

BASH
1$ sudo -u openbot python3 -m venv /opt/abacombot/venv
2$ sudo -u openbot /opt/abacombot/venv/bin/pip install --upgrade pip
1
python3 -m venv crea un entorno virtual aislado.
2
pip install –upgrade pip actualiza pip dentro del venv.

requirements.txt recomendado (en el host):

TEXT
python-telegram-bot==21.6
python-dotenv==1.0.1

Paso 5: Codigo base del Abacombot

Este es un esqueleto seguro para:

  • validar chats permitidos
  • rate limit basico
  • ejecutar comandos sin shell=True
  • limitar tiempo y tamano de salida

5.1 Archivo: abacombot/security.py

PYTHON
import os
import time
from dataclasses import dataclass


@dataclass(frozen=True)
class SecurityConfig:
    allowed_chat_ids: set[int]
    min_seconds_between_commands: float


class RateLimiter:
    def __init__(self, min_seconds_between_commands: float):
        self._min = float(min_seconds_between_commands)
        self._last_by_chat: dict[int, float] = {}

    def allow(self, chat_id: int) -> bool:
        now = time.time()
        last = self._last_by_chat.get(chat_id, 0.0)
        if now - last < self._min:
            return False
        self._last_by_chat[chat_id] = now
        return True


def parse_allowed_chat_ids(raw: str) -> set[int]:
    ids: set[int] = set()
    for part in (raw or "").split(","):
        part = part.strip()
        if not part:
            continue
        ids.add(int(part))
    return ids


def load_security_config() -> SecurityConfig:
    allowed = parse_allowed_chat_ids(os.environ.get("ABACOM_ALLOWED_CHAT_IDS", ""))
    return SecurityConfig(
        allowed_chat_ids=allowed,
        min_seconds_between_commands=float(os.environ.get("ABACOM_RATE_LIMIT_SECONDS", "2")),
    )
1
SecurityConfig + RateLimiter implementan controles basicos de acceso y anti-spam (defensa minima para un bot por chat).

5.2 Archivo: abacombot/commands.py

PYTHON
from __future__ import annotations

import subprocess
from dataclasses import dataclass


@dataclass(frozen=True)
class Cmd:
    name: str
    argv: list[str]


READ_ONLY_COMMANDS: dict[str, Cmd] = {
    "status": Cmd("status", ["/usr/bin/uptime"]),
    "disk": Cmd("disk", ["/usr/bin/df", "-h", "/"]),
    "mem": Cmd("mem", ["/usr/bin/free", "-h"]),
    "ip": Cmd("ip", ["/usr/sbin/ip", "-br", "addr"]),
    "ports": Cmd("ports", ["/usr/sbin/ss", "-lntup"]),
    "journal": Cmd("journal", ["/usr/bin/journalctl", "-n", "50", "--no-pager"]),
}


def run_cmd(cmd: Cmd, timeout_seconds: int = 8) -> str:
    completed = subprocess.run(
        cmd.argv,
        check=False,
        capture_output=True,
        text=True,
        timeout=timeout_seconds,
    )
    out = (completed.stdout or "") + ("\n" if completed.stderr else "") + (completed.stderr or "")
    out = out.strip()
    if not out:
        return f"(sin salida) exit_code={completed.returncode}"
    # Limite para no spamear Telegram
    if len(out) > 3500:
        return out[:3500] + "\n... (salida truncada)"
    return out
1
subprocess.run(…, capture_output=True, timeout=…) ejecuta argv fijo con timeout y captura stdout/stderr (sin shell=True).

5.3 Archivo: abacombot/main.py

PYTHON
import os

from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes

from abacombot.commands import READ_ONLY_COMMANDS, run_cmd
from abacombot.security import RateLimiter, load_security_config


SEC = load_security_config()
RL = RateLimiter(SEC.min_seconds_between_commands)


def is_allowed(update: Update) -> bool:
    chat_id = update.effective_chat.id if update.effective_chat else None
    if chat_id is None:
        return False
    if not SEC.allowed_chat_ids:
        # Modo inseguro (solo para laboratorio). Recomendado: configurar allowlist.
        return True
    return chat_id in SEC.allowed_chat_ids


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_allowed(update):
        return
    name = os.environ.get("ABACOM_BOT_NAME", "Abacombot")
    await update.message.reply_text(
        f"{name} listo. Usa /help para ver comandos disponibles."
    )


async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_allowed(update):
        return
    cmds = ", ".join(sorted(f"/{k}" for k in READ_ONLY_COMMANDS.keys()))
    await update.message.reply_text(
        "Comandos (read-only):\n" + cmds + "\n\nEj: /disk, /mem, /ports"
    )


async def run_readonly(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_allowed(update):
        return
    chat_id = update.effective_chat.id
    if not RL.allow(chat_id):
        await update.message.reply_text("Rate limit: espera unos segundos y reintenta.")
        return
    cmd_name = update.message.text.lstrip("/").strip()
    cmd = READ_ONLY_COMMANDS.get(cmd_name)
    if not cmd:
        await update.message.reply_text("Comando no reconocido. Usa /help")
        return
    try:
        output = run_cmd(cmd)
    except Exception as e:
        await update.message.reply_text(f"Error ejecutando comando: {e}")
        return
    await update.message.reply_text(f"$ {cmd.name}\n{output}")


def main() -> None:
    token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
    if not token:
        raise SystemExit("TELEGRAM_BOT_TOKEN no configurado")

    app = Application.builder().token(token).build()

    app.add_handler(CommandHandler("start", start))
    app.add_handler(CommandHandler("help", help_cmd))

    # Handlers read-only
    for name in READ_ONLY_COMMANDS.keys():
        app.add_handler(CommandHandler(name, run_readonly))

    app.run_polling(allowed_updates=Update.ALL_TYPES)


if __name__ == "__main__":
    main()
1
allowlist + rate limit + handlers por comando evita ejecucion arbitraria: solo rutas explicitamente registradas.

Paso 6: Configuracion por variables de entorno (sin hardcodear token)

Recomendado: archivo root-only en /etc/abacombot.env.

BASH
1$ sudo install -m 600 -o root -g root /dev/null /etc/abacombot.env
2$ sudoedit /etc/abacombot.env
1
install -m 600 crea un archivo solo-leible por root.
2
sudoedit edita de forma segura sin abrir permisos innecesarios.

Contenido sugerido:

BASH
1TELEGRAM_BOT_TOKEN="REDACTED"
2ABACOM_BOT_NAME="Abacombot"
3ABACOM_ALLOWED_CHAT_IDS="123456789,987654321"
4ABACOM_RATE_LIMIT_SECONDS="2"


1
TELEGRAM_BOT_TOKEN token del bot (secreto).
2
ABACOM_BOT_NAME nombre visible en mensajes.
3
ABACOM_ALLOWED_CHAT_IDS allowlist de chats autorizados (recomendado).
4
ABACOM_RATE_LIMIT_SECONDS evita spam/abuso.

Paso 7: Despliegue con systemd

Crear unit file:

BASH
1$ sudo tee /etc/systemd/system/abacombot.service >/dev/null <<'EOF'
[Unit]
Description=Abacombot (Openbot Telegram)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=openbot
Group=openbot
WorkingDirectory=/opt/abacombot
EnvironmentFile=/etc/abacombot.env
ExecStart=/opt/abacombot/venv/bin/python -m abacombot.main
Restart=on-failure
RestartSec=3

# Hardening basico
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
EOF
1
/etc/systemd/system/abacombot.service define como arrancar el bot como servicio.

Activar el servicio:

BASH
1$ sudo systemctl daemon-reload
2$ sudo systemctl enable --now abacombot
3$ systemctl status abacombot --no-pager

1
systemctl daemon-reload recarga units.
2
enable –now habilita al boot y arranca inmediatamente.
3
status valida estado, PID y ultimos logs.

Logs en vivo:

BASH
1$ sudo journalctl -u abacombot -f
1
journalctl -u abacombot -f sigue el log del servicio en tiempo real.

Paso 8: Seguridad para comandos con impacto (modo avanzado)

Por defecto, este anexo implementa solo comandos read-only. Si necesitas comandos como reiniciar nginx, debes:

  1. crear un comando explicito (no permitir ejecutar texto libre)
  2. limitarlo por allowlist
  3. usar sudoers solo para esa accion

Ejemplo: permitir reiniciar nginx sin password (solo para openbot).

BASH
1$ sudo visudo -f /etc/sudoers.d/abacombot
1
visudo valida sintaxis para no romper sudo.

Contenido recomendado (ejemplo):

TEXT
openbot ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
openbot ALL=(root) NOPASSWD: /usr/bin/systemctl status nginx
1
Permite solo systemctl restart nginx.
2
Permite consultar systemctl status nginx.
WarningADVERTENCIA CRITICA

Nunca implementes un comando tipo “ejecutar cualquier cosa” (shell) desde Telegram.

Lo que podria salir mal: - RCE (Remote Code Execution) por chat. - Exfiltracion de archivos y llaves. - Downtime por comandos maliciosos.

Como prevenirlo: 1. Usa un diccionario de comandos (allowlist) con argv fijo. 2. Ejecuta con subprocess.run([...], shell=False). 3. Usa timeouts y truncado de salida. 4. Aplica allowlist de chats + rate limit.


Troubleshooting

El bot no responde

BASH
1$ systemctl status abacombot --no-pager
2$ sudo journalctl -u abacombot -n 200 --no-pager
1
systemctl status confirma que el servicio corre.
2
journalctl -n 200 muestra el error real (token, import, permisos, red).

Token invalido / 401

  • Revisa TELEGRAM_BOT_TOKEN en /etc/abacombot.env.
  • Regenera token con BotFather si sospechas filtracion.

Comandos fallan por paths

  • En servidores, comandos pueden estar en rutas distintas. Prefiere rutas absolutas (/usr/bin/df, /usr/sbin/ss).

Checklist de aceptacion

Code Appendix