Telegram Bot
Pipe v0.50.5
A Telegram bot for managing a VPS through an AI agent.
Overview
The Telegram bot is a client of the agent. It connects to the backend over a Unix socket and passes messages from Telegram to the agent. Answers are formatted as HTML and sent back to the user.
It handles confirmations with inline buttons (YES / NO; the decision is added to the same message), sends diagrams as photos, shows progress in a single message that keeps updating – every agent command and a few lines of its output (when the work is done it collapses to "Steps taken: N", tap to expand the list) and, most importantly, writes by itself when something happens: watcher alerts and watcher alerts, suggestions, findings of its own investigations and routine reports arrive without asking, each with action buttons (Investigate, Free up space, Patch, Fix it).
Under the message box there is a fixed keyboard: Server status, What's new, Projects, Help – the most common things without knowing any command. The / menu shows only the everyday commands; the full list is in /help.
Alerts
The bot keeps a permanent connection to the backend ({"command": "subscribe"}, renewed after a restart) and forwards events to every user in TELEGRAM_ALLOWED_USER_IDS:
- WARNING / CRITICAL – disk, RAM, load, a container in a restart loop or unhealthy, a stopped container, a new public port. The Investigate button asks the agent for a diagnosis (reads only) and a proposed fix; a full disk also gets Free up space.
- Findings – on a critical alert Pipe does not wait for Investigate: a worker checks the cause by itself (reads only) and right after the alert writes what it found, with a Fix it button (one confirmation; admins only).
AUTO_INVESTIGATEinbackend/.env. - Suggestion – something that will become a problem or could be better: when the disk fills up, how much space can be reclaimed, a container that restarts often, a server waiting for a reboot, RAM that keeps growing. Computed without AI. Buttons: the action (e.g. Free up space), Later (back in a day), Fewer like this (that kind stays in the inbox only).
- RESOLVED – the problem is gone.
- Routine X – OK/PROBLEM – a report from a scheduled task.
- Report – every day at
DIGEST_TIME(7:00 by default): health, changes since yesterday, certificates, backups, updates, vulnerabilities and a load chart. Built without the LLM. - Security change – a new account (uid 0 = critical), a new key in
authorized_keys, a new SUID program, a change to sudoers/sshd/PAM/ld.so.preloadthat Pipe did not make. Checked every 2 minutes. - SSH logins – a series of failed logins (
WATCH_SSH_FAILURESin 10 min), a successful password login from an address that was guessing passwords (critical), a login from a new address. - Vulnerabilities – new urgent fixable vulnerabilities (actively exploited, critical, high) in the server's packages, container images and on remote servers; in application dependencies only critical and actively exploited ones. Each with a short note on what it risks. The Patch button (admins only) installs the fixes for what the alert is about right away, and the alert says what it will not patch (fixes only in Ubuntu Pro, containers without compose, Pipe's own containers). A known vulnerability does not come back, also after Pipe restarts.
- Findings for an outside alert – the same for an alert from Alertmanager, Grafana, Uptime Kuma or GitHub (webhooks,
WEBHOOK_INVESTIGATE), also with a Fix it button. - An alert that comes back has a Previously: line: what was found and what helped last time.
- APPROVAL – an external agent (for example Claude Code over MCP) wants to make a change: the command verbatim, the safety-fuse plan and Approve / Reject buttons (admins only). It expires after 30 minutes.
- Welcome – once, after installation: a map of the server, a security score and what Pipe watches.
- Alerts for certificates (expiring in 14 days / invalid), sites (not answering twice in a row), DNS and backups (the newest file older than 26 h), for the domains and directories Pipe found by itself.
Everything Pipe wrote by itself also lands in the inbox: /news (the What's new button) shows the latest messages with their buttons. When the bot should notify you is set with buttons in /notifications: the level (everything / important / critical only), quiet hours (at night only critical things, the rest in the morning with the report), the daily limit of suggestions. A message held back by these settings waits in the inbox.
TELEGRAM_ALERTS=0 in clients/telegram/.env turns forwarding off. Thresholds: Watching.
When notifications don't arrive
The bot gets alerts, reports and reminders through a permanent connection to the backend (an event subscription). When it is missing, /reminders and /alerts show a warning with the reason, and a reminder is marked waiting for pickup and arrives once the bot connects. The most common causes:
AGENT_TOKENinclients/telegram/.envdiffers from the one inbackend/.env; the bot log says:Kanal zdarzen nieaktywny: the backend rejected the subscription- the backend isn't running, or the socket (
AGENT_SOCKET) isn't shared by both processes - an old bot process is running (for example a second instance outside Docker)
Logs: docker compose logs telegram | grep -i "kanal\|subskrypcja"; when it works: Subskrypcja alertow aktywna. TELEGRAM_ALERTS=0 only silences watcher alerts and reports; reminders and approval requests always arrive.
Voice messages
Record a voice message and the bot replies I heard: ... and passes the text to the agent. With the gemini provider the model itself transcribes the recording, with openai or groq their Whisper does; it works right away. For other providers set STT_BASE_URL, STT_API_KEY, STT_MODEL in backend/.env (for example a free Groq key or a local faster-whisper).
Photos and files
Send a screenshot, a log or a config file; the caption is the question ("why won't nginx accept this?"). Several photos sent at once (an album) reach the agent as one message. The limit is 10 MB per file. The model looks at images and reads text (after secrets are hidden); other files are saved on the server and the agent gets the path. A viewer can send images and text but not files to be saved. Details: Attachments.
Roles
TELEGRAM_ALLOWED_USER_IDS– admins: everything, including YES/NO and/undo.TELEGRAM_VIEWER_IDS– read only: conversation, diagnosis, reports, charts and alerts; no YES/NO buttons and no/undo. NeedsAGENT_VIEWER_TOKENinclients/telegram/.envand the same one inbackend/.env. The backend enforces roles itself: even if the bot made a mistake, a change with a viewer token would not run.
Diagrams
/map or "show the architecture": the diagram arrives as a photo. A diagram larger than Telegram's photo limit (10 MB, sides adding up to 10000 px) arrives as a file in full resolution.
Formatting answers
The bot uses Telegram's HTML mode (not MarkdownV2). The reason: MarkdownV2 needs 18 special characters escaped, which breaks easily with dynamic content (for example container names with underscores). HTML needs only 3 characters escaped (<, >, &), which is much simpler.
Supported HTML tags:
<b>bold</b>– for key data<i>italic</i>– for file names<code>inline code</code>– for values and commands<pre>code block</pre>– for command output<u>underline</u>– when needed
The bot converts leftover Markdown (if the LLM produces it) into the matching HTML tags.
Text in backticks, including commands in confirmations, is shown verbatim, without Markdown conversion. So before tapping YES you see exactly the command that will run (including *, _, \\, <, & and backticks). The characters <, > and & outside tags are escaped automatically, so Telegram doesn't reject the message. Formatting code: clients/telegram/tg_format.py.
Reports and summaries
- Reports written by workers (routines, alert investigation, one-off tasks) arrive as a normal message: the status in the header, the
FINDINGS:/PROPOSALS:sections in bold, bulleted lists, commands in backticks verbatim. HTML tags in a report are shown as text: the report is written unattended, so it cannot insert a clickable link. - Summaries from the backend (
/changes,/health,/cost,/journal,/reminders,/incidents, the result of/undo) are headings and lists; identifiers (#ab12cd) can be copied with a tap. - A code block stays where the content is code or a log:
/history, file diffs, command output after approval.
Requirements
- Python 3.11+
- A running backend (
docker-compose up -dinbackend/) - Access to the Unix socket
/tmp/vps-agent.sock - A Telegram bot token (from @BotFather: https://t.me/botfather)
Configuration
1. Create a bot with @BotFather
/newbot
Copy the bot token.
2. Find your user ID
Send /start to @userinfobot (https://t.me/userinfobot); it returns your numeric ID.
3. Fill in .env
cp .env.example .env
Edit .env:
TELEGRAM_BOT_TOKEN=1234567890:ABCxyz...
TELEGRAM_ALLOWED_USER_IDS=123456789
AGENT_SOCKET=/tmp/vps-agent.sock
# Must be identical to AGENT_TOKEN in backend/.env.
# Leave empty if the backend doesn't require a token.
AGENT_TOKEN=
# Bot language: en (default) or pl, the same as PIPE_LANG in backend/.env.
# PIPE_LANG=pl
You can add several IDs separated by commas: 123456789,987654321
Running
The bot starts automatically together with the backend with docker-compose up -d.
Starting it by hand:
pip install -r requirements.txt
python bot.py
What you can ask
The full list is below (the Commands section). You can also just write, for example:
- "how much free disk space do I have?"
- "show the server architecture" / "draw how the deploy works"
- "why is the shop slow?"
- "check the disks on all servers" (workers)
- "every day at 7 check the backups" (a routine)
- "keep your answers shorter" (VIBE)
- "set up this app for me: https://github.com/acme/notebook" (the workshop)
- "write a script that counts the orders in the logs" (the workshop)
Security
The bot stays silent for users outside the whitelist: it doesn't answer any message. That way it doesn't reveal its existence to unauthorized people.
Commands
In English (the default) the / menu shows English names (/report, /changes, /undo...). Polish and English names work in both languages.
| Command | What it does |
|---|---|
/status | Server status at once, without AI: a plain-words headline (All good or what needs attention), CPU, memory and disk bars, the number of new messages and projects |
/news | The inbox: what Pipe wrote by itself (alerts, findings, suggestions, reports), with action buttons (/nowosci) |
/projects | Workshop projects: state, address, Logs and Stop/Start buttons (/projekty) |
/web | Pipe in the browser on your phone and computer: /web on exposes it at an HTTPS address, access requests arrive here with Let in / View only / Reject buttons (administrators only); /web off, /web revoke <no> |
/cleanup | Frees space without AI: untagged Docker images, old build cache, downloaded apt packages and npm/pip caches; says how much it freed. Admins only (/sprzataj) |
/notifications | When Pipe should notify you: level, quiet hours, suggestion limit – with buttons (/powiadomienia) |
/update [check] | Update Pipe itself (confirmed with buttons); check only shows the versions |
/report | The morning report on demand: health, alerts, changes since yesterday, certificates, backups, updates, LLM cost + chart |
/changes [24h|3d] | What changed on the server: packages, container images, ports, cron, accounts, SSH keys, configs |
/chart [load|ram|disk] [24h|7d] | Chart from the watcher's history as a photo |
/health | TLS certificates, site responses, DNS and backup freshness (not in the menu or /help – /status shows problems) |
/audit | Host security score 0-100 with ready fixes (write "fix 1") |
/incidents | Incident memory: what happened, what was found, what helped |
/approvals | Operations of external agents (MCP) waiting for approval; Approve/Reject buttons for admins, with the safety-fuse plan |
/mcp [add <add-on>|<name> <npx ...|uvx ...|URL> | remove <name> | reload] | Connected MCP servers with their state and ready add-ons (Google, Outlook, Microsoft 365, GitHub) with an Add button (administrators). Do not type keys in the chat – the /mcp window in pipe web or the CLI |
/journal | Approved changes with backups (what, when, whether it can be undone) |
/undo [id] | Undoes the last (or the given) change: a preview of the diff and inverse commands, then an Undo button. Works without the LLM |
/vulns | Known vulnerabilities in packages, container images, on remote servers and in applications, each with a short explanation. Viewers can use it too |
/patch [images|server|apps|servers|<target>] | Patches right away, without buttons or questions (admin): this server's packages, application dependencies (bumped in the dependency files, the compose project rebuilt), container images and remote servers, then cleans up old images and caches. One message shows the steps live, at the end a short result: what was patched, what stayed unchanged and why, how much space was freed. The Patch button on a vulnerability alert patches what the alert is about. Works without the LLM |
/cost | LLM token usage today and over the last days (with cost when prices are in .env) |
/view [simple|technical|description] | Pipe's view (/widok): without an argument Simple / Technical buttons. Simple changes the / menu, /help and the keyboard (status, news, skills, help), and the agent writes in plain words; shared with the CLI and pipe web. On the first /start the bot asks what you want to do – a button or a few words. Admin only |
/language [pl|en] | Pipe's language: switches agent instructions, reports, bot messages and the / menu without a restart, shared with the CLI and pipe web; admin only |
/providers [id [model]] | LLM providers: buttons for providers that have a key (the active one too, to change its model), then the provider's models as paged buttons; the current one is marked, "Keep ..." keeps it. /providers groq llama picks a model by name fragment, /providers model changes the model of the current provider. The bot never takes API keys (they would stay in the chat history); add them in pipe web or the CLI. Admin only |
/yolo [on|off] | YOLO mode: changes in this conversation run without YES/NO buttons (the safety fuse, the journal and /undo still work; forbidden operations are refused). Off by default, admin only, forgotten when the backend restarts |
/map [title] | Infrastructure diagram as a photo (no LLM, so fast and free) |
/server | Shows SERVER.md. When there is none, the agent explores the server and creates it |
/server update | The agent explores the server again and updates SERVER.md and DIRECTORY |
/directory | Map of repositories and directories (DIRECTORY) |
/skills | List of saved skills with their commands |
/download skill|plugin <owner/repo|URL> [filter] | Skills and plugins from a git repository: a preview with Install / Cancel buttons (/pobierz); admin only |
/alerts | Active watcher alerts and recent events (not in the menu or /help – messages are in /news) |
/routines | Tasks run on a schedule |
/reminders [cancel <id>] | One-off reminders; they arrive on time to the person who set them |
/targets | Remote servers, containers and clusters |
/new | New conversation from a clean slate (the bot has one conversation per user); the server notes, directories, skills, style and alerts stay |
/vibe | What the agent knows about your conversation style; /vibe reset clears it |
/<skill> | Runs a skill, for example /renew_certificate. You can add hints: /renew_certificate only for example.com |
/history | Latest audit-log entries, no LLM (not in the menu or /help – /journal shows changes) |
/help | Command list |
The bot sets the suggestion menu shown after typing / at startup and refreshes it after every message, so a new skill shows up in the menu right away. The menu is set separately for the chat of each user in TELEGRAM_ALLOWED_USER_IDS, not globally, so people outside the list don't see skill names or descriptions. If you don't see the menu, write anything to the bot (Telegram only allows setting the menu after the chat has started).
For an unknown command the bot replies with a hint instead of staying silent.