Как да следим промяната на публичното IP в Linux и да получаваме известие в Telegram (Надежден Bash скрипт)

Скрипт за следене на промяна на IP адрес Linux и известяване в Telegram

Следенето за промяна на ip адрес linux е важна задача, ако имате домашен сървър, Raspberry Pi, или просто използвате VPS с динамично адресиране. Вероятно ви е нужно да знаете кога външният ви IP адрес се е променил, за да обновите DNS записи или да получите известие. Повечето решения, които ще намерите в интернет…

В това ръководство ще ви покажем най-надеждния метод за промяна на ip адрес linux, който можете да настроите за минути.

Повечето решения, които ще намерите в интернет (или които ChatGPT ще ви генерира за 5 секунди), изглеждат така: правят една заявка, сравняват я с файл и пращат съобщение в Telegram. Проблемът е, че в реалната среда тези "играчки" скриптове се чупят, губят IP адреси при прекъсване на мрежата или изтичат пароли, ако случайно ги качите в публичен Git хранилище.

Днес ще ви покажа как да напишем наистина надежден (production-ready) Bash скрипт, който:

  1. Пази секретите си сигурно извън кода.
  2. Не се бърка, ако се стартира едновременно два пъти.
  3. Валидира истински IPv4 адреси (не просто "четири числа и точки").
  4. Записва новото IP само ако Telegram успешно е получил известието.

Стъпка 1: Подготовка на средата

Преди да напишем скрипта, трябва да подготвим файловата структура и да вземем нужните данни от Telegram.

1. Създаване на директории:

sudo mkdir -p /etc/check-ip
sudo mkdir -p /var/lib/check-ip

