Monitoring tools tell you when a server or website is down, but early warnings hide in the logs: a disk that fills up a little more every day, an error that repeats hundreds of times, a wave of SSH brute-force attempts, a process that quietly eats the CPU. Few people read those logs every morning. This guide collects the key facts once a day, lets a local language model in Ollama turn them into a short, readable report with suggested checks, and sends it to Telegram. Because the model runs on your own server, the logs never leave it.
You need Ollama with a small model, set up as in running a local LLM with Ollama, plus jq and curl. Create a bot with @BotFather in Telegram, send it any message, then read your chat ID:
apt install -y jq curl
ollama pull qwen2.5:7b
# Send any message to your bot first, then:
curl -s "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates" | jq '.result[].message.chat.id'
mkdir -p /opt/aireport
cat > /opt/aireport/report.sh <<'EOF2'
#!/bin/bash
# Daily AI server report: collect facts, summarise them with Ollama, send to Telegram
MODEL="qwen2.5:7b"
LANGUAGE="Simplified Chinese" # language of the report
TG_TOKEN="123456789:replace-with-your-bot-token"
TG_CHAT="123456789"
FACTS=/opt/aireport/facts.txt
{
echo "Host: $(hostname) Uptime: $(uptime -p)"
echo "Load average 1/5/15 min: $(cut -d' ' -f1-3 /proc/loadavg) CPU cores: $(nproc)"
echo; echo "## Memory"; free -h
echo; echo "## Disk usage"; df -h -x tmpfs -x devtmpfs -x overlay
echo; echo "## Failed systemd units"; systemctl --failed --no-legend
echo; echo "## Most frequent error messages in the last 24 hours"
journalctl --since "24 hours ago" -p err --no-pager -o cat | sort | uniq -c | sort -rn | head -20
echo; echo "## Failed SSH password logins in the last 24 hours (count, then top source IPs)"
journalctl --since "24 hours ago" --no-pager -o cat | grep -c "Failed password"
journalctl --since "24 hours ago" --no-pager -o cat | grep -oP "Failed password .* from \K[0-9a-fA-F.:]+" | sort | uniq -c | sort -rn | head -5
if [ -f /var/log/nginx/access.log ]; then
echo; echo "## Nginx HTTP status codes since the last log rotation"
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -8
echo; echo "## URLs with the most 5xx errors"
awk '$9 ~ /^5/ {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -5
fi
echo; echo "## Top processes by CPU"
ps -eo user,pid,%cpu,%mem,etime,comm --sort=-%cpu | head -8
} > "$FACTS" 2>&1
PROMPT="You are an experienced Linux operations engineer. Below are today's facts from a server.
Write a short daily report in $LANGUAGE for the server owner:
1. First line: overall status, one of OK / ATTENTION / PROBLEM.
2. Then at most 5 bullet points about anything unusual, each with the likely cause and one command to check it.
3. If everything looks normal, say so in one sentence.
Only use the facts below. Do not invent numbers.
$(head -c 12000 "$FACTS")"
REPORT=$(jq -n --arg m "$MODEL" --arg p "$PROMPT" \
'{model: $m, prompt: $p, stream: false, options: {temperature: 0.2, num_ctx: 8192}}' \
| curl -s --max-time 900 http://127.0.0.1:11434/api/generate -d @- | jq -r '.response // empty')
[ -z "$REPORT" ] && REPORT="AI summary failed. Raw facts:
$(head -c 3000 "$FACTS")"
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${TG_CHAT}" \
--data-urlencode "text=[$(hostname)] daily report
${REPORT:0:3800}" > /dev/null
EOF2
chmod 700 /opt/aireport/report.sh
Replace the bot token and chat ID. The script is readable only by root because it contains the token. num_ctx gives the model enough context for the whole fact sheet, and the report is cut to fit Telegram's message limit. If the model does not answer, you still receive the raw facts.
/opt/aireport/report.sh
cat /opt/aireport/facts.txt # the data the model saw
The first run takes longer while the model loads. Compare the report with facts.txt a few times to judge how reliable the summaries are for your servers, and adjust the prompt if it over- or under-reacts.
echo '0 8 * * * root /opt/aireport/report.sh' > /etc/cron.d/aireport
timedatectl | grep "Time zone" # cron uses the server's time zone
Add more sections inside the braces of the script; everything they print becomes part of the facts. Some useful examples:
echo; echo "## Fail2ban"; fail2ban-client status sshd | grep -E "Currently banned|Total banned"
echo; echo "## Stopped Docker containers"; docker ps -a --filter status=exited --format "{{.Names}}: {{.Status}}"
echo; echo "## Certificates"; for c in /etc/letsencrypt/live/*/cert.pem; do echo "$c $(openssl x509 -enddate -noout -in "$c")"; done
echo; echo "## Last backup file"; ls -lt /backup | head -3
Related guides: Fail2ban for SSH and Nginx, free SSL certificates and renewal, and for IP blocks, monitoring a rented IP block automatically.
Ollama does not need to run on every server. Run only the data-collection part on each server, copy facts.txt to the server with Ollama (for example with scp from cron, using an SSH key), and generate all reports there. If you call a central Ollama directly instead, do it over a private network or VPN, never by opening port 11434 to the internet.
On CPU the model writes slowly, which is fine for a daily job. For faster reports use a smaller model such as qwen2.5:3b or shorten the fact sheet by removing sections you do not need.
Technically yes, but logs contain IP addresses, user names, URLs and sometimes customer data. Sending them to a third party has privacy implications and must follow the provider's terms and supported regions. A local model avoids this.
Run the suggested command and look at the raw facts. If you see unknown processes using a lot of CPU or unexpected logins, follow checking whether a Linux server is hacked.
Still stuck after following these steps? Open a support ticket and the IMIDC 24/7 technical team will help. Include the server IP, OS version, the contents of facts.txt and the report you received so we can pinpoint the issue faster.