Hướng dẫn thực hành · MikroTik

Trong trang này

MikroTik PPPoE Monitor - Telegram Notification

Cài nhanh bằng Terminal

Chuẩn bị trên RouterOS 7

RouterOS 7 có thể chặn fetch hoặc Scheduler bằng device-mode. Kiểm tra trước khi cài:

/system/device-mode/print

Nếu thấy fetch: no hoặc scheduler: no, chỉ bật hai tính năng cần thiết:

/system/device-mode/update fetch=yes scheduler=yes

Trong thời gian router hiển thị, thường là 5 phút, rút nguồn rồi cắm lại để xác nhận vật lý. Không dùng /system reboot thay cho bước này. Sau khi router khởi động xong, kiểm tra lại:

/system/device-mode/print

Chỉ tiếp tục khi kết quả có đủ:

fetch: yes
scheduler: yes
flagged: no

Nếu gặp failure: not allowed by device-mode, chưa chạy lại installer ngay; hãy bật fetchscheduler theo các bước trên. Nếu flagged: yes, dừng cài đặt và kiểm tra an toàn của router trước khi gỡ trạng thái cảnh báo. Installer không thể tự bật các tính năng này vì chính fetch đang bị RouterOS chặn.

Tài liệu chính thức: MikroTik RouterOS Device-mode.

Chạy installer

Mặc định installer dùng pppoe-out1 và tự thêm pppoe-out2 nếu interface này tồn tại. Dán từng bước ASCII sau vào Terminal:

  1. Nhập Bot Token và Chat ID:
:global pppMonInstallBotToken "BOT_TOKEN"; :global pppMonInstallChatId "CHAT_ID"
  1. Nếu interface không dùng tên mặc định, khai báo riêng trước lệnh fetch:
:global pppMonInstallPppoe1 "WAN1"; :global pppMonInstallPppoe2 "WAN2";

Nếu dùng đúng pppoe-out1pppoe-out2, bỏ qua bước này. Cuối cùng tải và chạy installer:

/tool fetch url="https://mikrotik.thachanhitt.vn/pppoe-monitor-install.rsc" dst-path="pppoe-monitor-install.rsc" check-certificate=yes; /import file-name="pppoe-monitor-install.rsc"; /file remove [find where name="pppoe-monitor-install.rsc"]

Installer tự tạo hoặc cập nhật route probe, pppoe-monitor-config, pppoe-monitor, pppoe-monitor-reset và Scheduler pppoe-monitor-every-3m. Bot Token và Chat ID chỉ được lưu trong script cấu hình trên router, không nằm trong URL công khai.

Sau khi cài, chạy thử và xem log:

/system script run pppoe-monitor
/log print where message~"PPPoE-MON"

Phải thấy INSTALL | SUCCESSDONE. Việc kiểm tra thực tế trên RouterOS 6.49.x hoặc 7.x vẫn bắt buộc trước khi dùng chính thức.

Cảnh báo mạng mở rộng (tùy chọn)

Phần này đã tách riêng: Cài đặt cảnh báo mạng mở rộng. File đó có lệnh cài nhanh, lệnh bật/tắt từng nhóm và hướng dẫn tạo thủ công trong WinBox/WebFig.

Cài thủ công

  1. Đặt tên nhà trong System → Identity. Nếu dùng CLI, chỉ đặt tên ASCII:

    /system identity set name="NHA-ANH-THACH"
    
  2. Tạo route probe:

    /ip route
    add dst-address=8.8.8.8/32 gateway=pppoe-out1 comment="PPPoE-monitor-isp1"
    add dst-address=9.9.9.9/32 gateway=pppoe-out2 comment="PPPoE-monitor-isp2"
    add dst-address=208.67.220.220/32 gateway=pppoe-out3 comment="PPPoE-monitor-isp3"
    add dst-address=94.140.14.14/32 gateway=pppoe-out4 comment="PPPoE-monitor-isp4"
    

    Chỉ thêm route cho WAN đang sử dụng. Nếu đã có route cùng comment, chỉnh route đó, không thêm trùng.

  3. Vào System → Scripts → +:

    • Name: pppoe-monitor-config
    • Policy: read, policy, test
    • Source: dán khối dưới và sửa thông tin thật. WAN nào không sử dụng thì giữ pppMonPppoe2/3/4 là chuỗi rỗng; WAN có sử dụng phải điền đúng tên interface tương ứng với route.
    :global pppMonBotToken "BOT_TOKEN"
    :global pppMonChatId "CHAT_ID"
    :global pppMonNotifyChannel "telegram"
    :global pppMonMaxLines 4
    :global pppMonFailLimit 3
    :global pppMonPingCount 3
    :global pppMonRecoveryHoldTime 5m
    :global pppMonDebug true
    :global pppMonPppoe1 "pppoe-out1"
    :global pppMonPppoe2 ""
    :global pppMonPppoe3 ""
    :global pppMonPppoe4 ""
    :global pppMonProbe1 "8.8.8.8"
    :global pppMonProbe2 "9.9.9.9"
    :global pppMonProbe3 "208.67.220.220"
    :global pppMonProbe4 "94.140.14.14"
    
  4. Tạo Script thứ hai:

    • Name: pppoe-monitor
    • Policy: read, write, policy, test
    • Source: dán khối bên dưới, không cần nhập lại Bot Token.

