Operations manual

Everything HawkDNS.

A complete manual for installing, configuring, federating, and operating HawkDNS — the Telegram bot that watches your servers so you don't have to. Written for sysadmins who prefer reading documentation over guessing.

Version
1.0.0
Last revised
2026-06-17
License
MIT
Read time
~12 minutes

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 bothawkdns-bot.py, a long-running Python process that polls Telegram and answers commands.
  • The watchdoghawkdns-watchdog.sh, a cron-driven shell script that checks your domains every 2 minutes and pages on failure.
  • The boot-notifyhawkdns-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 #

WhatVersionNotes
linuxany modern kernelTested on Debian 11/12, Ubuntu 22/24, Alpine 3.18+, RHEL 9.
python3≥ 3.8Standard library only. No pip install.
curlanyFor Telegram API + installer.
jqanyInstaller auto-installs if missing.
systemdanyFor service management.
cronanyFor watchdog. cronie on RHEL, busybox-cron on Alpine.
dockeroptionalIf 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

  1. Open Telegram. Search @BotFather.
  2. Type /newbot and follow the prompts.
  3. Choose a display name (shown to you) and a username (must end in bot).
  4. BotFather replies with your token. Looks like 1234567:ABC-DEF1234ghIkl-zyx57W2v1u123ew11.
  5. Copy it. Treat it like a password.

Get your chat ID

  1. Open @userinfobot in Telegram.
  2. Send any message. It replies with your numeric ID.
  3. 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.

☞ Tip

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)

root@yourserver ~
$ 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.)

headless install
$ 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:

hub install
$ 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

PathPurpose
/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.serviceBot service unit.
/etc/systemd/system/hawkdns-boot-notify.serviceBoot-detection unit.
/var/log/hawkdns-watchdog.logWatchdog log.
⚠ Root required

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:

📊 web-prod-1
🔄 Alle
⚠️ Down
❓ Hilfe

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/:

FileRead byWhen
bot.envsystemd, watchdog, boot-notifyservice start, cron run
config.jsonhawkdns-bot.pyservice start
watchdog.confhawkdns-watchdog.shevery 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.

/etc/hawkdns/bot.env
# 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.

/etc/hawkdns/config.json
{
  "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" }
  ]
}
KeyTypeDescription
bot_tokenstringFrom BotFather. Required.
chat_whitelistarray of stringsChat IDs allowed to use the bot. Empty array means anyone with the bot can use it (not recommended).
server_namestringShown in alerts and main menu. Default: hostname.
peersarray of objectsOther 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.

/etc/hawkdns/watchdog.conf
# 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.

CommandWhat it does
/startOpen the main menu with inline keyboard.
/menuSame as /start.
/statusCompact one-line-per-server overview of all servers (this one + peers).
/allAlias for /status.
/downShow only failing containers (across all servers). Empty if all-green.
/helpShow 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:

📊 web-prod-1
📊 db1
📊 cache
🔄 Alle
⚠️ Down
❓ Hilfe

Server detail

🟢 web-prod-1

Load · RAM · Disk · Uptime · Containers

🔄 Refresh
📦 Container
⚠️ Down
📜 Logs Menu
🏠 Hauptmenü

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.

ⓘ Note

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.

/etc/hawkdns/config.json (on hub)
{
  "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"
⚠ Root SSH

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 #

  1. SSH-key out to the new peer from the hub (above).
  2. Edit /etc/hawkdns/config.json and append the peer to the array.
  3. systemctl restart hawkdns-bot.
  4. 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

  1. Cron runs /opt/hawkdns/hawkdns-watchdog.sh every 2 minutes.
  2. For each domain in watchdog.conf, the script issues an HTTPS request.
  3. If the response code matches the allowed list, success. If not, increment a failure counter.
  4. After 3 consecutive failures (≈6 min), send a Telegram 🚨 DOWN message — once.
  5. On first success after a DOWN, send a ✅ RECOVERED message and reset the counter.

Tuning

VariableDefaultWhere
ALERT_AFTER3 (≈ 6 min)/opt/hawkdns/hawkdns-watchdog.sh
Cron interval2 mincrontab -l
HTTP timeout10 secscript 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 #

ServiceTypeWhen
hawkdns-bot.servicesimple, restart=on-failureboot + always
hawkdns-boot-notify.serviceoneshotboot, 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 #

WhereWhat
journalctl -u hawkdns-botBot lifecycle, Python exceptions.
journalctl -u hawkdns-boot-notifyBoot alert send result.
/var/log/hawkdns-watchdog.logEach watchdog run start/stop.
/var/lib/hawkdns/watchdog/*.statePer-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 #

FlagDefaultDescription
--token=(prompt)Telegram bot token.
--chat=(prompt)Telegram chat ID.
--name=hostnameServer name shown in alerts.
--peers=(none)Comma-separated name:host peers.
--yes / -ySkip prompts. Use defaults or arg values.
--uninstallRemove HawkDNS.
--versionPrint version and exit.
--help / -hShow usage.
HAWKDNS_BASE_URL (env)https://bot.hawkdns.infoOverride download URL. Useful for air-gapped mirrors.

21 Architecture #

One Python process. Three responsibilities:

  1. Polling loop: getUpdates long-poll against Telegram API.
  2. Command router: parse /cmd and callback queries; dispatch to view functions.
  3. 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

  1. systemctl status hawkdns-bot — is it running?
  2. journalctl -u hawkdns-bot -n 30 — any Python exception?
  3. Check chat_whitelist in config.json contains your chat-ID.
  4. Try curl -s "https://api.telegram.org/bot$TOKEN/getMe" — should return ok:true.
  5. Check for token conflict: another instance of the bot (different server, dev laptop) polling the same token.

Watchdog never alerts

  1. tail /var/log/hawkdns-watchdog.log — is cron actually firing?
  2. ls /var/lib/hawkdns/watchdog/ — state files present?
  3. Run the watchdog manually: /opt/hawkdns/hawkdns-watchdog.sh — any errors?

Federation says "(timeout)" for a peer

  1. SSH to the peer manually from the hub. Does it work without password prompt?
  2. Increase ConnectTimeout in ~/.ssh/config for that host.
  3. 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.

✿   End of manual   ✿

Questions or corrections? Open an issue on GitHub or message the maintainer on Telegram.