01 Introduction #
HawkDNS is a single-process Python bot that polls Telegram, replies to commands with server status, and proactively alerts you when domains go down or servers reboot. It runs as a systemd service. It has zero external dependencies beyond Python 3, curl, and (optionally) jq.
The whole system is split into three working parts:
- The bot —
hawkdns-bot.py, a long-running Python process that polls Telegram and answers commands. - The watchdog —
hawkdns-watchdog.sh, a cron-driven shell script that checks your domains every 2 minutes and pages on failure. - The boot-notify —
hawkdns-boot-notify.sh, a one-shot systemd service that sends a Telegram message after every reboot.
Each part runs independently. If one breaks, the others keep working. The watchdog is intentionally written in bash so it can run even when Python or the bot are dead.
02 Requirements #
| What | Version | Notes |
|---|---|---|
linux | any modern kernel | Tested on Debian 11/12, Ubuntu 22/24, Alpine 3.18+, RHEL 9. |
python3 | ≥ 3.8 | Standard library only. No pip install. |
curl | any | For Telegram API + installer. |
jq | any | Installer auto-installs if missing. |
systemd | any | For service management. |
cron | any | For watchdog. cronie on RHEL, busybox-cron on Alpine. |
docker | optional | If present, bot reports container status. |
The bot needs root to read system stats and run docker commands. There is no non-root mode.
03 Telegram bot setup #
You need two values before installing: a bot token and your chat ID. Both come from Telegram itself.
Get the bot token
- Open Telegram. Search @BotFather.
- Type
/newbotand follow the prompts. - Choose a display name (shown to you) and a username (must end in
bot). - BotFather replies with your token. Looks like
1234567:ABC-DEF1234ghIkl-zyx57W2v1u123ew11. - Copy it. Treat it like a password.
Get your chat ID
- Open @userinfobot in Telegram.
- Send any message. It replies with your numeric ID.
- Copy it. Looks like
1476707413.
Activate your bot
Find your new bot (e.g. @your_bot) and send it /start once.
This tells Telegram you accept messages from it. Without this, the bot can
register with the API but cannot send to you.
You can reuse the same bot across many servers. Just install HawkDNS on each
with the same --token and --chat. All alerts arrive in the same conversation,
tagged with the server name.
04 Installation #
One curl line. The installer detects your OS, installs missing packages, validates the token, downloads files, writes config, sets up systemd, and sends a confirmation message.
Interactive (recommended for first install)
$ curl -fsSL https://bot.hawkdns.info/install.sh | sudo bash
The installer prompts you for the token, chat ID, and server name.
Server name defaults to hostname -s.
Headless (CI, Ansible, etc.)
$ curl -fsSL https://bot.hawkdns.info/install.sh | sudo bash -s -- \ --token="1234567:ABC-DEF…" \ --chat="987654321" \ --name="web-prod-1" \ --yes
With peers (hub mode)
If you want one server to monitor multiple, pass --peers:
$ curl -fsSL https://bot.hawkdns.info/install.sh | sudo bash -s -- \ --token="..." --chat="..." --name="hub" \ --peers="db1:10.0.0.10,cache:10.0.0.11,web2:10.0.0.12" \ --yes
Peer format: name:host, comma-separated. SSH user defaults to root, port to 22.
See Federation for how peers actually work.
What gets installed
| Path | Purpose |
|---|---|
/opt/hawkdns/ | Bot scripts (Python, shell). |
/etc/hawkdns/ | Config (bot.env, config.json, watchdog.conf). |
/var/lib/hawkdns/ | Runtime state (watchdog seen-counts). |
/etc/systemd/system/hawkdns-bot.service | Bot service unit. |
/etc/systemd/system/hawkdns-boot-notify.service | Boot-detection unit. |
/var/log/hawkdns-watchdog.log | Watchdog log. |
The installer needs root because it writes systemd units, modifies cron, and reads /proc.
Don't try to run it as a normal user — it will refuse.
05 Your first message #
After install completes, the installer sends a confirmation message
to your chat. Within 60 seconds, open Telegram and tap /start on the bot.
You should see something like this:
🦅 HawkDNS Dashboard
Wähle einen Server oder eine Aktion:
Tap any button. The reply is in-place: the same message updates with new content.
06 Config files #
Everything HawkDNS does is driven by three files under /etc/hawkdns/:
| File | Read by | When |
|---|---|---|
bot.env | systemd, watchdog, boot-notify | service start, cron run |
config.json | hawkdns-bot.py | service start |
watchdog.conf | hawkdns-watchdog.sh | every 2 min via cron |
After editing config.json or bot.env, restart the bot:
$ systemctl restart hawkdns-bot
Editing watchdog.conf needs no restart — it's re-read on every cron tick.
bot.env #
Shell-style environment file. Loaded by systemd's EnvironmentFile= directive and by the watchdog cron script.
# Generated by installer. Edit carefully. TG_BOT_TOKEN=1234567:ABC-DEF… TG_CHAT_ID=987654321 HAWKDNS_SERVER_NAME=web-prod-1
File mode is 600 (root-only). Keep the bot token confidential.
config.json #
Primary config for the bot process. JSON, strict syntax.
{
"bot_token": "1234567:ABC-DEF…",
"chat_whitelist": ["987654321"],
"server_name": "web-prod-1",
"peers": [
{ "name": "db1", "host": "10.0.0.10", "port": 22, "user": "root" },
{ "name": "cache", "host": "10.0.0.11" }
]
}
| Key | Type | Description |
|---|---|---|
bot_token | string | From BotFather. Required. |
chat_whitelist | array of strings | Chat IDs allowed to use the bot. Empty array means anyone with the bot can use it (not recommended). |
server_name | string | Shown in alerts and main menu. Default: hostname. |
peers | array of objects | Other servers to monitor via SSH. name and host required; port defaults to 22, user to root. |
watchdog.conf #
One domain per line. Format: domain.com|accepted,status,codes. Lines starting with # are ignored.
# public site — must always be 200 example.com|200 # admin — login redirect is fine admin.example.com|200,302 # API requires auth — 401 is normal for unauth requests api.example.com|200,401 # shop has multiple regional redirects shop.example.com|200,301,302
The watchdog checks each domain every 2 minutes (cron). If 3 consecutive checks fail (~6 minutes), it pages once. When the domain recovers, it sends a "RECOVERED" message and resets.
No restart needed after editing. Wait up to 2 minutes for next cron tick, or run /opt/hawkdns/hawkdns-watchdog.sh manually.
Editing config #
# Add a domain to the watchdog $ echo "new.example.com|200" >> /etc/hawkdns/watchdog.conf # Change the chat-id (e.g. moved to a group) $ nano /etc/hawkdns/bot.env $ nano /etc/hawkdns/config.json # also update chat_whitelist! $ systemctl restart hawkdns-bot # Add a peer (federation) $ nano /etc/hawkdns/config.json $ systemctl restart hawkdns-bot
07 Commands #
Type any of these in your Telegram chat with the bot. Commands are case-insensitive.
| Command | What it does |
|---|---|
/start | Open the main menu with inline keyboard. |
/menu | Same as /start. |
/status | Compact one-line-per-server overview of all servers (this one + peers). |
/all | Alias for /status. |
/down | Show only failing containers (across all servers). Empty if all-green. |
/help | Show available commands and configured servers. |
/<name> | Direct shortcut to a server's full status. E.g. /web-prod-1. |
08 Inline keyboards #
All views attach a Telegram inline keyboard. Pressing a button updates the current message in place — no chat-spam.
Main menu
🦅 HawkDNS Dashboard
Wähle einen Server oder eine Aktion:
Server detail
🟢 web-prod-1
Load · RAM · Disk · Uptime · Containers
09 Server views #
Single server status
Triggered by 📊 server-name button or /server-name. Renders Load, RAM (with bar), Disk (with bar), Uptime, container count, and any restarting/dead containers.
🟢 web-prod-1 Load 0.34 0.28 0.21 RAM 2.9G / 8.0G (free 5.1G) 🟩🟩🟩🟩⬜⬜⬜⬜⬜⬜ 36% Disk 54% of 197G (free 91G) 🟩🟩🟩🟩🟩⬜⬜⬜⬜⬜ 54% Up 42 days, 19 hours 📦 26 running, 19 stopped ⚠️ Down/Restarting: some-container: Restarting (1) 4 minutes ago
Compact overview (all)
Triggered by 🔄 Alle button or /status. One line per server with single-letter abbreviations.
🦅 Alle Server 🟢 web-prod-1 L 0.3 M 36% D 54% 📦26 🟢 db1 L 0.1 M 67% D 23% 📦 5 🟡 cache L 1.4 M 88% D 41% 📦 8 ⚠️1 🟢 worker L 0.0 M 42% D 19% 📦 3 L=Load · M=RAM% · D=Disk% · 📦=Container
Emojis: 🟢 healthy, 🟡 high resources (RAM/Disk > 85% or Load > 2), 🔴 has down containers.
Down filter
Shows only problem-containers across all servers. Empty if everything is healthy.
10 Container logs #
From any server view, tap 📜 Logs Menu. You get a list of running containers as buttons. Tap one — bot fetches the last 20 lines of docker logs and posts them inline.
Useful for "why is this restarting?" debugging without opening SSH.
Log output is HTML-escaped and truncated to 3500 chars. For longer
sessions, SSH in directly: docker logs -f <name>.
11 Federation #
The problem: Telegram only allows one bot instance to poll updates. If you install HawkDNS on N servers with the same token, they race for messages — most go to the wrong server, and the keyboard buttons break.
The solution: Pick one server as the hub. Install HawkDNS only on the
hub. Configure each peer in config.json as an SSH target. The hub
SSHes into each peer to fetch stats — peers don't run HawkDNS at all.
Hub mode #
On the hub server, edit config.json and add a peers array.
Each peer is queried via SSH whenever someone hits a /peer-name
command or the compact overview.
{
"bot_token": "...",
"chat_whitelist": ["123"],
"server_name": "hub",
"peers": [
{ "name": "web-prod-1", "host": "10.0.0.10", "port": 22, "user": "root" },
{ "name": "db1", "host": "10.0.0.11" },
{ "name": "cache", "host": "10.0.0.12" }
]
}
SSH key setup #
The hub needs passwordless SSH to each peer. Generate a key on the hub and add the public part to each peer's ~/.ssh/authorized_keys:
# on the hub $ ssh-keygen -t ed25519 -N "" -f /root/.ssh/hawkdns_ed25519 $ cat /root/.ssh/hawkdns_ed25519.pub # on each peer, append the public key $ echo "ssh-ed25519 AAAA..." >> /root/.ssh/authorized_keys # back on the hub, configure SSH to use this key $ cat >> /root/.ssh/config <<EOF Host 10.0.0.* IdentityFile /root/.ssh/hawkdns_ed25519 IdentitiesOnly yes StrictHostKeyChecking accept-new EOF # test it $ ssh root@10.0.0.10 "hostname; uptime"
Most hardened servers disable root SSH. If yours does, change the "user" field per peer
to a sudoers user, and use sudo in the SSH command — or simply allow key-only root SSH
to the hub's IP. Your call.
Adding a peer later #
- SSH-key out to the new peer from the hub (above).
- Edit
/etc/hawkdns/config.jsonand append the peer to the array. systemctl restart hawkdns-bot.- Open Telegram,
/start— the new peer appears in the menu.
12 Watchdog #
The watchdog is an independent cron-driven shell script that checks domains over HTTPS. It exists so you still get alerts when the bot process itself has crashed.
How it works
- Cron runs
/opt/hawkdns/hawkdns-watchdog.shevery 2 minutes. - For each domain in
watchdog.conf, the script issues an HTTPS request. - If the response code matches the allowed list, success. If not, increment a failure counter.
- After 3 consecutive failures (≈6 min), send a Telegram
🚨 DOWNmessage — once. - On first success after a DOWN, send a
✅ RECOVEREDmessage and reset the counter.
Tuning
| Variable | Default | Where |
|---|---|---|
ALERT_AFTER | 3 (≈ 6 min) | /opt/hawkdns/hawkdns-watchdog.sh |
| Cron interval | 2 min | crontab -l |
| HTTP timeout | 10 sec | script body |
13 Boot detection #
A oneshot systemd service runs after each boot and sends:
🔄 Server reboot detected
server: web-prod-1
host: web-prod-1.internal
kernel: 6.8.0-49-generic
HawkDNS is online and watching.
Triggered by hawkdns-boot-notify.service at multi-user.target. Sleeps 30 seconds
after network-up to let DNS settle.
14 Alert anatomy #
Examples of what you'll actually see in Telegram, in order:
🚨 DOWN: shop.example.com → HTTP 502 (seit 6 min) [web-prod-1] — 8 minutes later, after recovery — ✅ RECOVERED: shop.example.com → HTTP 200 (war ~14 min down) [web-prod-1] — after a reboot — 🔄 Server reboot detected server: web-prod-1 kernel: 6.8.0-49-generic HawkDNS is online and watching.
Alerts are tagged with [server-name] so federation chats stay unambiguous.
15 systemd #
| Service | Type | When |
|---|---|---|
hawkdns-bot.service | simple, restart=on-failure | boot + always |
hawkdns-boot-notify.service | oneshot | boot, once |
$ systemctl status hawkdns-bot $ systemctl restart hawkdns-bot $ systemctl stop hawkdns-bot $ systemctl disable hawkdns-bot # keep installed, don't autostart $ journalctl -fu hawkdns-bot # follow logs
16 Cron #
Installer appends one entry to root's crontab:
*/2 * * * * /opt/hawkdns/hawkdns-watchdog.sh
To change frequency, crontab -e and adjust the schedule.
17 Logs #
| Where | What |
|---|---|
journalctl -u hawkdns-bot | Bot lifecycle, Python exceptions. |
journalctl -u hawkdns-boot-notify | Boot alert send result. |
/var/log/hawkdns-watchdog.log | Each watchdog run start/stop. |
/var/lib/hawkdns/watchdog/*.state | Per-domain failure counter. |
18 Upgrading #
Re-run the installer with the same arguments. It detects the existing install, re-downloads the bot files, and restarts services. Config is preserved.
$ curl -fsSL https://bot.hawkdns.info/install.sh | sudo bash -s -- --yes
19 Uninstall #
$ curl -fsSL https://bot.hawkdns.info/install.sh | sudo bash -s -- --uninstall
Removes:
/opt/hawkdns/- systemd units (disables + removes)
- watchdog cron entry
Keeps:
/etc/hawkdns/(your config — delete manually if you want)/var/lib/hawkdns/(state files — delete manually if you want)/var/log/hawkdns-watchdog.log
20 CLI reference #
| Flag | Default | Description |
|---|---|---|
--token= | (prompt) | Telegram bot token. |
--chat= | (prompt) | Telegram chat ID. |
--name= | hostname | Server name shown in alerts. |
--peers= | (none) | Comma-separated name:host peers. |
--yes / -y | Skip prompts. Use defaults or arg values. | |
--uninstall | Remove HawkDNS. | |
--version | Print version and exit. | |
--help / -h | Show usage. | |
HAWKDNS_BASE_URL (env) | https://bot.hawkdns.info | Override download URL. Useful for air-gapped mirrors. |
21 Architecture #
One Python process. Three responsibilities:
- Polling loop:
getUpdateslong-poll against Telegram API. - Command router: parse
/cmdand callback queries; dispatch to view functions. - Run-remote: shell out locally or over SSH to fetch fresh stats.
No database. No web server. All state is config files + ephemeral process memory + watchdog's per-domain counter files. If the host disappears, everything is reconstructible from /etc/hawkdns/.
22 Security #
Chat whitelist
chat_whitelist in config.json blocks unknown chats. Even if someone discovers your bot's
username, they can message it but get "Nicht autorisiert." back.
Token handling
The token is stored in bot.env and config.json, both mode 0600. Don't commit them.
Treat the token like a private SSH key.
SSH from hub
Federation uses standard OpenSSH. We recommend a dedicated key per hub, restricted to peers via
~/.ssh/config. Don't reuse your developer key.
What HawkDNS does NOT do
- It does not open any inbound port.
- It does not phone home to any third party except Telegram's API.
- It does not install package mirrors, agents, or kernel modules.
23 Troubleshooting #
Bot doesn't reply
systemctl status hawkdns-bot— is it running?journalctl -u hawkdns-bot -n 30— any Python exception?- Check
chat_whitelistinconfig.jsoncontains your chat-ID. - Try
curl -s "https://api.telegram.org/bot$TOKEN/getMe"— should returnok:true. - Check for token conflict: another instance of the bot (different server, dev laptop) polling the same token.
Watchdog never alerts
tail /var/log/hawkdns-watchdog.log— is cron actually firing?ls /var/lib/hawkdns/watchdog/— state files present?- Run the watchdog manually:
/opt/hawkdns/hawkdns-watchdog.sh— any errors?
Federation says "(timeout)" for a peer
- SSH to the peer manually from the hub. Does it work without password prompt?
- Increase
ConnectTimeoutin~/.ssh/configfor that host. - Check the peer's firewall allows SSH from the hub's IP.
Telegram says "Forbidden: bot was blocked by the user"
You blocked your own bot. Unblock it in Telegram, then it works again.
Disk fills up from logs
Rotate the watchdog log via logrotate (file: /var/log/hawkdns-watchdog.log) or pipe to journal instead.
24 FAQ #
Can I use it without Docker?
Yes. If Docker isn't installed, the bot just doesn't show container counts. Everything else works.
Can I monitor non-Linux hosts?
The bot itself needs Linux. But you can SSH from a Linux hub to a BSD or macOS peer, as long as
the basic commands (uptime, df, free) exist there. free doesn't exist on BSD/macOS — those peers will
show "n/a" for RAM.
How does it compare to Uptime Kuma / Healthchecks.io / Grafana?
HawkDNS is single-process, no DB, no web UI, no dashboard. It's a notifier first. For long-term graphs and proper monitoring, run it alongside Kuma (the project's author does).
Can I customize alert messages?
Yes — edit the text in hawkdns-bot.py view functions or hawkdns-watchdog.sh.
After the next upgrade, your edits get overwritten. Fork it on disk if you want to keep them.
Will it work behind a corporate proxy?
Set HTTPS_PROXY in bot.env. Python urllib respects it, and curl picks it up automatically.
What's the resource footprint?
About 25 MB RAM for the bot. Negligible CPU (idle most of the time). Watchdog runs for ~1 second every 2 min.
Is there an MIT license?
Yes. The whole thing is MIT. Read the script — there's nothing to hide.
Questions or corrections? Open an issue on GitHub or message the maintainer on Telegram.