Source được dán vào giao diện Scripts, không dán vào Terminal. Trong CLI chỉ dùng ASCII, không dùng tiếng Việt có dấu. Đổi tên WAN trong pppMonPppoe1 tới pppMonPppoe4 của config và gateway= ở route tương ứng. Không sửa hàm $checkLine; nguồn dưới được đồng bộ từ installer.

SOURCE

    /system script run pppoe-monitor-config

    :global pppMonBotToken
    :global pppMonChatId
    :global pppMonFailLimit
    :global pppMonPingCount
    :global pppMonRecoveryHoldTime
    :global pppMonDebug
    :global pppMonPppoe1
    :global pppMonPppoe2
    :global pppMonPppoe3
    :global pppMonPppoe4
    :global pppMonProbe1
    :global pppMonProbe2
    :global pppMonProbe3
    :global pppMonProbe4

    :global pppMonFailCount
    :global pppMonLastAlert
    :global pppMonAlertOpen
    :global pppMonOutageActive
    :global pppMonOutageStartedAt
    :global pppMonOutageStartUptime
    :global pppMonRecoveryHoldUntil

    :if ([:typeof $pppMonFailCount] != "array") do={
        :set pppMonFailCount {"_init"=0}
    }
    :if ([:typeof $pppMonLastAlert] != "array") do={
        :set pppMonLastAlert {"_init"=""}
    }
    :if ([:typeof $pppMonAlertOpen] != "array") do={
        :set pppMonAlertOpen {"_init"=false}
    }
    :if ([:typeof $pppMonOutageActive] != "array") do={
        :set pppMonOutageActive {"_init"=false}
    }
    :if ([:typeof $pppMonOutageStartedAt] != "array") do={
        :set pppMonOutageStartedAt {"_init"=""}
    }
    :if ([:typeof $pppMonOutageStartUptime] != "array") do={
        :set pppMonOutageStartUptime {"_init"=0}
    }
    :if ([:typeof $pppMonRecoveryHoldUntil] != "array") do={
        :set pppMonRecoveryHoldUntil {"_init"=0}
    }

    :if ($pppMonDebug = true) do={
        :log info ("[PPPoE-MON] RUN | router=" . [/system identity get name])
    }

    :local checkLine do={
        :global pppMonBotToken
        :global pppMonChatId
        :global pppMonFailLimit
        :global pppMonPingCount
        :global pppMonRecoveryHoldTime
        :global pppMonDebug
        :global pppMonFailCount
        :global pppMonLastAlert
        :global pppMonAlertOpen
        :global pppMonOutageActive
        :global pppMonOutageStartedAt
        :global pppMonOutageStartUptime
        :global pppMonRecoveryHoldUntil

        :local notify do={
            :global pppMonBotToken
            :global pppMonChatId
            :global pppMonDebug

            :local pppoeId [/interface pppoe-client find where name=$pppoeName]
            :if ([:len $pppoeId] = 0) do={
                :log error ("[PPPoE-MON] ERROR | PPPoE not found | interface=" . $pppoeName)
                :return false
            }

            :local routerName [/system identity get name]
            :local pppoeUser [/interface pppoe-client get $pppoeId user]
            :local wanPort [/interface pppoe-client get $pppoeId interface]
            :local pppoeComment [/interface pppoe-client get $pppoeId comment]
            :local currentIp "Không có"

            :if ([:len $pppoeUser] = 0) do={ :set pppoeUser "Không khai báo" }
            :if ([:len $pppoeComment] = 0) do={ :set pppoeComment "Không có" }

            :do {
                :local addressIds [/ip address find where interface=$pppoeName]
                :if ([:len $addressIds] > 0) do={
                    :set currentIp [/ip address get [:pick $addressIds 0] address]
                }
            } on-error={ :set currentIp "Không có" }

            :local tgHeader ""
            :if ($eventName = "down") do={
                :set tgHeader "\F0\9F\94\B4 MẤT KẾT NỐI INTERNET"
            } else={
                :if ($eventName = "recovered-late") do={
                    :set tgHeader "\F0\9F\9F\A2 INTERNET ĐÃ KHÔI PHỤC SAU SỰ CỐ"
                } else={
                    :set tgHeader "\F0\9F\9F\A2 INTERNET ĐÃ KHÔI PHỤC"
                }
            }

            :local tgText ($tgHeader . "\n")
            :set tgText ($tgText . "------------------------------\n")
            :set tgText ($tgText . "\F0\9F\8F\A0 Nhà / Router: " . $routerName . "\n")
            :set tgText ($tgText . "\F0\9F\8C\90 Đường truyền: " . $lineLabel . "\n")
            :set tgText ($tgText . "\F0\9F\91\A4 Tài khoản PPPoE: " . $pppoeUser . "\n")
            :set tgText ($tgText . "\F0\9F\94\8C Interface: " . $pppoeName . " qua " . $wanPort . "\n")
            :set tgText ($tgText . "\F0\9F\93\9D Comment: " . $pppoeComment . "\n")
            :set tgText ($tgText . "\F0\9F\8C\8D IP hiện tại: " . $currentIp . "\n")
            :set tgText ($tgText . "\F0\9F\93\A1 Điểm kiểm tra: " . $probeAddress . "\n")
            :if ($eventName != "down") do={
                :set tgText ($tgText . "Bắt đầu mất: " . $outageStartedAt . "\n")
                :set tgText ($tgText . "Thời gian gián đoạn: " . $outageDuration . "\n")
                :if ($eventName = "recovered-late") do={
                    :set tgText ($tgText . \
                        "Ghi chú: Không thể gửi lúc mất mạng; đây là báo bù.\n")
                }
            }
            :set tgText ($tgText . "\F0\9F\95\92 Thời gian: " . [/system clock get date] . " " . [/system clock get time] . "\n")

            :local formText ""
            :for formIndex from=0 to=([:len $tgText] - 1) do={
                :local formChar [:pick $tgText $formIndex ($formIndex + 1)]
                :if ($formChar = "%") do={
                    :set formText ($formText . "%25")
                } else={
                    :if ($formChar = "&") do={
                        :set formText ($formText . "%26")
                    } else={
                        :if ($formChar = "+") do={
                            :set formText ($formText . "%2B")
                        } else={
                            :if ($formChar = "=") do={
                                :set formText ($formText . "%3D")
                            } else={
                                :if ($formChar = " ") do={
                                    :set formText ($formText . "%20")
                                } else={
                                    :if ($formChar = "\n") do={
                                        :set formText ($formText . "%0A")
                                    } else={
                                        :if ($formChar = "\r") do={
                                            :set formText $formText
                                        } else={
                                            :set formText ($formText . $formChar)
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            :local telegramUrl ("https://api.telegram.org/bot" . $pppMonBotToken . "/sendMessage")
            :local requestBody ("chat_id=" . $pppMonChatId . "&text=" . $formText)
            :local telegramSent false

            :if ($pppMonDebug = true) do={
                :log info ("[PPPoE-MON] TELEGRAM | event=" . $eventName . \
                    " | line=" . $lineLabel . " | status=SENDING")
            }

            :do {
                /tool fetch url=$telegramUrl http-method=post \
                    http-header-field="Content-Type:application/x-www-form-urlencoded" \
                    http-data=$requestBody keep-result=no check-certificate=yes
                :set telegramSent true
                :log info ("[PPPoE-MON] TELEGRAM | event=" . $eventName . \
                    " | line=" . $lineLabel . " | status=SUCCESS")
            } on-error={
                :log error ("[PPPoE-MON] TELEGRAM | event=" . $eventName . \
                    " | line=" . $lineLabel . " | status=FAILED")
            }

            :return $telegramSent
        }

        :local pppoeId [/interface pppoe-client find where name=$pppoeName]
        :if ([:len $pppoeId] = 0) do={
            :log error ("[PPPoE-MON] ERROR | check skipped | interface=" . $pppoeName)
            :return
        }

        :local failStateType [:typeof ($pppMonFailCount->$lineKey)]
        :if (($failStateType = "nothing") || ($failStateType = "nil")) do={
            :set ($pppMonFailCount->$lineKey) 0
        }
        :local alertDateStateType [:typeof ($pppMonLastAlert->$lineKey)]
        :if (($alertDateStateType = "nothing") || ($alertDateStateType = "nil")) do={
            :set ($pppMonLastAlert->$lineKey) ""
        }
        :local alertOpenStateType [:typeof ($pppMonAlertOpen->$lineKey)]
        :if (($alertOpenStateType = "nothing") || ($alertOpenStateType = "nil")) do={
            :set ($pppMonAlertOpen->$lineKey) false
        }
        :local outageStateType [:typeof ($pppMonOutageActive->$lineKey)]
        :if (($outageStateType = "nothing") || ($outageStateType = "nil")) do={
            # Preserve an open alert when upgrading from an older installer.
            :set ($pppMonOutageActive->$lineKey) ($pppMonAlertOpen->$lineKey)
        }
        :local startedAtType [:typeof ($pppMonOutageStartedAt->$lineKey)]
        :if (($startedAtType = "nothing") || ($startedAtType = "nil")) do={
            :set ($pppMonOutageStartedAt->$lineKey) "Không xác định"
        }
        :local startUptimeType [:typeof ($pppMonOutageStartUptime->$lineKey)]
        :if (($startUptimeType = "nothing") || ($startUptimeType = "nil")) do={
            :set ($pppMonOutageStartUptime->$lineKey) 0
        }
        :local holdUntilType [:typeof ($pppMonRecoveryHoldUntil->$lineKey)]
        :if (($holdUntilType = "nothing") || ($holdUntilType = "nil")) do={
            :set ($pppMonRecoveryHoldUntil->$lineKey) 0
        }

        :if ($pppMonDebug = true) do={
            :log info ("[PPPoE-MON] CHECK | line=" . $lineLabel . \
                " | interface=" . $pppoeName . " | probe=" . $probeAddress)
        }

        :local pingReplies 0
        :do {
            :set pingReplies [/ping address=$probeAddress interface=$pppoeName \
                count=$pppMonPingCount interval=500ms]
        } on-error={ :set pingReplies 0 }

        :if ($pingReplies > 0) do={
            :if ($pppMonDebug = true) do={
                :log info ("[PPPoE-MON] RESULT | line=" . $lineLabel . \
                    " | status=UP | replies=" . $pingReplies . "/" . $pppMonPingCount . \
                    " | previous-fails=" . ($pppMonFailCount->$lineKey))
            }

            :if (($pppMonOutageActive->$lineKey) = true) do={
                :local recoveryEvent "recovered-late"
                :if (($pppMonAlertOpen->$lineKey) = true) do={
                    :set recoveryEvent "up"
                }
                :local outageDuration "Không xác định"
                :local savedUptime ($pppMonOutageStartUptime->$lineKey)
                :if ([:typeof $savedUptime] = "time") do={
                    :set outageDuration \
                        ([/system resource get uptime] - $savedUptime)
                }
                :local recoverySent [$notify eventName=$recoveryEvent \
                    lineLabel=$lineLabel pppoeName=$pppoeName \
                    probeAddress=$probeAddress \
                    outageStartedAt=($pppMonOutageStartedAt->$lineKey) \
                    outageDuration=$outageDuration]
                :if ($recoverySent = true) do={
                    :set ($pppMonAlertOpen->$lineKey) false
                    :set ($pppMonOutageActive->$lineKey) false
                    :set ($pppMonOutageStartedAt->$lineKey) ""
                    :set ($pppMonOutageStartUptime->$lineKey) 0
                    :set ($pppMonRecoveryHoldUntil->$lineKey) \
                        ([/system resource get uptime] + $pppMonRecoveryHoldTime)
                }
            } else={
                :set ($pppMonOutageStartedAt->$lineKey) ""
                :set ($pppMonOutageStartUptime->$lineKey) 0
            }

            :set ($pppMonFailCount->$lineKey) 0
        } else={
            :local failCount (($pppMonFailCount->$lineKey) + 1)
            :if ($failCount > $pppMonFailLimit) do={ :set failCount $pppMonFailLimit }
            :set ($pppMonFailCount->$lineKey) $failCount
            :if (($failCount = 1) && \
                    (($pppMonOutageActive->$lineKey) = false)) do={
                :set ($pppMonOutageStartedAt->$lineKey) \
                    ([/system clock get date] . " " . [/system clock get time])
                :set ($pppMonOutageStartUptime->$lineKey) \
                    [/system resource get uptime]
            }
            :log warning ("[PPPoE-MON] RESULT | line=" . $lineLabel . \
                " | status=DOWN | replies=0/" . $pppMonPingCount . \
                " | fail=" . $failCount . "/" . $pppMonFailLimit)

            :local today [/system clock get date]
            :if ($failCount >= $pppMonFailLimit) do={
                :set ($pppMonOutageActive->$lineKey) true
                :if ((($pppMonAlertOpen->$lineKey) = false) && \
                        (($pppMonLastAlert->$lineKey) != $today)) do={
                    :local alertSent [$notify eventName="down" lineLabel=$lineLabel \
                        pppoeName=$pppoeName probeAddress=$probeAddress]
                    :if ($alertSent = true) do={
                        :set ($pppMonLastAlert->$lineKey) $today
                        :set ($pppMonAlertOpen->$lineKey) true
                    }
                } else={
                    :if ($pppMonDebug = true) do={
                        :log info ("[PPPoE-MON] ALERT-SKIP | line=" . $lineLabel . \
                            " | reason=already-sent-today")
                    }
                }
            }
        }

        :if ($pppMonDebug = true) do={
            :log info ("[PPPoE-MON] END | line=" . $lineLabel)
        }
    }

    $checkLine lineKey="isp1" lineLabel="ISP 1 - đường chính" \
        pppoeName=$pppMonPppoe1 probeAddress=$pppMonProbe1

    :if ([:len $pppMonPppoe2] > 0) do={
        $checkLine lineKey="isp2" lineLabel="ISP 2 - dự phòng" \
            pppoeName=$pppMonPppoe2 probeAddress=$pppMonProbe2
    }
    :if ([:len $pppMonPppoe3] > 0) do={
        $checkLine lineKey="isp3" lineLabel="ISP 3" \
            pppoeName=$pppMonPppoe3 probeAddress=$pppMonProbe3
    }
    :if ([:len $pppMonPppoe4] > 0) do={
        $checkLine lineKey="isp4" lineLabel="ISP 4" \
            pppoeName=$pppMonPppoe4 probeAddress=$pppMonProbe4
    }

    :if ($pppMonDebug = true) do={
        :log info "[PPPoE-MON] DONE | all configured lines checked"
    }

Scheduler

Vào System → Scheduler → +:

CLI ASCII tương đương:

/system scheduler add name="pppoe-monitor-every-3m" interval=3m on-event="/system script run pppoe-monitor" policy=read,write,policy,test comment="PPPoE-monitor"

Thêm WAN

Nguồn hiện hỗ trợ tối đa 4 WAN. Điền pppMonPppoe3/pppMonPppoe4 trong config và thêm route probe WAN tương ứng ở trên. Không tự thêm lời gọi $checkLine, không dùng probe trùng với generator/WARP. Xem khai báo và nâng cấp 4 WAN.

Kiểm tra

Reset để test lại

Tạo script thứ hai trong System → Scripts với Name: pppoe-monitor-reset. Dán Source sau, rồi nhấn Run Script trước khi test. Tạo bằng cùng tài khoản đang sở hữu pppoe-monitor.

:global pppMonFailCount
:global pppMonLastAlert
:global pppMonAlertOpen
:global pppMonOutageActive
:global pppMonOutageStartedAt
:global pppMonOutageStartUptime
:global pppMonRecoveryHoldUntil

:set pppMonFailCount {"_init"=0}
:set pppMonLastAlert {"_init"=""}
:set pppMonAlertOpen {"_init"=false}
:set pppMonOutageActive {"_init"=false}
:set pppMonOutageStartedAt {"_init"=""}
:set pppMonOutageStartUptime {"_init"=0}
:set pppMonRecoveryHoldUntil {"_init"=0}

:log warning "[PPPoE-MON] STATE | reset"

CLI ASCII để xem log:

/log print where message~"PPPoE-MON"

Không đưa Bot Token vào GitHub hoặc chat công khai.

↑↓ chọn · Enter mở · Esc đóng