2. Създаване на Telegram бот и вземане на ID-то на чата:

  • Отворете Telegram и намерете бота @BotFather. Създайте си нов бот и запазете неговия Token (примерно: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11).
  • За да разберете вашия CHAT_ID, напишете съобщение на вашия нов бот, след това отворете в браузъра следния адрес (замествайки TOKEN-а с вашия): https://api.telegram.org/bot<TOKEN>/getUpdates
  • Потърсете в резултата полето "chat":{"id": 123456789}. Това число е вашият CHAT_ID`.

Стъпка 2: Сигурно съхранение на секретите

Никога не пишете токени директно в скрипта! Използвайте отделен файл с ограничени права.

Създайте файл /etc/check-ip/secrets.env:

sudo nano /etc/check-ip/secrets.env

Добавете следното съдържание (заместете с вашите реални данни):

TOKEN="ВАШИЯТ_TELEGRAM_BOT_TOKEN"
CHAT_ID="ВАШИЯТ_CHAT_ID"

Защитете файла, така че само root да може да го чете:

sudo chmod 600 /etc/check-ip/secrets.env
sudo chown root:root /etc/check-ip/secrets.env
📖
Може да ви е интересно още
WordPress Multi-Site Manager

Стъпка 3: Самият скрипт

Създайте файла на скрипта, например в /usr/local/bin/check_ip.sh:

sudo nano /usr/local/bin/check_ip.sh

Вмъкнете следния код:

#!/usr/bin/env bash
set -euo pipefail

########################################
# Конфигурация
########################################
SECRETS_FILE="/etc/check-ip/secrets.env"
IP_FILE="/var/lib/check-ip/last_ip"
LOCK_FILE="/tmp/check_ip.lock"
LOG_FILE="/var/log/check_ip.log"
CURL_TIMEOUT=10

########################################
# Функция за логване (дефинирана рано)
########################################
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}

########################################
# Зареждане на secrets
########################################
if [[ ! -f "$SECRETS_FILE" ]]; then
    log "ГРЕШКА: липсва $SECRETS_FILE"
    exit 1
fi
# shellcheck source=/dev/null
source "$SECRETS_FILE"

if [[ -z "${TOKEN:-}" || -z "${CHAT_ID:-}" ]]; then
    log "ГРЕШКА: TOKEN или CHAT_ID не са зададени"
    exit 1
fi

########################################
# Заключване (предотвратява едновременен запуск)
########################################
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
    log "Lock зает, пропускам изпълнението."
    exit 0
fi

########################################
# Създаване на директория при нужда
########################################
mkdir -p "$(dirname "$IP_FILE")"

########################################
# Валидация на IPv4 (реални граници 0-255)
########################################
valid_ip() {
    local ip=$1
    [[ $ip =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$ ]] || return 1
    local octet
    for octet in "${BASH_REMATCH[@]:1}"; do
        (( 10#$octet <= 255 )) || return 1
    done
    return 0
}

########################################
# Получаване на публичното IP (с fallback)
########################################
get_ip() {
    local ip="" service
    local services=(
        "https://api.ipify.org"
        "https://ipv4.icanhazip.com"
        "https://checkip.amazonaws.com"
    )
    for service in "${services[@]}"; do
        ip=$(curl --fail -s --max-time "$CURL_TIMEOUT" "$service" 2>/dev/null | tr -d '[:space:]') || {
            log "Предупреждение: '$service' не отговори."
            continue
        }
        if valid_ip "$ip"; then
            echo "$ip"
            return 0
        else
            log "Предупреждение: '$service' върна невалиден IP: '$ip'"
        fi
    done
    echo ""
    return 1
}

CURRENT_IP=$(get_ip) || true

########################################
# Проверка дали IP е валидно
########################################
if [[ -z "$CURRENT_IP" ]] || ! valid_ip "$CURRENT_IP"; then
    log "ГРЕШКА: не успях да получа валиден IP от нито един източник."
    exit 1
fi

########################################
# Старото IP
########################################
OLD_IP=""
if [[ -f "$IP_FILE" ]]; then
    OLD_IP=$(cat "$IP_FILE")
fi

########################################
# Няма промяна
########################################
if [[ "$CURRENT_IP" == "$OLD_IP" ]]; then
    exit 0
fi

########################################
# Telegram съобщение (escape на HTML спец. символи)
########################################
html_escape() {
    local s=$1
    s=${s//&/&amp;}
    s=${s//</&lt;}
    s=${s//>/&gt;}
    echo "$s"
}

HOSTNAME_ESC=$(html_escape "$(hostname)")
MESSAGE="<b>🔔 Промяна на публичното IP</b>
<b>Сървър:</b> <code>${HOSTNAME_ESC}</code>
<b>Старо:</b> <code>${OLD_IP:-Няма}</code>
<b>Ново:</b> <code>${CURRENT_IP}</code>
<i>$(date '+%Y-%m-%d %H:%M:%S')</i>"

########################################
# Изпращане към Telegram
########################################
if curl --fail -s \
    -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
    -d chat_id="${CHAT_ID}" \
    -d parse_mode="HTML" \
    --data-urlencode text="$MESSAGE" \
    >/dev/null
then
    # Атомарен запис на новото IP
    echo "$CURRENT_IP" > "${IP_FILE}.tmp"
    mv "${IP_FILE}.tmp" "$IP_FILE"
    log "IP променено: ${OLD_IP:-няма} -> ${CURRENT_IP}"
else
    log "ГРЕШКА: Telegram не прие съобщението. Новото IP НЕ е записано."
    exit 1
fi

Направете скрипта изпълним:

sudo chmod +x /usr/local/bin/check_ip.sh

Защо този код е "Production-Ready"? (Анатомия на логиката)

Ако сте начинаещ, част от нещата в скрипта може да ви се страт странни. Ето защо са там:

  • set -euo pipefail: Това е първото правило на Bash скриптинга. Ако някоя команда гръмне, скриптът спира веднага. Ако ползвате недефинирана променлива – спира. Не позволява на грешките да се "разпространят" тихо надолу по кода.
  • flock -n 9: Представете си, че скриптът се изпълнява през cron на всяка минута, но интернетът е бавен и скриптът се забави с 2 минути. Без това заключване, ще имате 2 едновременно работещи копия, които ще пращат двойни съобщения. flock гарантира, че работи само едно копие.
  • 10#$octet <= 255: Повечето хора валидират IP сРегулярни изрази (Regex). Проблемът е, че Regex-ът ще приема 999.999.999.999 като валиден формат, защото са просто цифри. Записът 10# принуждава Bash да го прочете като десетично число и го сравнява с реалната максимална стойност на октет (255).
  • Атомарен запис (mv): Защо не правим просто echo "$CURRENT_IP" > "$IP_FILE"? Ако системата рестартира или прекъсне тока точно в милисекундата, в която се пише файлът, той ще се развали. Пишем във .tmp файл и използваме mv, което в Linux е атомарна операция (или файлът е стар, или е нов, никога "раз_halfчен").
  • Запис след успех: Обърнете внимание на най-долу – новото IP се записва в файла чак след като curl към Telegram върне успех. Ако Telegram е блокиран или ботът ви е спрян, скриптът ще гръмне с грешка. На следващото му пускане, той отново ще види, че има разлика между старото и новото IP и ще опита да прати съобщението. Никога няма да пропуснете известие.

Стъпка 4: Автоматизация

Остава ни само да кажем на Linux да го изпълнява периодично. Отворете crontab на root потребителя:

sudo crontab -e

Добавете следния ред, за да проверявате на всеки 5 минути:

*/5 * * * * /usr/local/bin/check_ip.sh

Забележка: Първото пускане на скрипта (когато файла last_ip все още не съществува) автоматично ще прати съобщение в Telegram с "Старо: Няма", за да ви уведоми, че скриптът работи.

Готово! Вече имате изключително стабилна система за мониторинг на външния ви IP адрес, която можете да оставите да работи с години без намеса.

open source spirit
🛠️
$

Намерихте материала за полезен?

Съдържанието на itpraktika.com е безплатно и ще остане такова.
Ако статията ти е помогнала — можеш да подкрепиш сайта с малка доброволна сума. Всяко дарение помага за поддръжката и развитието на портала.

PayPal Revolut

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *


Колко е 6 + 6 ? (въведете числото)