/interface bridge
add admin-mac=ZZ:BB:CC:DD:EE:GG auto-mac=no comment=defconf name=bridge \
protocol-mode=none
/interface ethernet
set [ find default-name=ether1 ] mac-address=AA:BB:CC:DD:EE:FF
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
add name=Outbond
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip ipsec mode-config
add name="NordVPN mode config" responder=no src-address-list=under_vpn \
use-responder-dns=no
/ip ipsec policy group
add name=NordVPN
/ip ipsec profile
add dh-group=modp2048 enc-algorithm=aes-256 hash-algorithm=sha512 name=\
"NordVPN profile"
/ip ipsec peer
add address=sg466.nordvpn.com exchange-mode=ike2 name=NordVPN profile=\
"NordVPN profile"
/ip ipsec proposal
add auth-algorithms=sha256 enc-algorithms=aes-256-cbc lifetime=0s name=\
"NordVPN proposal" pfs-group=none
/ip pool
add name=default-dhcp ranges=10.0.1.101-10.0.1.254
/ip dhcp-server
add address-pool=default-dhcp disabled=no interface=bridge lease-time=1d10m \
name=defconf
/ppp profile
add change-tcp-mss=yes name=MyPPPoE on-down="# !rsc\
\n# RouterOS script: myPPPoE-on-down\
\n# Copyright (c) 2020 xxxxx <xxxxx@gmail.com>\
\n#\
\n# run scripts on myPPPoE down\
\n\
\n:global LogPrintExit;\
\n\
\n:local Interface \$interface;\
\n\
\n:if ([ :typeof \$Interface ] = \"nothing\") do={\
\n \$LogPrintExit error \"This script is sdownposed to run from PPPo\
E on-down script hook.\" true;\
\n}\
\n\
\n:local IntName [ / interface get \$Interface name ];\
\n:log info (\"PPP interface \" . \$IntName . \" is down.\");\
\n\
\n:local Scripts {\
\n \"myPPPoE-on-down\"\
\n}\
\n\
\n:foreach Script in=\$Scripts do={\
\n :if ([ / system script print count-only where name=\$Script ] > 0) do=\
{\
\n :log debug (\"Running script from myPPPoE-on-down: \" . \$Script)\
;\
\n / system script run \$Script;\
\n }\
\n}\
\n" on-up="#!rsc\
\n# RouterOS script: ppp-on-up\
\n# Copyright (c) 2013-2020 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# run scripts on ppp up\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-up.md\
\n\
\n:global LogPrintExit;\
\n\
\n:local Interface \$interface;\
\n\
\n:if ([ :typeof \$Interface ] = \"nothing\") do={\
\n \$LogPrintExit error \"This script is supposed to run from ppp on-up s\
cript hook.\" true;\
\n}\
\n\
\n:local IntName [ / interface get \$Interface name ];\
\n:log info (\"PPP interface \" . \$IntName . \" is up.\");\
\n\
\n:local Scripts {\
\n \"check-PPPoE-ip\"\
\n}\
\n\
\n:foreach Script in=\$Scripts do={\
\n :if ([ / system script print count-only where name=\$Script ] > 0) do=\
{\
\n :log debug (\"Running script from ppp-on-up: \" . \$Script);\
\n / system script run \$Script;\
\n }\
\n}\
\n"
add change-tcp-mss=yes name=MyPptpPsh on-down="#!rsc by RouterOS\
\n# RouterOS script: ppp-on-down\
\n# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# run scripts on ppp down\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-down.md\
\n\
\n:local 0 \"ppp-on-down\";\
\n:global GlobalFunctionsReady;\
\n:while (\$GlobalFunctionsReady != true) do={ :delay 500ms; }\
\n\
\n:global LogPrintExit2;\
\n\
\n:local Interface \$interface;\
\n\
\n:if ([ :typeof \$Interface ] = \"nothing\") do={\
\n \$LogPrintExit2 error \$0 (\"This script is sdownposed to run from ppp\
\_on-down script hook.\") true;\
\n}\
\n\
\n:local IntName [ / interface get \$Interface name ];\
\n:log info (\"PPP interface \" . \$IntName . \" is down.\");\
\n\
\n:local Scripts {\
\n \"pptppsh-on-down\"\
\n}\
\n\
\n:foreach Script in=\$Scripts do={\
\n :if ([ :len [ / system script find where name=\$Script ] ] > 0) do={\
\n :log debug (\"Running script from ppp-on-down: \" . \$Script);\
\n / system script run \$Script;\
\n }\
\n}\
\n" on-up="#!rsc by RouterOS\
\n# RouterOS script: ppp-on-up\
\n# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# run scripts on ppp up\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-up.md\
\n\
\n:local 0 \"ppp-on-up\";\
\n:global GlobalFunctionsReady;\
\n:while (\$GlobalFunctionsReady != true) do={ :delay 500ms; }\
\n\
\n:global LogPrintExit2;\
\n\
\n:local Interface \$interface;\
\n\
\n:if ([ :typeof \$Interface ] = \"nothing\") do={\
\n \$LogPrintExit2 error \$0 (\"This script is supposed to run from ppp o\
n-up script hook.\") true;\
\n}\
\n\
\n:local IntName [ / interface get \$Interface name ];\
\n:log info (\"PPP interface \" . \$IntName . \" is up.\");\
\n\
\n\
\n:local Scripts {\
\n \"pptppsh-on-up\"\
\n}\
\n\
\n:foreach Script in=\$Scripts do={\
\n :if ([ :len [ / system script find where name=\$Script ] ] > 0) do={\
\n :log debug (\"Running script from ppp-on-up: \" . \$Script);\
\n / system script run \$Script;\
\n }\
\n}" use-encryption=yes
/interface pppoe-client
add add-default-route=yes disabled=no interface=ether1 name=PPPoE profile=\
MyPPPoE user=xxxxx@xxxxx.xxx
/interface pptp-client
add connect-to=111.111.111.111 disabled=no name=PptpPsh profile=MyPptpPsh \
user=x-user
/queue type
add kind=pcq name=pcq-download-streaming pcq-burst-rate=1536k \
pcq-burst-threshold=1152k pcq-classifier=dst-address pcq-rate=960k
add kind=pcq name=pcq-download-gaming pcq-classifier=dst-address pcq-rate=\
768k
add kind=pcq name=pcq-upload-gaming pcq-classifier=src-address pcq-rate=768k
add kind=pcq name=pcq-upload-streaming pcq-classifier=src-address pcq-rate=\
768k
add kind=pcq name=pcq-download-1M pcq-classifier=dst-address pcq-limit=64KiB \
pcq-rate=1M pcq-total-limit=3840KiB
add kind=pcq name=pcq-download-burst pcq-burst-rate=2M pcq-burst-threshold=\
1536 pcq-classifier=dst-address pcq-limit=64KiB pcq-rate=1M \
pcq-total-limit=3840KiB
/queue tree
add max-limit=29M name=Download parent=global priority=2 queue=default
add max-limit=5M name=Upload parent=global priority=1 queue=default
add limit-at=3M max-limit=5M name=HeavyDownload packet-mark=HeavyDownload \
parent=Download priority=6 queue=pcq-download-streaming
add limit-at=2M max-limit=8M name=VideoDownload packet-mark=VideoDownload \
parent=Download priority=7 queue=pcq-download-streaming
add limit-at=2M max-limit=29M name=SpeedtestDownload packet-mark=\
SpeedtestDownload parent=Download queue=pcq-download-default
add limit-at=3M max-limit=8M name=GameDownload packet-mark=GameDownload \
parent=Download priority=3 queue=pcq-download-gaming
add limit-at=3M max-limit=10M name=LightDownload packet-mark=LightDownload \
parent=Download priority=5 queue=pcq-download-gaming
add limit-at=4M max-limit=8M name=VoipDownload packet-mark=VoipDownload \
parent=Download priority=4 queue=pcq-download-default
add limit-at=512k max-limit=2M name=VideoUpload packet-mark=VideoUpload \
parent=Upload priority=6 queue=pcq-upload-streaming
add limit-at=512k max-limit=2M name=HeavyUpload packet-mark=HeavyUpload \
parent=Upload priority=5 queue=pcq-upload-streaming
add limit-at=128k max-limit=5M name=SpeedtestUpload packet-mark=\
SpeedtestUpload parent=Upload queue=pcq-upload-default
add limit-at=768k max-limit=4M name=GameUpload packet-mark=GameUpload parent=\
Upload priority=2 queue=pcq-upload-gaming
add limit-at=512k max-limit=2M name=LightUpload packet-mark=LightUpload \
parent=Upload priority=4 queue=pcq-upload-gaming
add limit-at=768k max-limit=2M name=VoipUpload packet-mark=VoipUpload parent=\
Upload priority=3 queue=pcq-upload-default
add limit-at=1M max-limit=18M name=PriviledgeDownload packet-mark=\
PriviledgeDownload parent=Download priority=7 queue=pcq-download-default
add limit-at=128k max-limit=3M name=PriviledgeUpload packet-mark=\
PriviledgeUpload parent=Upload priority=6 queue=pcq-upload-default
/interface bridge port
add bridge=bridge comment=defconf interface=ether2
add bridge=bridge comment=defconf interface=ether3
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5
/ip neighbor discovery-settings
set discover-interface-list=LAN
/interface detect-internet
set detect-interface-list=WAN internet-interface-list=WAN lan-interface-list=\
LAN wan-interface-list=WAN
/interface list member
add comment=defconf interface=bridge list=LAN
add comment=defconf disabled=yes interface=ether1 list=WAN
add interface=PPPoE list=WAN
add interface=bridge list=Outbond
add comment="dynamic, service=pptp, server=PptpPsh" interface=PptpPsh list=\
LAN
/ip address
add address=10.0.1.1/24 comment=defconf interface=bridge network=10.0.1.0
add address=192.168.1.6/30 interface=ether1 network=192.168.1.4
/ip cloud
set ddns-enabled=yes
/ip dhcp-client
add comment=defconf interface=ether1
/ip dhcp-server network
add address=10.0.1.0/24 comment=defconf dns-server=10.0.1.1 gateway=10.0.1.1
/ip dns
set allow-remote-requests=yes servers=8.8.8.8,8.8.4.4
/ip dns static
add address=10.0.1.1 comment=defconf name=router.lan
/ip firewall address-list
add address=0.0.0.0/8 comment="default, RFC6890" list=NotPublic
add address=10.0.0.0/8 comment="default, RFC6890" list=NotPublic
add address=100.64.0.0/10 comment="default, RFC6890" list=NotPublic
add address=127.0.0.0/8 comment="default, RFC6890" list=NotPublic
add address=169.254.0.0/16 comment="default, RFC6890" list=NotPublic
add address=172.16.0.0/12 comment="default, RFC6890" list=NotPublic
add address=192.0.0.0/24 comment="default, RFC6890" list=NotPublic
add address=192.0.2.0/24 comment="default, RFC6890" list=NotPublic
add address=192.168.0.0/16 comment="default, RFC6890" list=NotPublic
add address=192.88.99.0/24 comment="default, RFC3068" list=NotPublic
add address=198.18.0.0/15 comment="default, RFC6890" list=NotPublic
add address=198.51.100.0/24 comment="default, RFC6890" list=NotPublic
add address=203.0.113.0/24 comment="default, RFC6890" list=NotPublic
add address=224.0.0.0/4 comment="default, RFC4601" list=NotPublic
add address=240.0.0.0/4 comment="default, RFC6890" list=NotPublic
add address=10.0.1.112 list=PriviledgeClient
add address=10.0.1.124 list=PriviledgeClient
add address=192.168.1.5 comment="default, F609" list=RestrictedNetwork
add address=10.0.1.0/24 comment=default list=HomeNetwork
add address=10.0.1.0/24 list=LocalNetwork
add address=172.16.220.36 comment="dynamic, service=pptp, server=PptpPsh" \
list=PiHole
add address=172.16.220.36 comment="dynamic, service=pptp, server=PptpPsh" \
list=AcceptSrcAdd
add address=172.16.220.36 comment="dynamic, service=pptp, server=PptpPsh" \
list=AcceptDstAdd
/ip firewall filter
add action=accept chain=input comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
invalid
add action=accept chain=input comment="adminconf, OpenVPN" dst-port=1194 \
in-interface=PPPoE protocol=tcp
add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
add action=accept chain=input comment=\
"defconf: accept to local loopback (for CAPsMAN)" dst-address=127.0.0.1
add action=drop chain=input comment="defconf: drop all not coming from LAN" \
in-interface-list=!LAN
add action=accept chain=forward comment="defconf: accept in ipsec policy" \
ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" \
ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
connection-state=established,related disabled=yes
add action=accept chain=forward comment=\
"defconf: accept established,related, untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \
connection-state=new in-interface-list=WAN
/ip firewall mangle
add action=accept chain=prerouting dst-port=53 protocol=udp
add action=accept chain=prerouting dst-port=53,8291,64874,64875 protocol=tcp
add action=accept chain=prerouting src-address-list=AcceptSrcAdd
add action=accept chain=prerouting dst-address-list=AcceptDstAdd
add action=passthrough chain=lb-accept-prerouting disabled=yes
add action=passthrough chain=lb-mark-con-prerouting disabled=yes
add action=passthrough chain=lb-mark-routing-prerouting disabled=yes
add action=mark-connection chain=prerouting comment=\
"Mark All Outbond Connection" connection-mark=no-mark dst-address-list=\
!NotPublic in-interface-list=Outbond new-connection-mark=LightConnection \
passthrough=yes
add action=mark-connection chain=prerouting comment=GameConnection \
connection-mark=LightConnection disabled=yes in-interface-list=Outbond \
new-connection-mark=GameConnection passthrough=yes protocol=icmp
add action=mark-connection chain=prerouting comment=PriviledgeConnection \
connection-mark=LightConnection in-interface-list=Outbond \
new-connection-mark=PriviledgeConnection passthrough=yes \
src-address-list=PriviledgeClient
add action=mark-connection chain=prerouting comment=GameConnection \
connection-mark=LightConnection dst-address-list=GameNetwork \
in-interface-list=Outbond new-connection-mark=GameConnection passthrough=\
yes
add action=mark-connection chain=prerouting comment=VoipConnection \
connection-mark=LightConnection dst-address-list=VoipNetwork \
in-interface-list=Outbond new-connection-mark=VoipConnection passthrough=\
yes
add action=mark-connection chain=prerouting comment=VoipConnection \
connection-mark=LightConnection dst-port=599,3478,3479,5090,8801-8810 \
in-interface-list=Outbond new-connection-mark=VoipConnection passthrough=\
yes protocol=udp
add action=mark-connection chain=prerouting comment=VoipConnection \
connection-mark=LightConnection dst-port=5938 in-interface-list=Outbond \
new-connection-mark=VoipConnection passthrough=yes protocol=tcp
add action=mark-connection chain=prerouting comment=VideoConnection \
connection-mark=LightConnection dst-address-list=VideoNetwork \
in-interface-list=Outbond new-connection-mark=VideoConnection \
passthrough=yes
add action=mark-connection chain=prerouting comment=SpeedtestConnection \
connection-mark=LightConnection dst-port=8080 in-interface-list=Outbond \
new-connection-mark=SpeedtestConnection passthrough=yes protocol=tcp
add action=mark-connection chain=prerouting comment=SpeedtestConnection \
connection-mark=LightConnection dst-port=8080 in-interface-list=Outbond \
new-connection-mark=SpeedtestConnection passthrough=yes protocol=udp
add action=mark-connection chain=prerouting comment=HeavyConnection \
connection-bytes=500000-0 connection-mark=LightConnection \
connection-rate=200k-100M in-interface-list=Outbond new-connection-mark=\
HeavyConnection passthrough=yes protocol=tcp
add action=mark-connection chain=prerouting comment=HeavyConnection \
connection-bytes=500000-0 connection-mark=LightConnection \
connection-rate=200k-100M dst-port="" in-interface-list=Outbond \
new-connection-mark=HeavyConnection passthrough=yes protocol=udp
add action=accept chain=input comment="default, hotspot" dst-port=64872 \
hotspot="" protocol=udp
add action=accept chain=input comment="default, hotspot" dst-port=64872-64875 \
hotspot="" protocol=tcp
add action=passthrough chain=lb-mark-con-input disabled=yes
add action=accept chain=forward dst-port=53 protocol=udp
add action=accept chain=forward dst-port=53 protocol=tcp
add action=accept chain=forward src-address-list=AcceptSrcAdd
add action=accept chain=forward dst-address-list=AcceptDstAdd
add action=accept chain=forward dst-address-list=NotPublic in-interface-list=\
Outbond
add action=mark-packet chain=forward comment=PriviledgeConnection \
connection-mark=PriviledgeConnection in-interface-list=WAN \
new-packet-mark=PriviledgeDownload passthrough=no
add action=mark-packet chain=forward comment=PriviledgeConnection \
connection-mark=PriviledgeConnection in-interface-list=Outbond \
new-packet-mark=PriviledgeUpload passthrough=no
add action=mark-packet chain=forward comment=GameConnection connection-mark=\
GameConnection in-interface-list=WAN new-packet-mark=GameDownload \
passthrough=no
add action=mark-packet chain=forward comment=GameConnection connection-mark=\
GameConnection in-interface-list=Outbond new-packet-mark=GameUpload \
passthrough=no
add action=mark-packet chain=forward comment=VoipConnection connection-mark=\
VoipConnection in-interface-list=WAN new-packet-mark=VoipDownload \
passthrough=no
add action=mark-packet chain=forward comment=VoipConnection connection-mark=\
VoipConnection in-interface-list=Outbond new-packet-mark=VoipUpload \
passthrough=no
add action=mark-packet chain=forward comment=VideoConnection connection-mark=\
VideoConnection in-interface-list=WAN new-packet-mark=VideoDownload \
passthrough=no
add action=mark-packet chain=forward comment=VideoConnection connection-mark=\
VideoConnection in-interface-list=Outbond new-packet-mark=VideoUpload \
passthrough=no
add action=mark-packet chain=forward comment=SpeedtestConnection \
connection-mark=SpeedtestConnection in-interface-list=WAN \
new-packet-mark=SpeedtestDownload passthrough=no
add action=mark-packet chain=forward comment=SpeedtestConnection \
connection-mark=SpeedtestConnection in-interface-list=Outbond \
new-packet-mark=SpeedtestUpload passthrough=no
add action=mark-packet chain=forward comment=HeavyConnection connection-mark=\
HeavyConnection in-interface-list=WAN new-packet-mark=HeavyDownload \
passthrough=no
add action=mark-packet chain=forward comment=HeavyConnection connection-mark=\
HeavyConnection in-interface-list=Outbond new-packet-mark=HeavyUpload \
passthrough=no
add action=mark-packet chain=forward comment=LightConnection connection-mark=\
LightConnection in-interface-list=WAN new-packet-mark=LightDownload \
passthrough=no
add action=mark-packet chain=forward comment=LightConnection connection-mark=\
LightConnection in-interface-list=Outbond new-packet-mark=LightUpload \
passthrough=no
add action=accept chain=output comment="default, hotspot" hotspot="" \
protocol=udp src-port=64872
add action=accept chain=output comment="default, hotspot" hotspot="" \
protocol=tcp src-port=64872-64875
add action=passthrough chain=lb-mark-routing-output disabled=yes
/ip firewall nat
add action=src-nat chain=srcnat comment=\
"dynamic, service=pptp, server=PptpPsh" out-interface=PptpPsh \
to-addresses=172.16.220.12
add action=masquerade chain=srcnat dst-address-list=RestrictedNetwork \
src-address-list=HomeNetwork
add action=src-nat chain=srcnat comment=NordVPN dst-address=104.18.19.110 \
protocol=icmp to-addresses=10.6.0.36
add action=passthrough chain=ipsec-placeholder disabled=yes
add action=src-nat chain=srcnat comment="PPPoE, src-nat" out-interface=\
PPPoE to-addresses=111.111.111.222
add action=masquerade chain=srcnat comment="defconf: masquerade" \
ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment=\
"dynamic, service=pptp, server=PptpPsh" dst-address-list=!PiHole \
dst-port=53 in-interface-list=!WAN protocol=udp src-address-list=!PiHole \
to-addresses=172.16.220.36 to-ports=53
add action=dst-nat chain=dstnat comment=\
"dynamic, service=pptp, server=PptpPsh" dst-address-list=!PiHole \
dst-port=53 in-interface-list=!WAN protocol=tcp src-address-list=!PiHole \
to-addresses=172.16.220.36 to-ports=53
add action=dst-nat chain=pre-hotspot comment=\
"dynamic, service=pptp, server=PptpPsh" dst-port=53 hotspot=auth \
protocol=udp to-addresses=172.16.220.36 to-ports=53
add action=dst-nat chain=pre-hotspot comment=\
"dynamic, service=pptp, server=PptpPsh" dst-port=53 hotspot=auth \
protocol=tcp to-addresses=172.16.220.36 to-ports=53
/ip ipsec identity
add auth-method=eap certificate=NordVPNRootCA eap-methods=eap-mschapv2 \
generate-policy=port-strict mode-config="NordVPN mode config" peer=\
NordVPN policy-template-group=NordVPN username=KKKKKKKKKKKKKKKK
/ip ipsec policy
add action=none dst-address=10.0.1.0/24 src-address=0.0.0.0/0
add action=none comment="PPPoE IP" dst-address=111.111.111.222/32 \
src-address=0.0.0.0/0
add dst-address=0.0.0.0/0 group=NordVPN proposal="NordVPN proposal" \
src-address=0.0.0.0/0 template=yes
/ip route
add comment="dynamic, load-balance, PPPoE" distance=10 dst-address=\
89.187.162.97/32 gateway=213.245.6.1 scope=10
add comment="dynamic, service=pptp, server=PptpPsh" distance=6 dst-address=\
172.16.220.0/24 gateway=172.16.220.1
add distance=6 dst-address=172.30.15.0/24 gateway=172.16.220.1
add comment="dynamic, load-balance, PPPoE" distance=10 dst-address=\
213.244.68.174/32 gateway=213.245.6.1 scope=10
/system clock
set time-zone-name=Asia/Jakarta
/system identity
set name=RBFL_E67E
/system ntp client
set enabled=yes primary-ntp=216.239.35.0
/system scheduler
add interval=1m name=pihole-check on-event=\
"/ system script run pihole-check;" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=apr/15/2021 start-time=19:42:10
add interval=1m name=log-forward on-event="/ system script run log-forward;" \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
add interval=1m name=nordvpn-ipsec on-event=\
"/ system script run nordvpn-ipsec;" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
add name=global-scripts on-event="/ system script { run global-config; run glo\
bal-config-overlay; run global-functions; run my-function; }" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
add interval=1d name=ScriptInstallUpdate on-event=\
":global ScriptInstallUpdate; \$ScriptInstallUpdate;" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
add interval=1w name=email-backup on-event=\
"/ system script run email-backup;" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=mar/27/2021 start-time=09:15:00
add interval=1m name=netwatch-notify on-event=\
"/ system script run netwatch-notify;" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
add interval=1m name=FlushEmailQueue on-event=\
":global FlushEmailQueue; \$FlushEmailQueue;" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
/system script
add dont-require-permissions=no name=global-config owner=global-config \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source="#!rsc by RouterOS\
\n# RouterOS script: global-config\
\n# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# global configuration\
\n# https://git.eworm.de/cgit/routeros-scripts/about/\
\n\
\n# Make sure all configuration properties are up to date and this\
\n# value is in sync with value in script 'global-functions'!\
\n:global GlobalConfigVersion 52;\
\n\
\n# This is used for DNS and backup file.\
\n:global Domain \"example.com\";\
\n:global HostNameInZone true;\
\n:global PrefixInZone true;\
\n:global ServerNameInZone false;\
\n\
\n# These addresses are used to send e-mails to. The to-address needs\
\n# to be filled; cc-address can be empty, one address or a comma\
\n# separated list of addresses.\
\n:global EmailGeneralTo \"\";\
\n:global EmailGeneralCc \"\";\
\n#:global EmailGeneralTo \"mail@example.com\";\
\n#:global EmailGeneralCc \"another@example.com,third@example.com\";\
\n\
\n# You can send Telegram notifications. Register a bot\
\n# and add the token and chat ids here.\
\n:global TelegramTokenId \"\";\
\n:global TelegramChatId \"\";\
\n#:global TelegramTokenId \"123456:ABCDEF-GHI\";\
\n#:global TelegramChatId \"12345678\";\
\n# This is whether or not to send Telegram messages with fixed-width font\
.\
\n:global TelegramFixedWidthFont true;\
\n\
\n# It is possible to override e-mail and Telegram setting for every scrip\
t.\
\n# This is done in arrays EmailGeneralToOverride, EmailGeneralCcOverride,\
\n# TelegramTokenIdOverride and TelegramChatIdOverride like this:\
\n#:global EmailGeneralToOverride {\
\n# \"check-certificates\"=\"override@example.com\";\
\n# \"email-backup\"=\"backup@example.com\";\
\n#}\
\n\
\n# Toggle this to disable symbols in notifications.\
\n:global NotificationsWithSymbols true;\
\n# Toggle this to disable color output in terminal/cli.\
\n:global TerminalColorOutput true;\
\n\
\n# This defines what backups to generate and what password to use.\
\n:global BackupSendBinary false;\
\n:global BackupSendExport true;\
\n:global BackupPassword \"v3ry-s3cr3t\";\
\n:global BackupRandomDelay 0;\
\n# These credentials are used to upload backup and config export files.\
\n# SFTP authentication is tricky, you may have to limit authentication\
\n# methods for your SSH server.\
\n:global BackupUploadUrl \"sftp://example.com/backup/\";\
\n:global BackupUploadUser \"mikrotik\";\
\n:global BackupUploadPass \"v3ry-s3cr3t\";\
\n\
\n# This defines a filter on log topics not to be forwarded.\
\n:global LogForwardFilter \"(debug|info)\";\
\n# ... and the same for log message text. Regular expressions are support\
ed.\
\n# Do *NOT* set an empty string - that will filter everything!\
\n:global LogForwardFilterMessage [];\
\n#:global LogForwardFilterMessage \"message text\";\
\n#:global LogForwardFilterMessage \"(message text|another text|...)\";\
\n\
\n# Specify an address to enable auto update to version assumed safe.\
\n# The configured channel (bugfix, current, release-candidate) is appende\
d.\
\n:global SafeUpdateUrl \"\";\
\n#:global SafeUpdateUrl \"https://example.com/ros/safe-update/\";\
\n# Allow to install patch updates automatically.\
\n:global SafeUpdatePatch false;\
\n# Allow to install updates automatically if seen in neighbor list.\
\n:global SafeUpdateNeighbor false;\
\n\
\n# These thresholds control when to send health notification\
\n# on temperature and voltage.\
\n:global CheckHealthTemperature {\
\n temperature=50;\
\n cpu-temperature=70;\
\n board-temperature1=50;\
\n board-temperature2=50;\
\n}\
\n# This is deviation on recovery threshold against notification flooding.\
\n:global CheckHealthTemperatureDeviation 2;\
\n:global CheckHealthVoltagePercent 10;\
\n\
\n# This controls what configuration is activated by bridge-port-to-defaul\
t.\
\n:global BridgePortTo \"default\";\
\n\
\n# Access-list entries matching this comment are updated\
\n# with daily pseudo-random PSK.\
\n:global DailyPskMatchComment \"Daily PSK\";\
\n:global DailyPskSecrets {\
\n { \"Abusive\"; \"Aggressive\"; \"Bored\"; \"Chemical\"; \"Cold\";\
\n \"Cruel\"; \"Curved\"; \"Delightful\"; \"Discreet\"; \"Elite\";\
\n \"Evasive\"; \"Faded\"; \"Flat\"; \"Future\"; \"Grandiose\";\
\n \"Hanging\"; \"Humorous\"; \"Interesting\"; \"Magenta\";\
\n \"Magnificent\"; \"Numerous\"; \"Optimal\"; \"Pathetic\";\
\n \"Possessive\"; \"Remarkable\"; \"Rightful\"; \"Ruthless\";\
\n \"Stale\"; \"Unusual\"; \"Useless\"; \"Various\" };\
\n { \"Adhesive\"; \"Amusing\"; \"Astonishing\"; \"Frantic\";\
\n \"Kindhearted\"; \"Limping\"; \"Roasted\"; \"Robust\";\
\n \"Staking\"; \"Thundering\"; \"Ultra\"; \"Unreal\" };\
\n { \"Belief\"; \"Button\"; \"Curtain\"; \"Edge\"; \"Jewel\";\
\n \"String\"; \"Whistle\" }\
\n}\
\n\
\n# Run different commands with multiple mode-button presses.\
\n:global ModeButton {\
\n 1=\"/ system script run leds-toggle-mode;\";\
\n 2=\":global SendNotification; :global Identity; \\\$SendNotification (\
\\\"Hello...\\\") (\\\"Hello world, \\\" . \\\$Identity . \\\" calling!\\\
\");\";\
\n 3=\"/ system shutdown;\";\
\n 4=\"/ system reboot;\";\
\n 5=\"/ system script run bridge-port-toggle;\";\
\n# add more here...\
\n};\
\n# This led gives visual feedback if type is 'on' or 'off'.\
\n:global ModeButtonLED \"user-led\";\
\n\
\n# Run commands on SMS action.\
\n:global SmsAction {\
\n bridge-port-toggle=\"/ system script run bridge-port-toggle;\";\
\n reboot=\"/ system reboot;\";\
\n shutdown=\"/ system shutdown;\";\
\n# add more here...\
\n};\
\n\
\n# This address should resolve ntp servers and is used to update\
\n# ntp settings. A pool can rotate servers.\
\n:global NtpPool \"pool.ntp.org\";\
\n\
\n# This is the address used to send gps data to.\
\n:global GpsTrackUrl \"https://example.com/index.php\";\
\n\
\n# Enable this to fetch scripts from given url.\
\n:global ScriptUpdatesFetch true;\
\n:global ScriptUpdatesBaseUrl \"https://git.eworm.de/cgit/routeros-script\
s/plain/\";\
\n# alternative urls - main: stable code - next: currently in development\
\n#:global ScriptUpdatesBaseUrl \"https://raw.githubusercontent.com/eworm-\
de/routeros-scripts/main/\";\
\n#:global ScriptUpdatesBaseUrl \"https://raw.githubusercontent.com/eworm-\
de/routeros-scripts/next/\";\
\n#:global ScriptUpdatesBaseUrl \"https://gitlab.com/eworm-de/routeros-scr\
ipts/raw/main/\";\
\n#:global ScriptUpdatesBaseUrl \"https://gitlab.com/eworm-de/routeros-scr\
ipts/raw/next/\";\
\n:global ScriptUpdatesUrlSuffix \"\";\
\n# use next branch with default url (git.eworm.de)\
\n#:global ScriptUpdatesUrlSuffix \"\\\?h=next\";\
\n\
\n# This project is developed in private spare time and usage is free of c\
harge\
\n# for you. If you like the scripts and think this is of value for you or\
\_your\
\n# business please consider a donation:\
\n# https://git.eworm.de/cgit/routeros-scripts/about/#donate\
\n# Enable this to silence donation hint.\
\n:global IDonate false;\
\n\
\n# Use this for certificate auto-renew\
\n:global CertRenewUrl \"\";\
\n#:global CertRenewUrl \"https://example.com/certificates/\";\
\n:global CertRenewTime 3w;\
\n:global CertRenewPass {\
\n \"v3ry-s3cr3t\";\
\n \"4n0th3r-s3cr3t\";\
\n}\
\n:global CertIssuedExportPass {\
\n \"cert1-cn\"=\"v3ry-s3cr3t\";\
\n \"cert2-cn\"=\"4n0th3r-s3cr3t\";\
\n}\
\n"
add dont-require-permissions=no name=global-config-overlay owner=\
global-config-overlay policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
\_Overlay for global configuration by RouterOS Scripts\
\n# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# global configuration, custom overlay\
\n# https://git.eworm.de/cgit/routeros-scripts/about/\
\n\
\n# Make sure all configuration properties are up to date and this\
\n# value is in sync with value in script 'global-functions'!\
\n# Comment or remove to disable news and change notifications.\
\n:global GlobalConfigVersion 47;\
\n\
\n# Copy configuration from global-config here and modify it.\
\n\
\n:global PptpServerIfaceName \"PptpPsh\";\
\n:global PptpServerName \"asdkfeifhdkfks.sn.mynetname.net\";\
\n:global PptpServerIp \"0.0.0.0\";\
\n:global PshStatus 0;\
\n\
\n# This is for PPPoE PPPOE\
\n:global PPPoEIface \"PPPoE\";\
\n:global PPPoEIpPref {111;222;223;224};\
\n\
\n# This is used for DNS and backup file.\
\n:global Domain \"zzzzzzzzzzzz.xx.zz.yy\";\
\n:global HostNameInZone true;\
\n:global PrefixInZone true;\
\n:global ServerNameInZone false;\
\n\
\n# These addresses are used to send e-mails to. The to-address needs\
\n# to be filled; cc-address can be empty, one address or a comma\
\n# separated list of addresses.\
\n:global EmailGeneralTo \"xxxxx@gmail.com\";\
\n:global EmailGeneralCc \"xxxxx@gmail.com\";\
\n#:global EmailGeneralTo \"mail@example.com\";\
\n#:global EmailGeneralCc \"another@example.com,third@example.com\";\
\n\
\n# You can send Telegram notifications. Register a bot\
\n# and add the token and chat ids here.\
\n:global TelegramTokenId \"123456:ABCDEF-GHI\
\";\
\n:global TelegramChatId \"-12345678\";\
\n#:global TelegramTokenId \"123456:ABCDEF-GHI\";\
\n#:global TelegramChatId \"12345678\";\
\n# This is whether or not to send Telegram messages with fixed-width font\
.\
\n:global TelegramFixedWidthFont true;\
\n\
\n# Toggle this to disable symbols in notifications.\
\n:global NotificationsWithSymbols true;\
\n# Toggle this to disable color output in terminal/cli.\
\n:global TerminalColorOutput true;\
\n\
\n# This defines what backups to generate and what password to use.\
\n:global BackupSendBinary true;\
\n:global BackupSendExport true;\
\n:global BackupPassword \"xzxzxzxz\";\
\n:global BackupRandomDelay 0;\
\n# These addresses are used to send backup and config export files to.\
\n:global EmailBackupTo \"xxxxx@gmail.com\";\
\n:global EmailBackupCc \"xxxxx@gmail.com\";\
\n# These credentials are used to upload backup and config export files.\
\n# SFTP authentication is tricky, you may have to limit authentication\
\n# methods for your SSH server.\
\n:global BackupUploadUrl \"sftp://example.com/backup/\";\
\n:global BackupUploadUser \"root\";\
\n:global BackupUploadPass \"xzxzxzxz\";\
\n\
\n# This defines a filter on log topics not to be forwarded.\
\n:global LogForwardFilter \"(debug|info)\";\
\n# ... and the same for log message text. Regular expressions are support\
ed.\
\n# Do *NOT* set an empty string - that will filter everything!\
\n:global LogForwardFilterMessage [];\
\n#:global LogForwardFilterMessage \"message text\";\
\n#:global LogForwardFilterMessage \"(message text|another text|...)\";\
\n\
\n# Specify an address to enable auto update to version assumed safe.\
\n# The configured channel (bugfix, current, release-candidate) is appende\
d.\
\n:global SafeUpdateUrl \"\";\
\n#:global SafeUpdateUrl \"https://example.com/ros/safe-update/\";\
\n# Allow to install patch updates automatically.\
\n:global SafeUpdatePatch false;\
\n# Allow to install updates automatically if seen in neighbor list.\
\n:global SafeUpdateNeighbor false;\
\n\
\n# These thresholds control when to send health notification\
\n# on temperature and voltage.\
\n:global CheckHealthTemperature {\
\n temperature=50;\
\n cpu-temperature=70;\
\n board-temperature1=50;\
\n board-temperature2=50;\
\n}\
\n# This is deviation on recovery threshold against notification flooding.\
\n:global CheckHealthTemperatureDeviation 2;\
\n:global CheckHealthVoltagePercent 10;\
\n\
\n# This controls what configuration is activated by bridge-port-to-defaul\
t.\
\n:global BridgePortTo \"default\";\
\n\
\n# Access-list entries matching this comment are updated\
\n# with daily pseudo-random PSK.\
\n:global DailyPskMatchComment \"Daily PSK\";\
\n:global DailyPskSecrets {\
\n { \"Abusive\"; \"Aggressive\"; \"Bored\"; \"Chemical\"; \"Cold\";\
\n \"Cruel\"; \"Curved\"; \"Delightful\"; \"Discreet\"; \"Elite\";\
\n \"Evasive\"; \"Faded\"; \"Flat\"; \"Future\"; \"Grandiose\";\
\n \"Hanging\"; \"Humorous\"; \"Interesting\"; \"Magenta\";\
\n \"Magnificent\"; \"Numerous\"; \"Optimal\"; \"Pathetic\";\
\n \"Possessive\"; \"Remarkable\"; \"Rightful\"; \"Ruthless\";\
\n \"Stale\"; \"Unusual\"; \"Useless\"; \"Various\" };\
\n { \"Adhesive\"; \"Amusing\"; \"Astonishing\"; \"Frantic\";\
\n \"Kindhearted\"; \"Limping\"; \"Roasted\"; \"Robust\";\
\n \"Staking\"; \"Thundering\"; \"Ultra\"; \"Unreal\" };\
\n { \"Belief\"; \"Button\"; \"Curtain\"; \"Edge\"; \"Jewel\";\
\n \"String\"; \"Whistle\" }\
\n}\
\n\
\n# Run different commands with multiple mode-button presses.\
\n:global ModeButton {\
\n 1=\"/ system script run leds-toggle-mode;\";\
\n 2=\":global SendNotification; :global Identity; \\\$SendNotification (\
\\\"Hello...\\\") (\\\"Hello world, \\\" . \\\$Identity . \\\" calling!\\\
\");\";\
\n 3=\"/ system shutdown;\";\
\n 4=\"/ system reboot;\";\
\n 5=\"/ system script run bridge-port-toggle;\";\
\n# add more here...\
\n};\
\n# This led gives visual feedback if type is 'on' or 'off'.\
\n:global ModeButtonLED \"user-led\";\
\n\
\n# Run commands on SMS action.\
\n:global SmsAction {\
\n bridge-port-toggle=\"/ system script run bridge-port-toggle;\";\
\n reboot=\"/ system reboot;\";\
\n shutdown=\"/ system shutdown;\";\
\n# add more here...\
\n};\
\n\
\n# This address should resolve ntp servers and is used to update\
\n# ntp settings. A pool can rotate servers.\
\n:global NtpPool \"pool.ntp.org\";\
\n\
\n# This is the address used to send gps data to.\
\n:global GpsTrackUrl \"https://example.com/index.php\";\
\n\
\n# Enable this to fetch scripts from given url.\
\n:global ScriptUpdatesFetch true;\
\n:global ScriptUpdatesBaseUrl \"https://git.eworm.de/cgit/routeros-script\
s/plain/\";\
\n# alternative urls - main: stable code - next: currently in development\
\n#:global ScriptUpdatesBaseUrl \"https://raw.githubusercontent.com/eworm-\
de/routeros-scripts/main/\";\
\n#:global ScriptUpdatesBaseUrl \"https://raw.githubusercontent.com/eworm-\
de/routeros-scripts/next/\";\
\n#:global ScriptUpdatesBaseUrl \"https://gitlab.com/eworm-de/routeros-scr\
ipts/raw/main/\";\
\n#:global ScriptUpdatesBaseUrl \"https://gitlab.com/eworm-de/routeros-scr\
ipts/raw/next/\";\
\n:global ScriptUpdatesUrlSuffix \"\";\
\n# use next branch with default url (git.eworm.de)\
\n#:global ScriptUpdatesUrlSuffix \"\\\?h=next\";\
\n\
\n# This project is developed in private spare time and usage is free of c\
harge\
\n# for you. If you like the scripts and think this is of value for you or\
\_your\
\n# business please consider a donation:\
\n# https://git.eworm.de/cgit/routeros-scripts/about/#donate\
\n# Enable this to silence donation hint.\
\n:global IDonate false;\
\n\
\n# Use this for certificate auto-renew\
\n:global CertRenewUrl \"\";\
\n#:global CertRenewUrl \"https://example.com/certificates/\";\
\n:global CertRenewTime 3w;\
\n:global CertRenewPass {\
\n \"xzxzxzxz\";\
\n \"xzxzxzxz\";\
\n}\
\n:global CertIssuedExportPass {\
\n \"cert1-cn\"=\"xzxzxzxz\";\
\n \"cert2-cn\"=\"xzxzxzxz\";\
\n}\
\n\
\n# End of global-config-overlay\
\n"
add dont-require-permissions=no name=global-functions owner=global-functions \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source="#!rsc by RouterOS\
\n# RouterOS script: global-functions\
\n# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>\
\n# Michael Gisbers <michael@gisbers.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# global functions\
\n# https://git.eworm.de/cgit/routeros-scripts/about/\
\n\
\n# expected configuration version\
\n:global ExpectedConfigVersion 52;\
\n\
\n# global variables not to be changed by user\
\n:global GlobalFunctionsReady false;\
\n:global Identity [ / system identity get name ];\
\n\
\n# global functions\
\n:global CertificateAvailable;\
\n:global CertificateDownload;\
\n:global CertificateNameByCN;\
\n:global CharacterReplace;\
\n:global CleanFilePath;\
\n:global DefaultRouteIsReachable;\
\n:global DeviceInfo;\
\n:global DNSIsResolving;\
\n:global DownloadPackage;\
\n:global EitherOr;\
\n:global EscapeForRegEx;\
\n:global FlushEmailQueue;\
\n:global FlushTelegramQueue;\
\n:global GetMacVendor;\
\n:global GetRandom20CharHex;\
\n:global GetRandomNumber;\
\n:global IfThenElse;\
\n:global IPCalc;\
\n:global LogPrintExit;\
\n:global LogPrintExit2;\
\n:global MkDir;\
\n:global ParseKeyValueStore;\
\n:global QuotedPrintable;\
\n:global RandomDelay;\
\n:global RequiredRouterOS;\
\n:global ScriptFromTerminal;\
\n:global ScriptInstallUpdate;\
\n:global ScriptLock;\
\n:global SendEMail;\
\n:global SendEMail2;\
\n:global SendNotification;\
\n:global SendNotification2;\
\n:global SendTelegram;\
\n:global SendTelegram2;\
\n:global SymbolByUnicodeName;\
\n:global SymbolForNotification;\
\n:global TimeIsSync;\
\n:global UrlEncode;\
\n:global ValidateSyntax;\
\n:global VersionToNum;\
\n:global WaitDefaultRouteReachable;\
\n:global WaitDNSResolving;\
\n:global WaitForFile;\
\n:global WaitFullyConnected;\
\n:global WaitTimeSync;\
\n\
\n# check and download required certificate\
\n:set CertificateAvailable do={\
\n :local CommonName [ :tostr \$1 ];\
\n\
\n :global CertificateDownload;\
\n :global LogPrintExit2;\
\n :global ParseKeyValueStore;\
\n :global RequiredRouterOS;\
\n\
\n :if ([ / system resource get free-hdd-space ] < 8388608 && \\\
\n [ / certificate settings get crl-download ] = true && \\\
\n [ / certificate settings get crl-store ] = \"system\") do={\
\n \$LogPrintExit2 warning \$0 (\"This system has low free flash space \
but \" . \\\
\n \"is configured to download certificate CRLs to system!\") false;\
\n }\
\n\
\n :if ([ :len [ / certificate find where common-name=\$CommonName ] ] = \
0) do={\
\n \$LogPrintExit2 info \$0 (\"Certificate with CommonName \\\"\" . \$C\
ommonName . \"\\\" not available.\") false;\
\n :if ([ \$CertificateDownload \$CommonName ] = false) do={\
\n :return false;\
\n }\
\n }\
\n\
\n :if ([ \$RequiredRouterOS \$0 \"6.47\" ] = false) do={\
\n :return true;\
\n }\
\n\
\n :local CertVal [ / certificate get [ find where common-name=\$CommonNa\
me ] ];\
\n :while ((\$CertVal->\"akid\") != \"\" && (\$CertVal->\"akid\") != (\$C\
ertVal->\"skid\")) do={\
\n :if ([ :len [ / certificate find where skid=(\$CertVal->\"akid\") ] \
] = 0) do={\
\n \$LogPrintExit2 info \$0 (\"Certificate chain for \\\"\" . \$Commo\
nName . \\\
\n \"\\\" is incomplete, missing \\\"\" . ([ \$ParseKeyValueStore (\
\$CertVal->\"issuer\") ]->\"CN\") . \"\\\".\") false;\
\n :if ([ \$CertificateDownload \$CommonName ] = false) do={\
\n :return false;\
\n }\
\n }\
\n :set CertVal [ / certificate get [ find where skid=(\$CertVal->\"aki\
d\") ] ];\
\n }\
\n :return true;\
\n}\
\n\
\n# download and import certificate\
\n:set CertificateDownload do={\
\n :local CommonName [ :tostr \$1 ];\
\n\
\n :global ScriptUpdatesBaseUrl;\
\n :global ScriptUpdatesUrlSuffix;\
\n\
\n :global CertificateNameByCN;\
\n :global LogPrintExit2;\
\n :global UrlEncode;\
\n :global WaitForFile;\
\n\
\n \$LogPrintExit2 info \$0 (\"Downloading and importing certificate with\
\_\" . \\\
\n \"CommonName \\\"\" . \$CommonName . \"\\\".\") false;\
\n :do {\
\n :local LocalFileName (\$CommonName . \".pem\");\
\n :local UrlFileName ([ \$UrlEncode \$CommonName ] . \".pem\");\
\n / tool fetch check-certificate=yes-without-crl \\\
\n (\$ScriptUpdatesBaseUrl . \"certs/\" . \\\
\n \$UrlFileName . \$ScriptUpdatesUrlSuffix) \\\
\n dst-path=\$LocalFileName as-value;\
\n \$WaitForFile \$LocalFileName;\
\n / certificate import file-name=\$LocalFileName passphrase=\"\" as-va\
lue;\
\n / file remove \$LocalFileName;\
\n\
\n :foreach Cert in=[ / certificate find where name~(\"^\" . \$LocalFil\
eName . \"_[0-9]+\\\$\") ] do={\
\n \$CertificateNameByCN [ / certificate get \$Cert common-name ];\
\n }\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Failed importing certificate with \" \
. \\\
\n \"CommonName \\\"\" . \$CommonName . \"\\\"!\") false;\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# name a certificate by its common-name\
\n:set CertificateNameByCN do={\
\n :local CommonName [ :tostr \$1 ];\
\n\
\n :global CharacterReplace;\
\n\
\n :local Cert [ / certificate find where common-name=\$CommonName ];\
\n / certificate set \$Cert \\\
\n name=[ \$CharacterReplace [ \$CharacterReplace [ \$CharacterReplace \
\$CommonName \"'\" \"-\" ] \" \" \"-\" ] \"---\" \"-\" ];\
\n}\
\n\
\n# character replace\
\n:set CharacterReplace do={\
\n :local String [ :tostr \$1 ];\
\n :local ReplaceFrom [ :tostr \$2 ];\
\n :local ReplaceWith [ :tostr \$3 ];\
\n :local Return \"\";\
\n\
\n :if (\$ReplaceFrom = \"\") do={\
\n :return \$String;\
\n }\
\n\
\n :while ([ :typeof [ :find \$String \$ReplaceFrom ] ] != \"nil\") do={\
\n :local Pos [ :find \$String \$ReplaceFrom ];\
\n :set Return (\$Return . [ :pick \$String 0 \$Pos ] . \$ReplaceWith);\
\n :set String [ :pick \$String (\$Pos + [ :len \$ReplaceFrom ]) [ :len\
\_\$String ] ];\
\n }\
\n\
\n :return (\$Return . \$String);\
\n}\
\n\
\n# clean file path\
\n:set CleanFilePath do={\
\n :local Path [ :tostr \$1 ];\
\n\
\n :global CharacterReplace;\
\n\
\n :while (\$Path ~ \"//\") do={\
\n :set \$Path [ \$CharacterReplace \$Path \"//\" \"/\" ];\
\n }\
\n :if ([ :pick \$Path 0 ] = \"/\") do={\
\n :set Path [ :pick \$Path 1 [ :len \$Path ] ];\
\n }\
\n :if ([ :pick \$Path ([ :len \$Path ] - 1) ] = \"/\") do={\
\n :set Path [ :pick \$Path 0 ([ :len \$Path ] - 1) ];\
\n }\
\n\
\n :return \$Path;\
\n}\
\n\
\n# default route is reachable\
\n:set DefaultRouteIsReachable do={\
\n :if ([ :len [ / ip route find where dst-address=0.0.0.0/0 active !blac\
khole !routing-mark !unreachable ] ] > 0) do={\
\n :return true;\
\n }\
\n :return false;\
\n}\
\n\
\n# get readable device info\
\n:set DeviceInfo do={\
\n :global ExpectedConfigVersion;\
\n :global GlobalConfigVersion;\
\n :global Identity;\
\n\
\n :global IfThenElse;\
\n\
\n :local Resource [ / system resource get ];\
\n :local RouterBoard [ / system routerboard get ];\
\n :local Update [ / system package update get ];\
\n\
\n :return ( \\\
\n \"Hostname: \" . \$Identity . \\\
\n \"\\nBoard name: \" . \$Resource->\"board-name\" . \\\
\n \"\\nArchitecture: \" . \$Resource->\"architecture-name\" . \\\
\n [ \$IfThenElse (\$RouterBoard->\"routerboard\" = true) \\\
\n (\"\\nModel: \" . \$RouterBoard->\"model\" . \\\
\n [ \$IfThenElse ([ :len (\$RouterBoard->\"revision\") ] > 0) \\\
\n (\" \" . \$RouterBoard->\"revision\") ] . \\\
\n \"\\nSerial number: \" . \$RouterBoard->\"serial-number\") ] . \
\\\
\n \"\\nRouterOS:\" . \\\
\n \"\\n Channel: \" . \$Update->\"channel\" . \\\
\n \"\\n Installed: \" . \$Update->\"installed-version\" . \\\
\n [ \$IfThenElse ([ :typeof (\$Update->\"latest-version\") ] != \"noth\
ing\" && \\\
\n \$Update->\"installed-version\" != \$Update->\"latest-version\")\
\_\\\
\n (\"\\n Available: \" . \$Update->\"latest-version\") ] . \\\
\n \"\\nRouterOS-Scripts:\" . \\\
\n \"\\n Current: \" . \$GlobalConfigVersion . \\\
\n [ \$IfThenElse (\$GlobalConfigVersion != \$ExpectedConfigVersion) \\\
\n (\"\\n Expected: \" . \$ExpectedConfigVersion) ]);\
\n}\
\n\
\n# check if DNS is resolving\
\n:set DNSIsResolving do={\
\n :global CharacterReplace;\
\n\
\n :do {\
\n :resolve \"low-ttl.eworm.de\";\
\n } on-error={\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# download package from upgrade server\
\n:set DownloadPackage do={\
\n :local PkgName [ :tostr \$1 ];\
\n :local PkgVer [ :tostr \$2 ];\
\n :local PkgArch [ :tostr \$3 ];\
\n :local PkgDir [ :tostr \$4 ];\
\n\
\n :global CertificateAvailable;\
\n :global CleanFilePath;\
\n :global LogPrintExit2;\
\n :global WaitForFile;\
\n\
\n :if ([ :len \$PkgName ] = 0) do={ :return false; }\
\n :if ([ :len \$PkgVer ] = 0) do={ :set PkgVer [ / system package upda\
te get installed-version ]; }\
\n :if ([ :len \$PkgArch ] = 0) do={ :set PkgArch [ / system resource get\
\_architecture-name ]; }\
\n\
\n :local PkgFile (\$PkgName . \"-\" . \$PkgVer . \"-\" . \$PkgArch . \".\
npk\");\
\n :if (\$PkgArch = \"x86_64\" || \$PkgName ~ \"^routeros-\") do={\
\n :set PkgFile (\$PkgName . \"-\" . \$PkgVer . \".npk\");\
\n }\
\n :local PkgDest [ \$CleanFilePath (\$PkgDir . \"/\" . \$PkgFile) ];\
\n\
\n :if ([ :len [ / file find where name=\$PkgDest type=\"package\" ] ] > \
0) do={\
\n \$LogPrintExit2 info \$0 (\"Package file \" . \$PkgName . \" already\
\_exists.\") false;\
\n :return true;\
\n }\
\n\
\n :if ([ \$CertificateAvailable \"R3\" ] = false) do={\
\n \$LogPrintExit2 error \$0 (\"Downloading required certificate failed\
.\") true;\
\n }\
\n\
\n \$LogPrintExit2 info \$0 (\"Downloading package file '\" . \$PkgName .\
\_\"'...\") false;\
\n :local Retry 3;\
\n :while (\$Retry > 0) do={\
\n :do {\
\n / tool fetch check-certificate=yes-without-crl \\\
\n (\"https://upgrade.mikrotik.com/routeros/\" . \$PkgVer . \"/\" .\
\_\$PkgFile) \\\
\n dst-path=\$PkgDest;\
\n \$WaitForFile \$PkgDest;\
\n\
\n :if ([ / file get [ find where name=\$PkgDest ] type ] = \"package\
\") do={\
\n :return true;\
\n }\
\n } on-error={\
\n \$LogPrintExit2 debug \$0 (\"Downloading package file failed.\") f\
alse;\
\n }\
\n\
\n / file remove [ find where name=\$PkgDest ];\
\n :set Retry (\$Retry - 1);\
\n }\
\n\
\n \$LogPrintExit2 warning \$0 (\"Downloading package file '\" . \$PkgNam\
e . \"' failed.\") false;\
\n :return false;\
\n}\
\n\
\n# return either first (if \"true\") or second\
\n:set EitherOr do={\
\n :global IfThenElse;\
\n\
\n :if ([ :typeof \$1 ] = \"num\") do={\
\n :return [ \$IfThenElse (\$1 != 0) \$1 \$2 ];\
\n }\
\n :return [ \$IfThenElse ([ :len [ :tostr \$1 ] ] > 0) \$1 \$2 ];\
\n}\
\n\
\n# escape for regular expression\
\n:set EscapeForRegEx do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :if ([ :len \$Input ] = 0) do={\
\n :return \"\";\
\n }\
\n\
\n :local Return \"\";\
\n :local Chars \"^.[]\\\$()|*+\\\?{}\\\\\";\
\n\
\n :for I from=0 to=([ :len \$Input ] - 1) do={\
\n :local Char [ :pick \$Input \$I ];\
\n :if ([ :find \$Chars \$Char ]) do={\
\n :set Char (\"\\\\\" . \$Char);\
\n }\
\n :set Return (\$Return . \$Char);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# flush e-mail queue\
\n:set FlushEmailQueue do={\
\n :global EmailQueue;\
\n\
\n :global EitherOr;\
\n :global LogPrintExit2;\
\n\
\n :local AllDone true;\
\n :local QueueLen [ :len \$EmailQueue ];\
\n\
\n :if ([ :len [ / system scheduler find where name=\"FlushEmailQueue\" ]\
\_] > 0 && \$QueueLen = 0) do={\
\n \$LogPrintExit2 warning \$0 (\"Flushing E-Mail messages from schedul\
er, but queue is empty.\") false;\
\n }\
\n\
\n / system scheduler set interval=(\$QueueLen . \"m\") [ find where name\
=\"FlushEmailQueue\" ];\
\n\
\n :foreach Id,Message in=\$EmailQueue do={\
\n :if ([ :typeof \$Message ] = \"array\" ) do={\
\n / tool e-mail send to=(\$Message->\"to\") cc=(\$Message->\"cc\") s\
ubject=(\$Message->\"subject\") \\\
\n body=(\$Message->\"body\") file=[ \$EitherOr (\$Message->\"attac\
h\") \"\" ];\
\n :local Wait true;\
\n :do {\
\n :delay 1s;\
\n :local Status [ / tool e-mail get last-status ];\
\n :if (\$Status = \"succeeded\") do={\
\n :set (\$EmailQueue->\$Id);\
\n :set Wait false;\
\n }\
\n :if (\$Status = \"failed\") do={\
\n :set AllDone false;\
\n :set Wait false;\
\n }\
\n } while=(\$Wait = true);\
\n }\
\n }\
\n\
\n :if (\$AllDone = true && \$QueueLen = [ :len \$EmailQueue ]) do={\
\n / system scheduler remove [ find where name=\"FlushEmailQueue\" ];\
\n :set EmailQueue;\
\n } else={\
\n / system scheduler set interval=1m [ find where name=\"FlushEmailQue\
ue\" ];\
\n }\
\n}\
\n\
\n# flush telegram queue\
\n:set FlushTelegramQueue do={\
\n :global TelegramQueue;\
\n\
\n :global LogPrintExit2;\
\n\
\n :local AllDone true;\
\n :local QueueLen [ :len \$TelegramQueue ];\
\n\
\n :if ([ :len [ / system scheduler find where name=\"FlushTelegramQueue\
\" ] ] > 0 && \$QueueLen = 0) do={\
\n \$LogPrintExit2 warning \$0 (\"Flushing Telegram messages from sched\
uler, but queue is empty.\") false;\
\n }\
\n\
\n :foreach Id,Message in=\$TelegramQueue do={\
\n :if ([ :typeof \$Message ] = \"array\" ) do={\
\n :do {\
\n / tool fetch check-certificate=yes-without-crl output=none http-\
method=post \\\
\n (\"https://api.telegram.org/bot\" . (\$Message->\"tokenid\") .\
\_\"/sendMessage\") \\\
\n http-data=(\"chat_id=\" . (\$Message->\"chatid\") . \\\
\n \"&disable_notification=\" . (\$Message->\"silent\") . \\\
\n \"&disable_web_page_preview=true&parse_mode=\" . (\$Message->\
\"parsemode\") . \\\
\n \"&text=\" . (\$Message->\"text\")) as-value;\
\n :set (\$TelegramQueue->\$Id);\
\n } on-error={\
\n \$LogPrintExit2 debug \$0 (\"Sending queued Telegram message fai\
led.\") false;\
\n :set AllDone false;\
\n }\
\n }\
\n }\
\n\
\n :if (\$AllDone = true && \$QueueLen = [ :len \$TelegramQueue ]) do={\
\n / system scheduler remove [ find where name=\"FlushTelegramQueue\" ]\
;\
\n :set TelegramQueue;\
\n }\
\n}\
\n\
\n# get MAC vendor\
\n:set GetMacVendor do={\
\n :local Mac [ :tostr \$1 ];\
\n\
\n :global CertificateAvailable;\
\n :global LogPrintExit2;\
\n\
\n :do {\
\n :if ([ \$CertificateAvailable \"Cloudflare Inc ECC CA-3\" ] = false)\
\_do={\
\n \$LogPrintExit2 warning \$0 (\"Downloading required certificate fa\
iled.\") true;\
\n }\
\n :local Vendor ([ / tool fetch check-certificate=yes-without-crl \\\
\n (\"https://api.macvendors.com/\" . [ :pick \$Mac 0 8 ]) output=u\
ser as-value ]->\"data\");\
\n :return \$Vendor;\
\n } on-error={\
\n :do {\
\n / tool fetch check-certificate=yes-without-crl (\"https://api.macv\
endors.com/\") \\\
\n output=none as-value;\
\n \$LogPrintExit2 debug \$0 (\"The mac vendor is not known in databa\
se.\") false;\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Failed getting mac vendor.\") false\
;\
\n }\
\n :return \"unknown vendor\";\
\n }\
\n}\
\n\
\n# generate random 20 chars hex (0-9 and a-f)\
\n:set GetRandom20CharHex do={\
\n :local Random ([ / certificate scep-server otp generate minutes-valid=\
0 as-value ]->\"password\");\
\n / certificate scep-server otp remove [ find where password=\$Random ];\
\n :return \$Random;\
\n}\
\n\
\n# generate random number\
\n:set GetRandomNumber do={\
\n :local Max 4294967295;\
\n :if ([ :typeof \$1 ] != \"nothing\" ) do={\
\n :set Max ([ :tonum \$1 ] + 1);\
\n }\
\n\
\n :global GetRandom20CharHex;\
\n\
\n :local Num;\
\n :local 40CharHex ([ \$GetRandom20CharHex ] . [ \$GetRandom20CharHex ])\
;\
\n\
\n :for I from=0 to=39 do={\
\n :local Char [ :pick \$40CharHex \$I ];\
\n :if (\$Char~\"[0-9]\") do={\
\n :set Num (\$Num . \$Char);\
\n }\
\n }\
\n\
\n :return ([ :tonum [ :pick \$Num 0 18 ] ] % \$Max);\
\n}\
\n\
\n# mimic conditional/ternary operator (condition \? consequent : alternat\
ive)\
\n:set IfThenElse do={\
\n :if ([ :tostr \$1 ] = \"true\" || [ :tobool \$1 ] = true) do={\
\n :return \$2;\
\n }\
\n :return \$3;\
\n}\
\n\
\n# calculate and print netmask, network, min host, max host and broadcast\
\n:set IPCalc do={\
\n :local Input [ :tostr \$1 ];\
\n :local Address [ :toip [ :pick \$Input 0 [ :find \$Input \"/\" ] ] ];\
\n :local Bits [ :tonum [ :pick \$Input ([ :find \$Input \"/\" ] + 1) [ :\
len \$Input ] ] ];\
\n :local Mask ((255.255.255.255 << (32 - \$Bits)) & 255.255.255.255);\
\n\
\n :local Return {\
\n \"address\"=\$Address;\
\n \"netmask\"=\$Mask;\
\n \"networkaddress\"=(\$Address & \$Mask);\
\n \"networkbits\"=\$Bits;\
\n \"network\"=((\$Address & \$Mask) . \"/\" . \$Bits);\
\n \"hostmin\"=((\$Address & \$Mask) | 0.0.0.1);\
\n \"hostmax\"=((\$Address | ~\$Mask) ^ 0.0.0.1);\
\n \"broadcast\"=(\$Address | ~\$Mask);\
\n }\
\n\
\n :put ( \\\
\n \"Address: \" . \$Return->\"address\" . \"\\n\\r\" . \\\
\n \"Netmask: \" . \$Return->\"netmask\" . \"\\n\\r\" . \\\
\n \"Network: \" . \$Return->\"network\" . \"\\n\\r\" . \\\
\n \"HostMin: \" . \$Return->\"hostmin\" . \"\\n\\r\" . \\\
\n \"HostMax: \" . \$Return->\"hostmax\" . \"\\n\\r\" . \\\
\n \"Broadcast: \" . \$Return->\"broadcast\");\
\n\
\n :return \$Return;\
\n}\
\n\
\n# deprecated compatibility wrapper\
\n:set LogPrintExit do={\
\n :global LogPrintExit2;\
\n\
\n \$LogPrintExit2 warning \$0 (\"This function is deprecated. Please use\
\_\\\$LogPrintExit2 instead.\") false;\
\n \$LogPrintExit2 \$1 \"unknown\" \$2 \$3;\
\n}\
\n\
\n# log and print with same text, optionally exit\
\n:set LogPrintExit2 do={\
\n :local Severity [ :tostr \$1 ];\
\n :local Name [ :tostr \$2 ];\
\n :local Message [ :tostr \$3 ];\
\n :local Exit [ :tostr \$4 ];\
\n\
\n :global PrintDebug;\
\n\
\n :local PrintSeverity do={\
\n :global TerminalColorOutput;\
\n\
\n :if (\$TerminalColorOutput != true) do={\
\n :return \$1;\
\n }\
\n\
\n :local Color { debug=96; info=97; warning=93; error=91 };\
\n :return (\"\\1B[\" . \$Color->\$1 . \"m\" . \$1 . \"\\1B[0m\");\
\n }\
\n\
\n :local Log (\$Name . \": \" . \$Message);\
\n :if (\$Severity ~ \"^(debug|error|info)\\\$\") do={\
\n :if (\$Severity = \"debug\") do={ :log debug \$Log; }\
\n :if (\$Severity = \"error\") do={ :log error \$Log; }\
\n :if (\$Severity = \"info\" ) do={ :log info \$Log; }\
\n } else={\
\n :log warning \$Log;\
\n :set Severity \"warning\";\
\n }\
\n\
\n :if (\$Severity != \"debug\" || \$PrintDebug = true) do={\
\n :if (\$Exit = \"true\") do={\
\n :error ([ \$PrintSeverity \$Severity ] . \": \" . \$Message);\
\n } else={\
\n :put ([ \$PrintSeverity \$Severity ] . \": \" . \$Message);\
\n }\
\n }\
\n}\
\n\
\n# create directory\
\n:set MkDir do={\
\n :local Dir [ :tostr \$1 ];\
\n\
\n :global CleanFilePath;\
\n :global WaitForFile;\
\n\
\n :set Dir [ \$CleanFilePath \$Dir ];\
\n\
\n :if ([ :len [ / file find where name=\$Dir type=\"directory\" ] ] = 1)\
\_do={\
\n :return true;\
\n }\
\n\
\n :local Return true;\
\n :local WwwVal [ / ip service get www ];\
\n / ip service set www address=127.0.0.1/32 disabled=no port=80;\
\n :do {\
\n / tool fetch http://127.0.0.1/ dst-path=(\$Dir . \"/tmp\") as-value;\
\n \$WaitForFile (\$Dir . \"/tmp\");\
\n / file remove (\$Dir . \"/tmp\");\
\n } on-error={\
\n :set Return false;\
\n }\
\n / ip service set www address=(\$WwwVal->\"address\") \\\
\n disabled=(\$WwwVal->\"disabled\") port=(\$WwwVal->\"port\");\
\n :return \$Return;\
\n}\
\n\
\n# parse key value store\
\n:set ParseKeyValueStore do={\
\n :local Source \$1;\
\n :if ([ :typeof \$Source ] != \"array\") do={\
\n :set Source [ :tostr \$1 ];\
\n }\
\n :local Result [ :toarray \"\" ];\
\n :foreach KeyValue in=[ :toarray \$Source ] do={\
\n :if ([ :find \$KeyValue \"=\" ]) do={\
\n :set (\$Result->[ :pick \$KeyValue 0 [ :find \$KeyValue \"=\" ] ])\
\_\\\
\n [ :pick \$KeyValue ([ :find \$KeyValue \"=\" ] + 1) [ :len \$Key\
Value ] ];\
\n } else={\
\n :set (\$Result->\$KeyValue) true;\
\n }\
\n }\
\n :return \$Result;\
\n}\
\n\
\n# convert string to quoted-printable\
\n:global QuotedPrintable do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :if ([ :len \$Input ] = 0) do={\
\n :return \$Input;\
\n }\
\n\
\n :local Return \"\";\
\n :local Chars (\"\\80\\81\\82\\83\\84\\85\\86\\87\\88\\89\\8A\\8B\\8C\\\
8D\\8E\\8F\\90\\91\\92\\93\\94\\95\\96\\97\" . \\\
\n \"\\98\\99\\9A\\9B\\9C\\9D\\9E\\9F\\A0\\A1\\A2\\A3\\A4\\A5\\A6\\A7\\\
A8\\A9\\AA\\AB\\AC\\AD\\AE\\AF\\B0\\B1\\B2\\B3\" . \\\
\n \"\\B4\\B5\\B6\\B7\\B8\\B9\\BA\\BB\\BC\\BD\\BE\\BF\\C0\\C1\\C2\\C3\\\
C4\\C5\\C6\\C7\\C8\\C9\\CA\\CB\\CC\\CD\\CE\\CF\" . \\\
\n \"\\D0\\D1\\D2\\D3\\D4\\D5\\D6\\D7\\D8\\D9\\DA\\DB\\DC\\DD\\DE\\DF\\\
E0\\E1\\E2\\E3\\E4\\E5\\E6\\E7\\E8\\E9\\EA\\EB\" . \\\
\n \"\\EC\\ED\\EE\\EF\\F0\\F1\\F2\\F3\\F4\\F5\\F6\\F7\\F8\\F9\\FA\\FB\\\
FC\\FD\\FE\\FF\");\
\n :local Hex { \"0\"; \"1\"; \"2\"; \"3\"; \"4\"; \"5\"; \"6\"; \"7\"; \
\"8\"; \"9\"; \"A\"; \"B\"; \"C\"; \"D\"; \"E\"; \"F\" };\
\n\
\n :for I from=0 to=([ :len \$Input ] - 1) do={\
\n :local Char [ :pick \$Input \$I ];\
\n :local Replace [ :find \$Chars \$Char ];\
\n\
\n :if (\$Char = \"=\") do={\
\n :set Char \"=3D\";\
\n }\
\n :if ([ :typeof \$Replace ] = \"num\") do={\
\n :set Char (\"=\" . (\$Hex->(\$Replace / 16 + 8)) . (\$Hex->(\$Repl\
ace % 16)));\
\n }\
\n :set Return (\$Return . \$Char);\
\n }\
\n\
\n :if (\$Input = \$Return) do={\
\n :return \$Input;\
\n }\
\n\
\n :return (\"=\\\?utf-8\\\?Q\\\?\" . \$Return . \"\\\?=\");\
\n}\
\n\
\n# delay a random amount of seconds\
\n:set RandomDelay do={\
\n :global GetRandomNumber;\
\n\
\n :delay ([ \$GetRandomNumber \$1 ] . \"s\");\
\n}\
\n\
\n# check for required RouterOS version\
\n:set RequiredRouterOS do={\
\n :local Caller [ :tostr \$1 ];\
\n :local Required [ :tostr \$2 ];\
\n\
\n :global IfThenElse;\
\n :global LogPrintExit2;\
\n :global VersionToNum;\
\n\
\n :if ([ \$VersionToNum \$Required ] > [ \$VersionToNum [ / system packa\
ge update get installed-version ] ]) do={\
\n \$LogPrintExit2 warning \$0 (\"This \" . [ \$IfThenElse ([ :pick \$C\
aller 0 ] = \"\\\$\") \"function\" \"script\" ] . \\\
\n \" '\" . \$Caller . \"' (at least specific functionality) requires\
\_RouterOS \" . \$Required . \". Please update!\") false;\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# check if script is run from terminal\
\n:set ScriptFromTerminal do={\
\n :local Script [ :tostr \$1 ];\
\n\
\n :global LogPrintExit2;\
\n\
\n :foreach Job in=[ / system script job find where script=\$Script ] do=\
{\
\n :set Job [ / system script job get \$Job ];\
\n :while ([ :typeof (\$Job->\"parent\") ] = \"id\") do={\
\n :set Job [ / system script job get [ find where .id=(\$Job->\"pare\
nt\") ] ];\
\n }\
\n :if ((\$Job->\"type\") = \"login\") do={\
\n \$LogPrintExit2 debug \$0 (\"Script \" . \$Script . \" started fro\
m terminal.\") false;\
\n :return true;\
\n }\
\n }\
\n \$LogPrintExit2 debug \$0 (\"Script \" . \$Script . \" NOT started fro\
m terminal.\") false;\
\n\
\n :return false;\
\n}\
\n\
\n# install new scripts, update existing scripts\
\n:set ScriptInstallUpdate do={\
\n :local Scripts [ :toarray \$1 ];\
\n\
\n :global ExpectedConfigVersion;\
\n :global GlobalConfigVersion;\
\n :global Identity;\
\n :global IDonate;\
\n :global NotificationsWithSymbols;\
\n :global ScriptUpdatesBaseUrl;\
\n :global ScriptUpdatesFetch;\
\n :global ScriptUpdatesUrlSuffix;\
\n :global SentConfigChangesNotification;\
\n\
\n :global CertificateAvailable;\
\n :global IfThenElse;\
\n :global LogPrintExit2;\
\n :global ParseKeyValueStore;\
\n :global ScriptInstallUpdate;\
\n :global SendNotification2;\
\n :global SymbolForNotification;\
\n :global ValidateSyntax;\
\n\
\n :if ([ \$CertificateAvailable \"R3\" ] = false) do={\
\n \$LogPrintExit2 warning \$0 (\"Downloading certificate failed, tryin\
g without.\") false;\
\n }\
\n\
\n :foreach Script in=\$Scripts do={\
\n :if ([ :len [ / system script find where name=\$Script ] ] = 0) do={\
\n \$LogPrintExit2 info \$0 (\"Adding new script: \" . \$Script) fals\
e;\
\n / system script add name=\$Script source=\"#!rsc by RouterOS\\n\";\
\n }\
\n }\
\n\
\n :local ExpectedConfigVersionBefore \$ExpectedConfigVersion;\
\n :local ScriptInstallUpdateBefore [ :tostr \$ScriptInstallUpdate ];\
\n\
\n :foreach Script in=[ / system script find where source~\"^#!rsc( by Ro\
uterOS)\\\?\\n\" ] do={\
\n :local ScriptVal [ / system script get \$Script ];\
\n :local ScriptFile [ / file find where name=(\"script-updates/\" . \$\
ScriptVal->\"name\") ];\
\n :local SourceNew;\
\n :if ([ :len \$ScriptFile ] > 0) do={\
\n :set SourceNew [ / file get \$ScriptFile content ];\
\n / file remove \$ScriptFile;\
\n }\
\n\
\n :foreach Scheduler in=[ / system scheduler find where on-event~(\"\\\
\\b\" . \$ScriptVal->\"name\" . \"\\\\b\") ] do={\
\n :local SchedulerVal [ / system scheduler get \$Scheduler ];\
\n :if (\$ScriptVal->\"policy\" != \$SchedulerVal->\"policy\") do={\
\n \$LogPrintExit2 warning \$0 (\"Policies differ for script \" . \
\$ScriptVal->\"name\" . \\\
\n \" and its scheduler \" . \$SchedulerVal->\"name\" . \"!\") fa\
lse;\
\n }\
\n }\
\n\
\n :if ([ :len \$SourceNew ] = 0 && \$ScriptUpdatesFetch = true) do={\
\n :local Comment [ \$ParseKeyValueStore (\$ScriptVal->\"comment\") ]\
;\
\n :if (!(\$Comment->\"ignore\" = true)) do={\
\n \$LogPrintExit2 debug \$0 (\"Fetching script from url: \" . \$Sc\
riptVal->\"name\") false;\
\n :do {\
\n :local BaseUrl \$ScriptUpdatesBaseUrl;\
\n :local UrlSuffix \$ScriptUpdatesUrlSuffix;\
\n :if ([ :typeof (\$Comment->\"base-url\") ] = \"str\") do={ :se\
t BaseUrl (\$Comment->\"base-url\"); }\
\n :if ([ :typeof (\$Comment->\"url-suffix\") ] = \"str\") do={ :\
set UrlSuffix (\$Comment->\"url-suffix\"); }\
\n\
\n :local Result [ / tool fetch check-certificate=yes-without-crl\
\_\\\
\n (\$BaseUrl . \$ScriptVal->\"name\" . \$UrlSuffix) output=u\
ser as-value ];\
\n :if (\$Result->\"status\" = \"finished\") do={\
\n :set SourceNew (\$Result->\"data\");\
\n }\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Failed fetching \" . \$ScriptVa\
l->\"name\") false;\
\n }\
\n }\
\n }\
\n\
\n :if ([ :len \$SourceNew ] > 0) do={\
\n :if (\$SourceNew != \$ScriptVal->\"source\") do={\
\n :if ([ :pick \$SourceNew 0 18 ] = \"#!rsc by RouterOS\\n\") do={\
\n :if ([ \$ValidateSyntax \$SourceNew ] = true) do={\
\n :local DontRequirePermissions \\\
\n (\$SourceNew~\"\\n# requires: dont-require-permissions=y\
es\\n\");\
\n \$LogPrintExit2 info \$0 (\"Updating script: \" . \$ScriptVa\
l->\"name\") false;\
\n / system script set owner=(\$ScriptVal->\"name\") source=\$S\
ourceNew \\\
\n dont-require-permissions=\$DontRequirePermissions \$Scri\
pt;\
\n :if (\$ScriptVal->\"name\" = \"global-config\") do={\
\n \$LogPrintExit2 info \$0 (\"Reloading global configuration\
\_and overlay.\") false;\
\n :do {\
\n / system script { run global-config; run global-config-o\
verlay; }\
\n } on-error={\
\n \$LogPrintExit2 error \$0 (\"Reloading global configurat\
ion and overlay failed!\" . \\\
\n \" Syntax error or missing overlay\\\?\") false;\
\n }\
\n }\
\n :if (\$ScriptVal->\"name\" = \"global-functions\") do={\
\n \$LogPrintExit2 info \$0 (\"Reloading global functions.\")\
\_false;\
\n :do {\
\n / system script run global-functions;\
\n } on-error={\
\n \$LogPrintExit2 error \$0 (\"Reloading global functions \
failed!\") false;\
\n }\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"Syntax validation for script \
\" . \$ScriptVal->\"name\" . \\\
\n \" failed! Ignoring!\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"Looks like new script \" . \$Sc\
riptVal->\"name\" . \\\
\n \" is not valid (missing shebang). Ignoring!\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 debug \$0 (\"Script \" . \$ScriptVal->\"name\" \
. \" did not change.\") false;\
\n }\
\n } else={\
\n \$LogPrintExit2 debug \$0 (\"No update for script \" . \$ScriptVal\
->\"name\" . \".\") false;\
\n }\
\n }\
\n\
\n :if (\$ExpectedConfigVersionBefore != \$ExpectedConfigVersion) do={\
\n :global GlobalConfigChanges;\
\n :global GlobalConfigMigration;\
\n :local ChangeLogCode;\
\n\
\n \$LogPrintExit2 debug \$0 (\"Fetching news, changes and migration.\"\
) false;\
\n :do {\
\n :local Result [ / tool fetch check-certificate=yes-without-crl \\\
\n (\$ScriptUpdatesBaseUrl . \"global-config.changes\" . \$Script\
UpdatesUrlSuffix) \\\
\n output=user as-value ];\
\n :if (\$Result->\"status\" = \"finished\") do={\
\n :set ChangeLogCode (\$Result->\"data\");\
\n }\
\n } on-error={\
\n \$LogPrintExit2 warning \$0 (\"Failed fetching news, changes and m\
igration!\") false;\
\n }\
\n\
\n :if ([ :len \$ChangeLogCode ] > 0) do={\
\n :if ([ \$ValidateSyntax \$ChangeLogCode ] = true) do={\
\n [ :parse \$ChangeLogCode ];\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"The changelog failed syntax valid\
ation!\") false;\
\n }\
\n }\
\n\
\n :if ([ :len \$GlobalConfigMigration ] > 0) do={\
\n :for I from=(\$ExpectedConfigVersionBefore + 1) to=\$ExpectedConfi\
gVersion do={\
\n :local Migration (\$GlobalConfigMigration->[ :tostr \$I ]);\
\n :if ([ :typeof \$Migration ] = \"str\") do={\
\n :if ([ \$ValidateSyntax \$Migration ] = true) do={\
\n \$LogPrintExit2 info \$0 (\"Applying migration for change \"\
\_. \$I . \": \" . \$Migration) false;\
\n [ :parse \$Migration ];\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"Migration code for change \" \
. \$I . \" failed syntax validation!\") false;\
\n }\
\n }\
\n }\
\n }\
\n\
\n :if (\$SentConfigChangesNotification != \$ExpectedConfigVersion && \
\\\
\n \$GlobalConfigVersion < \$ExpectedConfigVersion) do={\
\n :local NotificationMessage (\"Current configuration on \" . \$Iden\
tity . \\\
\n \" is out of date. Please update global-config-overlay, then i\
ncrease \" . \\\
\n \"\\\$GlobalConfigVersion (currently \" . \$GlobalConfigVersio\
n . \\\
\n \") to \" . \$ExpectedConfigVersion . \" and re-run global-con\
fig-overlay.\");\
\n \$LogPrintExit2 info \$0 (\$NotificationMessage) false;\
\n\
\n :if ([ :len \$GlobalConfigChanges ] > 0) do={\
\n :set NotificationMessage (\$NotificationMessage . \"\\n\\nChange\
s:\");\
\n :for I from=(\$GlobalConfigVersion + 1) to=\$ExpectedConfigVersi\
on do={\
\n :local Change (\$GlobalConfigChanges->[ :tostr \$I ]);\
\n :set NotificationMessage (\$NotificationMessage . \"\\n \" . \
\\\
\n [ \$IfThenElse (\$NotificationsWithSymbols = true) (\"\\E2\
\\97\\8F\") \"*\" ] . \" \" . \$Change);\
\n \$LogPrintExit2 info \$0 (\"Change \" . \$I . \": \" . \$Chang\
e) false;\
\n }\
\n } else={\
\n :set NotificationMessage (\$NotificationMessage . \"\\n\\nNews a\
nd changes are not available.\");\
\n }\
\n\
\n :local Link;\
\n :if (\$IDonate != true) do={\
\n :set NotificationMessage (\$NotificationMessage . \\\
\n \"\\n\\n==== donation hint ====\\n\" . \\\
\n \"This project is developed in private spare time and usage is\
\_\" . \\\
\n \"free of charge for you. If you like the scripts and think th\
is is \" . \\\
\n \"of value for you or your business please consider a donation\
.\");\
\n :set Link \"https://git.eworm.de/cgit/routeros-scripts/about/#do\
nate\";\
\n }\
\n\
\n \$SendNotification2 ({ origin=\$0; \\\
\n subject=([ \$SymbolForNotification \"pushpin\" ] . \"News and co\
nfiguration changes\"); \\\
\n message=\$NotificationMessage; link=\$Link });\
\n :set SentConfigChangesNotification \$ExpectedConfigVersion;\
\n }\
\n\
\n :set GlobalConfigChanges;\
\n :set GlobalConfigMigration;\
\n }\
\n\
\n :if (\$ScriptInstallUpdateBefore != [ :tostr \$ScriptInstallUpdate ]) \
do={\
\n \$LogPrintExit2 info \$0 (\"This function changed, you may want to r\
e-run.\") false;\
\n }\
\n}\
\n\
\n# lock script against multiple invocation\
\n:set ScriptLock do={\
\n :global LogPrintExit2;\
\n\
\n :local Script [ :tostr \$1 ];\
\n\
\n :if ([ :len [ / system script job find where script=\$Script ] ] > 1) \
do={\
\n \$LogPrintExit2 info \$0 (\"Script \" . \$Script . \" started more t\
han once... Aborting.\") true;\
\n }\
\n}\
\n\
\n# send notification via e-mail - expects at lease two string arguments\
\n:set SendEMail do={\
\n :global SendEMail2;\
\n\
\n \$SendEMail2 ({ subject=\$1; message=\$2; link=\$3 });\
\n}\
\n\
\n# send notification via e-mail - expects one array argument\
\n:set SendEMail2 do={\
\n :local Notification \$1;\
\n\
\n :global Identity;\
\n :global EmailGeneralTo;\
\n :global EmailGeneralToOverride;\
\n :global EmailGeneralCc;\
\n :global EmailGeneralCcOverride;\
\n :global EmailQueue;\
\n\
\n :global EitherOr;\
\n :global IfThenElse;\
\n :global LogPrintExit2;\
\n :global QuotedPrintable;\
\n\
\n :local To [ \$EitherOr (\$EmailGeneralToOverride->(\$Notification->\"o\
rigin\")) \$EmailGeneralTo ];\
\n :local Cc [ \$EitherOr (\$EmailGeneralCcOverride->(\$Notification->\"o\
rigin\")) \$EmailGeneralCc ];\
\n\
\n :if ([ :len \$To ] = 0) do={\
\n :return false;\
\n }\
\n\
\n :if ([ :typeof \$EmailQueue ] = \"nothing\") do={\
\n :set EmailQueue [ :toarray \"\" ];\
\n }\
\n :local Signature [ / system note get note ];\
\n :set (\$EmailQueue->[ :len \$EmailQueue ]) {\
\n to=\$To; cc=\$Cc;\
\n subject=[ \$QuotedPrintable (\"[\" . \$Identity . \"] \" . (\$Notifi\
cation->\"subject\")) ];\
\n body=((\$Notification->\"message\") . \\\
\n [ \$IfThenElse ([ :len (\$Notification->\"link\") ] > 0) (\"\\n\\n\
\" . (\$Notification->\"link\")) \"\" ] . \\\
\n [ \$IfThenElse ([ :len \$Signature ] > 0) (\"\\n-- \\n\" . \$Signa\
ture) \"\" ]); \\\
\n attach=(\$Notification->\"attach\") };\
\n :if ([ :len [ / system scheduler find where name=\"FlushEmailQueue\" ]\
\_] = 0) do={\
\n / system scheduler add name=FlushEmailQueue interval=1s start-time=s\
tartup \\\
\n on-event=\":global FlushEmailQueue; \\\$FlushEmailQueue;\";\
\n }\
\n}\
\n\
\n# send notification via e-mail and telegram - expects at lease two strin\
g arguments\
\n:set SendNotification do={\
\n :global SendNotification2;\
\n\
\n \$SendNotification2 ({ subject=\$1; message=\$2; link=\$3; silent=\$4 \
});\
\n}\
\n\
\n# send notification via e-mail and telegram - expects one array argument\
\n:set SendNotification2 do={\
\n :local Notification \$1;\
\n\
\n :global SendEMail2;\
\n :global SendTelegram2;\
\n\
\n \$SendEMail2 \$Notification;\
\n \$SendTelegram2 \$Notification;\
\n}\
\n\
\n# send notification via telegram - expects at lease two string arguments\
\n:set SendTelegram do={\
\n :global SendTelegram2;\
\n\
\n \$SendTelegram2 ({ subject=\$1; message=\$2; link=\$3; silent=\$4 });\
\n}\
\n\
\n# send notification via telegram - expects one array argument\
\n:set SendTelegram2 do={\
\n :local Notification \$1;\
\n\
\n :global Identity;\
\n :global TelegramChatId;\
\n :global TelegramChatIdOverride;\
\n :global TelegramFixedWidthFont;\
\n :global TelegramQueue;\
\n :global TelegramTokenId;\
\n :global TelegramTokenIdOverride;\
\n\
\n :global CertificateAvailable;\
\n :global CharacterReplace;\
\n :global EitherOr;\
\n :global IfThenElse;\
\n :global LogPrintExit2;\
\n :global SymbolForNotification;\
\n :global UrlEncode;\
\n\
\n :local EscapeMD do={\
\n :global TelegramFixedWidthFont;\
\n\
\n :global CharacterReplace;\
\n :global IfThenElse;\
\n\
\n :if (\$TelegramFixedWidthFont != true) do={\
\n :return (\$1 . [ \$IfThenElse (\$2 = \"body\") \"\\n\" \"\" ]);\
\n }\
\n\
\n :local Return \$1;\
\n :local Chars {\
\n \"body\"={ \"\\\\\"; \"`\" };\
\n \"hint\"={ \"_\"; \"*\"; \"[\"; \"]\"; \"(\"; \")\"; \"~\"; \"`\";\
\_\">\";\
\n \"#\"; \"+\"; \"-\"; \"=\"; \"|\"; \"{\"; \"}\"; \".\"; \
\"!\" };\
\n }\
\n :foreach Char in=(\$Chars->\$2) do={\
\n :set Return [ \$CharacterReplace \$Return \$Char (\"\\\\\" . \$Cha\
r) ];\
\n }\
\n\
\n :if (\$2 = \"body\") do={\
\n :return (\"```\\n\" . \$Return . \"\\n```\");\
\n }\
\n\
\n :return \$Return;\
\n }\
\n\
\n :local ChatId [ \$EitherOr (\$TelegramChatIdOverride->(\$Notification-\
>\"origin\")) \$TelegramChatId ];\
\n :local TokenId [ \$EitherOr (\$TelegramTokenIdOverride->(\$Notificatio\
n->\"origin\")) \$TelegramTokenId ];\
\n\
\n :if ([ :len \$TokenId ] = 0 || [ :len \$ChatId ] = 0) do={\
\n :return false;\
\n }\
\n\
\n :local Truncated false;\
\n :local LenLink [ :len (\$Notification->\"link\") ];\
\n :local Text (\"[\" . \$Identity . \"] \" . (\$Notification->\"subject\
\") . \"\\n\\n\" . (\$Notification->\"message\"));\
\n :local LenText [ :len \$Text ];\
\n :if (\$LenText > (3968 - \$LenLink)) do={\
\n :set Text [ \$EscapeMD ([ :pick \$Text 0 (3840 - \$LenLink) ] . \"..\
.\") \"body\" ];\
\n :set Truncated true;\
\n } else={\
\n :set Text [ \$EscapeMD \$Text \"body\" ];\
\n }\
\n :if (\$LenLink > 0) do={\
\n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"link\" ] .\
\_[ \$EscapeMD (\$Notification->\"link\") \"hint\" ]);\
\n }\
\n :if (\$Truncated = true) do={\
\n :set Text (\$Text . \"\\n\" . [ \$SymbolForNotification \"scissors\"\
\_] . \\\
\n [ \$EscapeMD (\"The Telegram message was too long and has been tru\
ncated, cut off \" . \\\
\n ((\$LenText - [ :len \$Text ]) * 100 / \$LenText) . \"%!\") \"hint\
\" ]);\
\n }\
\n :set Text [ \$UrlEncode \$Text ];\
\n :local ParseMode [ \$IfThenElse (\$TelegramFixedWidthFont = true) \"Ma\
rkdownV2\" \"\" ];\
\n\
\n :do {\
\n :if ([ \$CertificateAvailable \"Go Daddy Secure Certificate Authorit\
y - G2\" ] = false) do={\
\n \$LogPrintExit2 warning \$0 (\"Downloading required certificate fa\
iled.\") true;\
\n }\
\n / tool fetch check-certificate=yes-without-crl output=none http-meth\
od=post \\\
\n (\"https://api.telegram.org/bot\" . \$TokenId . \"/sendMessage\") \
\\\
\n http-data=(\"chat_id=\" . \$ChatId . \"&disable_notification=\" . \
(\$Notification->\"silent\") . \\\
\n \"&disable_web_page_preview=true&parse_mode=\" . \$ParseMode . \"&\
text=\" . \$Text) as-value;\
\n } on-error={\
\n \$LogPrintExit2 info \$0 (\"Failed sending telegram notification! Qu\
euing...\") false;\
\n\
\n :if ([ :typeof \$TelegramQueue ] = \"nothing\") do={\
\n :set TelegramQueue [ :toarray \"\" ];\
\n }\
\n :set Text (\$Text . [ \$UrlEncode (\"\\n\" . [ \$SymbolForNotificati\
on \"alarm-clock\" ] . \\\
\n [ \$EscapeMD (\"This message was queued since \" . [ / system cloc\
k get date ] . \\\
\n \" \" . [ / system clock get time ] . \" and may be obsolete.\") \
\"hint\" ]) ]);\
\n :set (\$TelegramQueue->[ :len \$TelegramQueue ]) { chatid=\$ChatId; \
tokenid=\$TokenId;\
\n parsemode=\$ParseMode; text=\$Text; silent=(\$Notification->\"sile\
nt\") };\
\n :if ([ :len [ / system scheduler find where name=\"FlushTelegramQueu\
e\" ] ] = 0) do={\
\n / system scheduler add name=FlushTelegramQueue interval=1m start-t\
ime=startup \\\
\n on-event=\":global FlushTelegramQueue; \\\$FlushTelegramQueue;\"\
;\
\n }\
\n }\
\n}\
\n\
\n# return UTF-8 symbol for unicode name\
\n:set SymbolByUnicodeName do={\
\n :local Symbols {\
\n \"alarm-clock\"=\"\\E2\\8F\\B0\";\
\n \"calendar\"=\"\\F0\\9F\\93\\85\";\
\n \"cloud\"=\"\\E2\\98\\81\";\
\n \"cross-mark\"=\"\\E2\\9D\\8C\";\
\n \"fire\"=\"\\F0\\9F\\94\\A5\";\
\n \"floppy-disk\"=\"\\F0\\9F\\92\\BE\";\
\n \"high-voltage-sign\"=\"\\E2\\9A\\A1\";\
\n \"incoming-envelope\"=\"\\F0\\9F\\93\\A8\";\
\n \"link\"=\"\\F0\\9F\\94\\97\";\
\n \"lock-with-ink-pen\"=\"\\F0\\9F\\94\\8F\";\
\n \"mobile-phone\"=\"\\F0\\9F\\93\\B1\";\
\n \"pushpin\"=\"\\F0\\9F\\93\\8C\";\
\n \"scissors\"=\"\\E2\\9C\\82\";\
\n \"sparkles\"=\"\\E2\\9C\\A8\";\
\n \"up-arrow\"=\"\\E2\\AC\\86\";\
\n \"warning-sign\"=\"\\E2\\9A\\A0\";\
\n \"white-heavy-check-mark\"=\"\\E2\\9C\\85\"\
\n }\
\n\
\n :return (\$Symbols->\$1);\
\n}\
\n\
\n# return symbol for notification\
\n:set SymbolForNotification do={\
\n :global NotificationsWithSymbols;\
\n :global SymbolByUnicodeName;\
\n\
\n :if (\$NotificationsWithSymbols != true) do={\
\n :return \"\";\
\n }\
\n :local Return \"\";\
\n :foreach Symbol in=[ :toarray \$1 ] do={\
\n :set Return (\$Return . [ \$SymbolByUnicodeName \$Symbol ]);\
\n }\
\n :return (\$Return . \" \");\
\n}\
\n\
\n# check if system time is sync\
\n:set TimeIsSync do={\
\n :global LogPrintExit2;\
\n\
\n :if ([ / system ntp client get enabled ] = true) do={\
\n :do {\
\n :if ([ / system ntp client get status ] = \"synchronized\") do={\
\n :return true;\
\n }\
\n } on-error={\
\n :if ([ :typeof [ / system ntp client get last-adjustment ] ] = \"t\
ime\") do={\
\n :return true;\
\n }\
\n }\
\n :return false;\
\n }\
\n\
\n :if ([ / ip cloud get ddns-enabled ] = true && [ / ip cloud get update\
-time ] = true) do={\
\n :if ([ :typeof [ / ip cloud get public-address ] ] = \"ip\") do={\
\n :return true;\
\n }\
\n :return false;\
\n }\
\n\
\n \$LogPrintExit2 debug \$0 (\"No time source configured! Returning grac\
efully...\") false;\
\n :return true;\
\n}\
\n\
\n# url encoding\
\n:set UrlEncode do={\
\n :local Input [ :tostr \$1 ];\
\n\
\n :if ([ :len \$Input ] = 0) do={\
\n :return \"\";\
\n }\
\n\
\n :local Return \"\";\
\n :local Chars \"\\n\\r !\\\"#\\\$%&'()*+,:;<=>\\\?@[\\\\]^`{|}~\";\
\n :local Subs { \"%0A\"; \"%0D\"; \"%20\"; \"%21\"; \"%22\"; \"%23\"; \"\
%24\"; \"%25\"; \"%26\"; \"%27\";\
\n \"%28\"; \"%29\"; \"%2A\"; \"%2B\"; \"%2C\"; \"%3A\"; \"%3B\"; \
\"%3C\"; \"%3D\"; \"%3E\"; \"%3F\";\
\n \"%40\"; \"%5B\"; \"%5C\"; \"%5D\"; \"%5E\"; \"%60\"; \"%7B\"; \
\"%7C\"; \"%7D\"; \"%7E\" };\
\n\
\n :for I from=0 to=([ :len \$Input ] - 1) do={\
\n :local Char [ :pick \$Input \$I ];\
\n :local Replace [ :find \$Chars \$Char ];\
\n\
\n :if ([ :typeof \$Replace ] = \"num\") do={\
\n :set Char (\$Subs->\$Replace);\
\n }\
\n :set Return (\$Return . \$Char);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# basic syntax validation\
\n:set ValidateSyntax do={\
\n :local Code [ :tostr \$1 ];\
\n\
\n :do {\
\n [ :parse (\":local Validate do={\\n\" . \$Code . \"\\n}\") ];\
\n } on-error={\
\n :return false;\
\n }\
\n :return true;\
\n}\
\n\
\n# convert version string to numeric value\
\n:set VersionToNum do={\
\n :local Input [ :tostr \$1 ];\
\n :local Multi 0x1000000;\
\n :local Return 0;\
\n\
\n :global CharacterReplace;\
\n\
\n :set Input [ \$CharacterReplace [ \$CharacterReplace [ \$CharacterRepl\
ace \$Input \\\
\n \".\" \",\" ] \"beta\" \",beta,\" ] \"rc\" \",rc,\" ];\
\n\
\n :foreach Value in=([ :toarray \$Input ], 0) do={\
\n :local Num [ :tonum \$Value ];\
\n :if (\$Multi = 0x100) do={\
\n :if ([ :typeof \$Num ] = \"num\") do={\
\n :set Return (\$Return + 0xff00);\
\n :set Multi (\$Multi / 0x100);\
\n } else={\
\n :if (\$Value = \"beta\") do={ :set Return (\$Return + 0x3f00); }\
\n :if (\$Value = \"rc\") do={ :set Return (\$Return + 0x7f00); }\
\n }\
\n }\
\n :if ([ :typeof \$Num ] = \"num\") do={ :set Return (\$Return + (\$Va\
lue * \$Multi)); }\
\n :set Multi (\$Multi / 0x100);\
\n }\
\n\
\n :return \$Return;\
\n}\
\n\
\n# wait for default route to be reachable\
\n:set WaitDefaultRouteReachable do={\
\n :global DefaultRouteIsReachable;\
\n\
\n :while ([ \$DefaultRouteIsReachable ] = false) do={\
\n :delay 1s;\
\n }\
\n}\
\n\
\n# wait for DNS to resolve\
\n:set WaitDNSResolving do={\
\n :global DNSIsResolving;\
\n\
\n :while ([ \$DNSIsResolving ] = false) do={\
\n :delay 1s;\
\n }\
\n}\
\n\
\n# wait for file to be available\
\n:set WaitForFile do={\
\n :local FileName [ :tostr \$1 ];\
\n\
\n :global CleanFilePath;\
\n\
\n :set FileName [ \$CleanFilePath \$FileName ];\
\n :local I 0;\
\n\
\n :while ([ :len [ / file find where name=\$FileName ] ] = 0) do={\
\n :if (\$I > 20) do={\
\n :return false;\
\n }\
\n :delay 100ms;\
\n :set I (\$I + 1);\
\n }\
\n :return true;\
\n}\
\n\
\n# wait to be fully connected (default route is reachable, time is sync, \
DNS resolves)\
\n:set WaitFullyConnected do={\
\n :global WaitDefaultRouteReachable;\
\n :global WaitDNSResolving;\
\n :global WaitTimeSync;\
\n\
\n \$WaitDefaultRouteReachable;\
\n \$WaitTimeSync;\
\n \$WaitDNSResolving;\
\n}\
\n\
\n# wait for time to become synced\
\n:set WaitTimeSync do={\
\n :global LogPrintExit2;\
\n :global TimeIsSync;\
\n\
\n :while ([ \$TimeIsSync ] = false) do={\
\n :if ([ :len [ / system script find where name=\"rotate-ntp\" ] ] > 0\
\_&& \\\
\n ([ / system resource get uptime ] % (180 * 1000000000)) = 0s) d\
o={\
\n :do {\
\n / system script run rotate-ntp;\
\n } on-error={\
\n \$LogPrintExit2 debug \$0 (\"Running rotate-ntp failed.\") false\
;\
\n }\
\n }\
\n :delay 1s;\
\n }\
\n}\
\n\
\n# check for required RouterOS version\
\n\$RequiredRouterOS \"global-functions\" \"6.47\";\
\n\
\n# signal we are ready\
\n:set GlobalFunctionsReady true;\
\n"
add dont-require-permissions=no name=email-backup owner=email-backup policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
!rsc by RouterOS\
\n# RouterOS script: email-backup\
\n# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# create and email backup and config file\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/email-backup.md\
\n\
\n:local 0 \"email-backup\";\
\n:global GlobalFunctionsReady;\
\n:while (\$GlobalFunctionsReady != true) do={ :delay 500ms; }\
\n\
\n:global BackupPassword;\
\n:global BackupRandomDelay;\
\n:global BackupSendBinary;\
\n:global BackupSendExport;\
\n:global Domain;\
\n:global Identity;\
\n\
\n:global CharacterReplace;\
\n:global DeviceInfo;\
\n:global LogPrintExit2;\
\n:global MkDir;\
\n:global RandomDelay;\
\n:global ScriptFromTerminal;\
\n:global SendEMail2;\
\n:global SymbolForNotification;\
\n:global WaitForFile;\
\n:global WaitFullyConnected;\
\n\
\n:if (\$BackupSendBinary != true && \\\
\n \$BackupSendExport != true) do={\
\n \$LogPrintExit2 error \$0 (\"Configured to send neither backup nor con\
fig export.\") true;\
\n}\
\n\
\n\$WaitFullyConnected;\
\n\
\n:if ([ \$ScriptFromTerminal \$0 ] = false && \$BackupRandomDelay > 0) do\
={\
\n \$RandomDelay \$BackupRandomDelay;\
\n}\
\n\
\n:if ([ \$MkDir \$0 ] = false) do={\
\n \$LogPrintExit2 error \$0 (\"Failed creating directory!\") true;\
\n}\
\n\
\n# filename based on identity\
\n:local FileName [ \$CharacterReplace (\$Identity . \".\" . \$Domain) \".\
\" \"_\" ];\
\n:local FilePath (\$0 . \"/\" . \$FileName);\
\n:local BackupFile \"none\";\
\n:local ConfigFile \"none\";\
\n:local Attach [ :toarray \"\" ];\
\n\
\n# binary backup\
\n:if (\$BackupSendBinary = true) do={\
\n / system backup save encryption=aes-sha256 name=\$FilePath password=\$\
BackupPassword;\
\n \$WaitForFile (\$FilePath . \".backup\");\
\n :set BackupFile (\$FileName . \".backup\");\
\n :set Attach (\$Attach, (\$FilePath . \".backup\"));\
\n}\
\n\
\n# create configuration export\
\n:if (\$BackupSendExport = true) do={\
\n / export terse file=\$FilePath;\
\n \$WaitForFile (\$FilePath . \".rsc\");\
\n :set ConfigFile (\$FileName . \".rsc\");\
\n :set Attach (\$Attach, (\$FilePath . \".rsc\"));\
\n}\
\n\
\n# send email with status and files\
\n\$SendEMail2 ({ origin=\$0; \\\
\n subject=([ \$SymbolForNotification \"floppy-disk,incoming-envelope\" ]\
\_. \\\
\n \"Backup & Config\"); \\\
\n message=(\"See attached files for backup and config export for \" . \\\
\n \$Identity . \".\\n\\n\" . \\\
\n [ \$DeviceInfo ] . \"\\n\\n\" . \\\
\n \"Backup file: \" . \$BackupFile . \"\\n\" . \\\
\n \"Config file: \" . \$ConfigFile); \\\
\n attach=\$Attach });\
\n"
add dont-require-permissions=no name=log-forward owner=log-forward policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
!rsc by RouterOS\
\n# RouterOS script: log-forward\
\n# Copyright (c) 2020-2021 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# forward log messages via notification\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/log-forward.md\
\n\
\n:local 0 \"log-forward\";\
\n:global GlobalFunctionsReady;\
\n:while (\$GlobalFunctionsReady != true) do={ :delay 500ms; }\
\n\
\n:global Identity;\
\n:global LogForwardFilter;\
\n:global LogForwardFilterMessage;\
\n:global LogForwardLast;\
\n:global LogForwardRateLimit;\
\n:global NotificationsWithSymbols;\
\n\
\n:global EscapeForRegEx;\
\n:global IfThenElse;\
\n:global LogPrintExit2;\
\n:global QuotedPrintable;\
\n:global ScriptLock;\
\n:global SendNotification2;\
\n:global SymbolForNotification;\
\n:global WaitFullyConnected;\
\n\
\n\$ScriptLock \$0;\
\n\
\n:if ([ :typeof \$LogForwardRateLimit ] = \"nothing\") do={\
\n :set LogForwardRateLimit 0;\
\n}\
\n\
\n:if (\$LogForwardRateLimit > 30) do={\
\n :set LogForwardRateLimit (\$LogForwardRateLimit - 1);\
\n \$LogPrintExit2 info \$0 (\"Rate limit in action, not forwarding logs,\
\_if any!\") true;\
\n}\
\n\
\n\$WaitFullyConnected;\
\n\
\n:local Count 0;\
\n:local Duplicates false;\
\n:local Messages \"\";\
\n:local MessageVal;\
\n:local MessageDups [ :toarray \"\" ];\
\n\
\n:local LogForwardFilterLogForwarding (\"^\" . [ \$EscapeForRegEx (\"Erro\
r sending e-mail <\" . \\\
\n [ \$QuotedPrintable (\"[\" . \$Identity . \"] \" . [ \$SymbolForNoti\
fication \"warning-sign\" ] . \\\
\n \"Log Forwarding\") ] . \">:\") ]);\
\n:foreach Message in=[ / log find where !(topics~\$LogForwardFilter) !(me\
ssage=\"\") \\\
\n !(message~\$LogForwardFilterLogForwarding) !(message~\$LogForwardFil\
terMessage) ] do={\
\n :set MessageVal [ / log get \$Message ];\
\n\
\n :if (\$LogForwardLast = (\$MessageVal->\".id\")) do={\
\n :set Count 0;\
\n :set Duplicates false;\
\n :set Messages \"\";\
\n :set MessageDups [ :toarray \"\" ];\
\n } else={\
\n :local DupCount (\$MessageDups->(\$MessageVal->\"message\"));\
\n :if (\$DupCount < 3) do={\
\n :set Messages (\$Messages . \"\\n\" . [ \$IfThenElse (\$Notificati\
onsWithSymbols = true) (\" \\E2\\97\\8F \") ] . \\\
\n \$MessageVal->\"time\" . \" \" . [ :tostr (\$MessageVal->\"topic\
s\") ] . \" \" . \$MessageVal->\"message\");\
\n } else={\
\n :set Duplicates true;\
\n }\
\n :set (\$MessageDups->(\$MessageVal->\"message\")) (\$DupCount + 1);\
\n :set Count (\$Count + 1);\
\n }\
\n}\
\n\
\n:if (\$Count > 0) do={\
\n \$SendNotification2 ({ origin=\$0; \\\
\n subject=([ \$SymbolForNotification \"warning-sign\" ] . \"Log Forwar\
ding\"); \\\
\n message=(\"The log on \" . \$Identity . \" contains \" . [ \$IfThenE\
lse (\$Count = 1) \\\
\n \"this message\" (\"these \" . \$Count . \" messages\") ] . \" aft\
er \" . \\\
\n [ / system resource get uptime ] . \" uptime.\" . [ \$IfThenElse (\
\$Duplicates = true) \\\
\n (\" Multi-repeated messages have been skipped.\") ] . \"\\n\" . \$\
Messages) });\
\n\
\n :set LogForwardRateLimit (\$LogForwardRateLimit + 10);\
\n :set LogForwardLast (\$MessageVal->\".id\");\
\n} else={\
\n :if (\$LogForwardRateLimit > 0) do={\
\n :set LogForwardRateLimit (\$LogForwardRateLimit - 1);\
\n }\
\n}\
\n"
add dont-require-permissions=no name=netwatch-notify owner=netwatch-notify \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source="#!rsc by RouterOS\
\n# RouterOS script: netwatch-notify\
\n# Copyright (c) 2020-2021 Christian Hesse <mail@eworm.de>\
\n# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md\
\n#\
\n# monitor netwatch and send notifications\
\n# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-notify.m\
d\
\n\
\n:local 0 \"netwatch-notify\";\
\n:global GlobalFunctionsReady;\
\n:while (\$GlobalFunctionsReady != true) do={ :delay 500ms; }\
\n\
\n:global NetwatchNotify;\
\n\
\n:global DNSIsResolving;\
\n:global IfThenElse;\
\n:global LogPrintExit2;\
\n:global ParseKeyValueStore;\
\n:global SendNotification2;\
\n:global SymbolForNotification;\
\n:global ValidateSyntax;\
\n\
\n:if ([ :typeof \$NetwatchNotify ] = \"nothing\") do={\
\n :set NetwatchNotify [ :toarray \"\" ];\
\n}\
\n\
\n:foreach Host in=[ / tool netwatch find where comment~\"^notify,\" disab\
led=no ] do={\
\n :local HostVal [ / tool netwatch get \$Host ];\
\n :local HostInfo [ \$ParseKeyValueStore (\$HostVal->\"comment\") ];\
\n :local HostName (\$HostInfo->\"hostname\");\
\n\
\n :local Metric { \"count\"=0; \"notified\"=false };\
\n :if ([ :typeof (\$NetwatchNotify->\$HostName) ] = \"array\") do={\
\n :set \$Metric (\$NetwatchNotify->\$HostName);\
\n }\
\n\
\n :if ([ :typeof (\$HostInfo->\"resolve\") ] = \"str\" && [ \$DNSIsResol\
ving ] = true) do={\
\n :do {\
\n :local Resolve [ :resolve (\$HostInfo->\"resolve\") ];\
\n :if (\$Resolve != \$HostVal->\"host\") do={\
\n \$LogPrintExit2 info \$0 (\"Name '\" . \$HostInfo->\"resolve\" \
. [ \$IfThenElse (\$HostInfo->\"resolve\" != \\\
\n \$HostInfo->\"hostname\") (\"' for host '\" . \$HostInfo->\"h\
ostname\") \"\" ] . \\\
\n \"' resolves to different address \" . \$Resolve . \", updati\
ng.\") false;\
\n / tool netwatch set host=\$Resolve \$Host;\
\n :set (\$Metric->\"resolve-failed\") false;\
\n }\
\n } on-error={\
\n :if (\$Metric->\"resolve-failed\" != true) do={\
\n \$LogPrintExit2 warning \$0 (\"Resolving name '\" . \$HostInfo->\
\"resolve\" . [ \$IfThenElse (\$HostInfo->\"resolve\" != \\\
\n \$HostInfo->\"hostname\") (\"' for host '\" . \$HostInfo->\"ho\
stname\") \"\" ] . \"' failed.\") false;\
\n :set (\$Metric->\"resolve-failed\") true;\
\n }\
\n }\
\n }\
\n\
\n :if (\$HostVal->\"status\" = \"up\") do={\
\n :local Count (\$Metric->\"count\");\
\n :if (\$Count > 0) do={\
\n \$LogPrintExit2 info \$0 (\"Host \" . \$HostName . \" (\" . \$Host\
Val->\"host\" . \") is up.\") false;\
\n :set (\$Metric->\"count\") 0;\
\n }\
\n :if (\$Metric->\"notified\" = true) do={\
\n :local Message (\"Host \" . \$HostName . \" (\" . \$HostVal->\"hos\
t\" . \") is up since \" . \$HostVal->\"since\" . \".\\n\" . \\\
\n \"It was down for \" . \$Count . \" checks since \" . (\$Metric-\
>\"since\") . \".\");\
\n :if ([ :typeof (\$HostInfo->\"up-hook\") ] = \"str\") do={\
\n :if ([ \$ValidateSyntax (\$HostInfo->\"up-hook\") ] = true) do={\
\n \$LogPrintExit2 info \$0 (\"Running hook on host \" . \$HostNa\
me . \" up: \" . (\$HostInfo->\"up-hook\")) false;\
\n :set Message (\$Message . \"\\n\\nRunning hook:\\n\" . \$HostI\
nfo->\"up-hook\");\
\n [ :parse (\$HostInfo->\"up-hook\") ];\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"The up-hook for host \" . \$Hos\
tName . \" failed syntax validation.\") false;\
\n }\
\n }\
\n \$SendNotification2 ({ origin=\$0; \\\
\n subject=([ \$SymbolForNotification \"white-heavy-check-mark\" ] \
. \"Netwatch Notify: \" . \$HostName . \" up\"); \\\
\n message=\$Message });\
\n }\
\n :set (\$Metric->\"notified\") false;\
\n :set (\$Metric->\"parent\") (\$HostInfo->\"parent\");\
\n :set (\$Metric->\"since\");\
\n } else={\
\n :set (\$Metric->\"count\") (\$Metric->\"count\" + 1);\
\n :set (\$Metric->\"parent\") (\$HostInfo->\"parent\");\
\n :set (\$Metric->\"since\") (\$HostVal->\"since\");\
\n :local Count [ \$IfThenElse ([ :tonum (\$HostInfo->\"count\") ] > 0)\
\_(\$HostInfo->\"count\") 5 ];\
\n :local Parent (\$HostInfo->\"parent\");\
\n :while ([ :len \$Parent ] > 0) do={\
\n :set Count (\$Count + 1);\
\n :set Parent (\$NetwatchNotify->\$Parent->\"parent\");\
\n }\
\n :set Parent (\$HostInfo->\"parent\");\
\n :local ParentNotified false;\
\n :while (\$ParentNotified = false && [ :len \$Parent ] > 0) do={\
\n :set ParentNotified [ \$IfThenElse ((\$NetwatchNotify->\$Parent->\
\"notified\") = true) true false ];\
\n :if (\$ParentNotified = false) do={\
\n :set Parent (\$NetwatchNotify->\$Parent->\"parent\");\
\n }\
\n }\
\n \$LogPrintExit2 info \$0 (\"Host \" . \$HostName . \" (\" . \$HostVa\
l->\"host\" . \") is down for \" . \\\
\n \$Metric->\"count\" . \" checks, \" . [ \$IfThenElse (\$ParentNoti\
fied = false) [ \$IfThenElse \\\
\n (\$Metric->\"notified\" = true) (\"already notified.\") (\$Count -\
\_\$Metric->\"count\" . \" to go.\") ] \\\
\n (\"parent host \" . \$Parent . \" is down.\") ]) false;\
\n :if (\$ParentNotified = false && \$Metric->\"count\" >= \$Count && \
\$Metric->\"notified\" != true) do={\
\n :local Message (\"Host \" . \$HostName . \" (\" . \$HostVal->\"hos\
t\" . \") is down since \" . \$HostVal->\"since\" . \".\");\
\n :if ([ :typeof (\$HostInfo->\"down-hook\") ] = \"str\") do={\
\n :if ([ \$ValidateSyntax (\$HostInfo->\"down-hook\") ] = true) do\
={\
\n \$LogPrintExit2 info \$0 (\"Running hook on host \" . \$HostNa\
me . \" down: \" . (\$HostInfo->\"down-hook\")) false;\
\n :set Message (\$Message . \"\\n\\nRunning hook:\\n\" . \$HostI\
nfo->\"down-hook\");\
\n [ :parse (\$HostInfo->\"down-hook\") ];\
\n } else={\
\n \$LogPrintExit2 warning \$0 (\"The down-hook for host \" . \$H\
ostName . \" failed syntax validation.\") false;\
\n }\
\n }\
\n \$SendNotification2 ({ origin=\$0; \\\
\n subject=([ \$SymbolForNotification \"cross-mark\" ] . \"Netwatch\
\_Notify: \" . \$HostName . \" down\"); \\\
\n message=\$Message });\
\n :set (\$Metric->\"notified\") true;\
\n }\
\n }\
\n :set (\$NetwatchNotify->\$HostName) {\
\n \"count\"=(\$Metric->\"count\");\
\n \"notified\"=(\$Metric->\"notified\");\
\n \"parent\"=(\$Metric->\"parent\");\
\n \"resolve-failed\"=(\$Metric->\"resolve-failed\");\
\n \"since\"=(\$Metric->\"since\") };\
\n}\
\n"
add dont-require-permissions=no name=pptppsh-on-up owner=pptppsh-on-up \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source="# !rsc by RouterOS\
\n# RouterOS script: pptppsh-on-up\
\n# Copyright (c) 2020 xxxxx <xxxxx@gmail.com>\
\n#\
\n#\
\n\
\n:local 0 \"pptppsh-on-up\";\
\n:global LogPrintExit2;\
\n:global PptpServerIfaceName;\
\n:global PptpServerIp;\
\n:local MyChain \"pihole-placeholder-chain\";\
\n:local MyComment (\"dynamic, service=pptp, server=\" . \$PptpServerIface\
Name);\
\n:local PiHoleIp \"172.16.220.36\";\
\n:local PiHoleNetwork \"172.16.220.0/24\";\
\n:local IpAddress [ / ip address get [ find interface=\$PptpServerIfaceNa\
me ] address ];\
\n\
\n:if ([ :len [ / system script job find where script=\"pptppsh-on-up\" ] \
] > 1) do={\
\n \$LogPrintExit2 error \$0 (\"on process, skip this one\") true;\
\n}\
\n\
\n:while ([ :len [ / system script job find where script=\"pptppsh-on-down\
\" ] ] > 0) do={\
\n :local d 100;;\
\n :delay (\$d . \"ms\");\
\n}\
\n\
\n:set PptpServerIp \"0.0.0.0\";\
\n\
\n/ interface list member add list=\"LAN\" interface=\$PptpServerIfaceName\
\_comment=\$MyComment;\
\n/ ip route add dst-address=\$PiHoleNetwork distance=6 gateway=\"172.16.2\
20.1\" comment=\$MyComment;\
\n/ ip firewall address-list add address=\$PiHoleIp list=PiHole comment=\$\
MyComment;\
\n/ ip firewall address-list add address=\$PiHoleIp list=AcceptSrcAdd comm\
ent=\$MyComment;\
\n/ ip firewall address-list add address=\$PiHoleIp list=AcceptDstAdd comm\
ent=\$MyComment;\
\n/ ip firewall nat add chain=srcnat action=src-nat to-addresses=\$IpAddre\
ss place-before=([ find ]->0) comment=\$MyComment out-interface=\$PptpServ\
erIfaceName;\
\n\
\n/ ip firewall nat add action=dst-nat chain=dstnat comment=\$MyComment ds\
t-address-list=!PiHole dst-port=53 in-interface-list=!WAN protocol=udp src\
-address-list=!PiHole to-addresses=\$PiHoleIp to-ports=53;\
\n/ ip firewall nat add action=dst-nat chain=dstnat comment=\$MyComment ds\
t-address-list=!PiHole dst-port=53 in-interface-list=!WAN protocol=tcp src\
-address-list=!PiHole to-addresses=\$PiHoleIp to-ports=53;\
\n/ ip firewall nat add action=dst-nat chain=pre-hotspot comment=\$MyComme\
nt dst-port=53 hotspot=auth protocol=udp to-addresses=\$PiHoleIp to-ports=\
53;\
\n/ ip firewall nat add action=dst-nat chain=pre-hotspot comment=\$MyComme\
nt dst-port=53 hotspot=auth protocol=tcp to-addresses=\$PiHoleIp to-ports=\
53;\
\n\
\n# / ip firewall mangle add action=accept chain=forward dst-address-list=\
PiHole place-before=[ find where chain=\$MyChain ] comment=\$MyComment;\
\n\$LogPrintExit2 info \$0 (\"completed\") true;\
\n"
add dont-require-permissions=no name=pptppsh-on-down owner=pptppsh-on-down \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source="# !rsc by RouterOS\
\n# RouterOS script: pptppsh-on-down\
\n# Copyright (c) 2020 xxxxx <xxxxx@gmail.com>\
\n#\
\n#\
\n\
\n:local 0 \"pptppsh-on-down\";\
\n:global LogPrintExit2;\
\n:global PptpServerIfaceName;\
\n:global PptpServerName;\
\n:global PptpServerIp;\
\n:global PshStatus;\
\n:local MyComment (\"dynamic, service=pptp, server=\" . \$PptpServerIface\
Name);\
\n\
\n:if ([ :len [ / system script job find where script=\"pptppsh-on-down\" \
] ] > 1) do={\
\n \$LogPrintExit2 error \$0 (\"on process, skip this one\") true;\
\n}\
\n\
\n:local NewIp [ :tostr [ :resolve \$PptpServerName ] ];\
\n:if (\$NewIp ~ \"^failure\") do={\
\n \$LogPrintExit2 error \$0 (\"Error while checking PPTP server. \" . \$\
NewIp) false;\
\n}\
\n\
\n:if (\$PptpServerIp = \$NewIp) do={\
\n \$LogPrintExit2 debug \$0 (\"Skip PPTP Server IP check\") false;\
\n} else={\
\n / interface pptp-client set connect-to=\$NewIp [ find where name=\$Ppt\
pServerIfaceName ];\
\n :set PptpServerIp \$NewIp;\
\n \$LogPrintExit2 debug \$0 (\"Renew \" . \$PptpServerIfaceName . \" IP \
Address to: \" . \$NewIp) false;\
\n}\
\n\
\n/ interface list member remove [ find where comment=\$MyComment ];\
\n/ ip route remove [ find where comment=\$MyComment ];\
\n/ ip firewall address-list remove [ find where comment=\$MyComment ];\
\n/ ip firewall nat remove [ find where comment=\$MyComment ];\
\n/ ip firewall mangle remove [ find where comment=\$MyComment ];\
\n:delay 500ms;\
\n\$LogPrintExit2 info \$0 (\"completed\") true;\
\n"
add dont-require-permissions=no name=reset-owner owner=reset-owner policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\
foreach i in=[ / system script find where owner=\"admin\" ] do={\
\n / system script set \$i owner=[ / system script get \$i name ];\
\n};\
\n"
add dont-require-permissions=no name=restart-pptppsh owner=restart-pptppsh \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source=":global PptpServerIfaceName;\
\n\
\n/ interface set disabled=yes [ find where !disabled && name=\$PptpServer\
IfaceName ];\
\n#:delay 3s;\
\n/ interface set disabled=no [ find where disabled && name=\$PptpServerIf\
aceName ];\
\n"
add dont-require-permissions=no name=nordvpn-ipsec owner=nordvpn-ipsec \
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
source=":local MyComment \"ipsec mode-config\";\
\n:if ([ / ip firewall nat print count-only where comment=\$MyComment && d\
ynamic ] > 0) do={\
\n :local count 0;\
\n :local MyChain;\
\n :while (\$MyChain != \"ipsec-placeholder\") do={\
\n :set MyChain [ / ip firewall nat get ([ find ]->\$count) chain ];\
\n :set count (\$count +1);\
\n }\
\n :if ([ / ip firewall nat get ([ find ]->(\$count-2)) comment ] != \$My\
Comment) do={\
\n # Route to NordVPN except GameConnection\
\n / ip firewall nat set [ find comment=\$MyComment && dynamic ] src-ad\
dress-list=NordVPNClient dst-address-list=!NordVPNClient connection-mark=!Game\
Connection;\
\n / ip firewall nat move [ find comment=\$MyComment && dynamic ] desti\
nation=[ find where chain=\$MyChain ];\
\n / ip firewall nat set [ find where comment~\"NordVPN\" && chain=srcn\
at ] disable=no to-addresses=([ / ip firewall nat get [ find comment=\$MyC\
omment && dynamic ] to-addresses ]);\
\n / ip firewall mangle set disable=no [ find where comment~\"NordVPN\"\
\_&& disabled ];\
\n / ip firewall address-list set disable=no [ find where list~\"NordVP\
N\" && disabled ];\
\n }\
\n} else={\
\n / ip firewall address-list set disable=yes [ find where list~\"NordVPN\
\" && !disabled ];\
\n / ip firewall mangle set disable=yes [ find where comment~\"NordVPN\" \
&& !disabled ];\
\n / ip firewall nat set disable=yes [ find where comment~\"NordVPN\" && \
!disabled ];\
\n}\
\n"
add dont-require-permissions=no name=myPPPoE-on-down owner=\
myPPPoE-on-down policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\
global PptpServerIfaceName;\
\n# / interface set disabled=yes [ find where !disabled && name=\$PptpServ\
erIfaceName ];\
\n\
\n:global PPPoEIface;\
\n/ ip route remove [ find where comment~\$PPPoEIface ];\
\n\
\n/ ip ipsec peer disable [ find where !disabled ];\
\n/ system script run nordvpn-ipsec;\
\n\
\n:global PshStatus;\
\n:set \$PshStatus 0;\
\n"
add dont-require-permissions=no name=check-PPPoE-ip owner=\
check-PPPoE-ip policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
\_!rsc\
\n# RouterOS script: check-PPPoE-ip\
\n# Copyright (c) 2020 xxxxx <xxxxx@gmail.com>\
\n#\
\n# Force PPPoE to get IP Public\
\n\
\n:local 0 \"check-PPPoE-ip\";\
\n:global LogPrintExit2;\
\n:global PPPoEIface;\
\n:global PptpServerName;\
\n:global PptpServerIfaceName;\
\n:local MyComment (\"dynamic, load-balance, \" . \$PPPoEIface);\
\n\
\n/ ip ipsec peer disable [ find where !disabled ];\
\n/ interface set disabled=yes [ find where !disabled && name=\$PptpServer\
IfaceName ];\
\n\
\n:local count 0;\
\n:while ([ / interface pppoe-client print count-only where name=\$PPPo\
EIface ] = 0) do={\
\n :if (\$count = 30) do={\
\n \$LogPrintExit2 error \$0 (\"Unable to find PPP Interface \" . \$Ind\
iHomeIface .\" as PPPoE Client\") true;\
\n }\
\n :delay 1s;\
\n :set count (\$count +1);\
\n};\
\n\
\n:set count 0;\
\n:while ([ / ip address print count-only where interface=\$PPPoEIface \
] = 0) do={\
\n :if (\$count = 30) do={\
\n \$LogPrintExit2 error \$0 (\$PPPoEIface .\" is not connected\") t\
rue;\
\n }\
\n :delay 1s;\
\n :set count (\$count +1);\
\n};\
\n\
\n:global PPPoEIpPref;\
\n\
\n\
\n:local IpAddress;\
\n:local NetworkAddress;\
\n:local GatewayIp;\
\n:local CurrentIp;\
\n:local PPPoEIp;\
\n:local CurrentPref;\
\n:local VarType;\
\n\
\n:foreach i in=[ / ip address find where interface=\$PPPoEIface ] do={\
\n :set IpAddress [ / ip address get \$i address ];\
\n :set NetworkAddress [ / ip address get \$i network ];\
\n :set CurrentIp [ :pick \$IpAddress 0 [ :find \$IpAddress \"/\" ] ];\
\n :if (\$CurrentIp != \$NetworkAddress) do={\
\n :set GatewayIp \$NetworkAddress;\
\n :set PPPoEIp \$CurrentIp;\
\n :set CurrentPref [ :pick \$CurrentIp 0 [ :find \$CurrentIp \".\" ]\
\_];\
\n :set VarType [ :typeof [ :find \$PPPoEIpPref \$CurrentPref ] ];\
\n }\
\n}\
\n\
\nif (\$VarType = \"num\" and \$CurrentPref != \"10\") do={\
\n / ip firewall nat set to-addresses=\$PPPoEIp [ find where comment~\
\$PPPoEIface ];\
\n / interface set disabled=no [ find where disabled && name=\$PptpServer\
IfaceName ];\
\n / ip cloud force-update;\
\n / ip ipsec policy set dst-address=\$PPPoEIp [ find comment=\"PPP\
oE IP\" ];\
\n / ip ipsec peer enable [ find where disabled ];\
\n / ip route add distance=10 dst-address=[ :resolve \$PptpServerName ] g\
ateway=\$GatewayIp scope=10 comment=\$MyComment;\
\n / ip route add distance=10 dst-address=[ :resolve [ / ip ipsec peer ge\
t NordVPN address ] ] gateway=\$GatewayIp scope=10 comment=\$MyComment;\
\n / ip firewall connection remove [ find where src-address~\"192.168.219\
.243\" ];\
\n \$LogPrintExit2 info \$0 (\"Completed. \" . \$PPPoEIface . \" is no\
w connected on prefered IP: \" . \$PPPoEIp) true;\
\n}\
\n\
\n\$LogPrintExit2 info \$0 (\$PPPoEIface . \" is not connected on prefe\
red IP: \" . \$PPPoEIp) false;\
\n\
\n/ interface disable [ find where name=\$PPPoEIface ];\
\n:delay 1s;\
\n/ interface enable [ find where name=\$PPPoEIface ];\
\n"
add dont-require-permissions=no name=pihole-check owner=pihole-check policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
\_!rsc\
\n# RouterOS script: pihole-check\
\n# Copyright (c) 2020 xxxxx <xxxxx@gmail.com>\
\n#\
\n# Check PiHole for dynamically change DNS\
\n\
\n:local 0 \"pihole-check\";\
\n:local Server \"172.16.220.36\";\
\n:local Domain \"google.com\";\
\n:global LogPrintExit2;\
\n\
\n:do {\
\n :put [ :resolve \$Domain server \$Server ];\
\n / ip firewall nat set [ find where action=dst-nat && to-addresses=\$Se\
rver && disabled ] disabled=no;\
\n} on-error={\
\n \$LogPrintExit2 error \$0 (\"Unable to resolve \" . \$Domain .\" via \
\" . \$Server ) false;\
\n / ip firewall nat set [ find where action=dst-nat && to-addresses=\$Se\
rver && !disabled ] disabled=yes;\
\n};\
\n"
add dont-require-permissions=no name=my-function owner=my-function policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":\
global NordVpnStart;\
\n\
\n:set NordVpnStart do={\
\n :local Peer [ :tostr \$1 ];\
\n\
\n :local NewPeer;\
\n :global LogPrintExit2;\
\n :global SymbolForNotification;\
\n :global SendTelegram;\
\n\
\n\
\n :local CurrentPeer [ /ip ipsec peer get [ find name=NordVPN ] address \
];\
\n \$LogPrintExit2 info \$0 (\"NordVPN current peer: \" . \$CurrentPeer) \
false;\
\n :local CurrentSrcAddress \"10.6.\";\
\n :do {\
\n :set CurrentSrcAddress [ / ip ipsec policy get [ find peer=NordVPN ]\
\_src-address ];\
\n :set CurrentSrcAddress [ :pick \$CurrentSrcAddress 0 [ :find \$Curre\
ntSrcAddress \"/\" ] ];\
\n } on-error={\
\n \$LogPrintExit2 info \$0 (\"NordVPN was disconnected\") false;\
\n }\
\n\
\n\
\n :if ([ :len \$Peer ] = 0) do={\
\n # ApiCountry is used to select a country, if it is empty then the co\
untry will be selected automatically\
\n # https://api.nordvpn.com/v1/servers/countries\
\n :local ApiCountry 195\
\n\
\n # ApiGroup is used to select server type, if it is empty then will b\
e selected automatically\
\n # https://api.nordvpn.com/v1/servers/groups\
\n :local ApiGroup 11\
\n\
\n # ApiMaxLoad is used to define the maximum acceptable load of the cu\
rrent server, after exceeding this value the server will be changed\
\n :local ApiMaxLoad 20\
\n\
\n :if (([:typeof \$ApiCountry] = \"num\" || [:len \$ApiCountry] = 0) &\
& ([:typeof \$ApiGroup] = \"num\" || [:len \$ApiGroup] = 0) && ([:typeof \
\$ApiMaxLoad] = \"num\" || [:len \$ApiMaxLoad] = 0)) do={\
\n :local CurrentLoad ([/tool fetch url=(\"https://api.nordvpn.com/se\
rver/stats/\$CurrentPeer\") output=user as-value ]->\"data\");\
\n :set CurrentLoad [:pick [:pick \$CurrentLoad [:find \$CurrentLoad \
\"percent\"] [:find \$CurrentLoad \"}\"]] 9 99];\
\n :if (\$CurrentLoad > \$ApiMaxLoad) do={\
\n :local RecPeer ([/tool fetch url=(\"https://api.nordvpn.com/v1/s\
ervers/recommendations\\\?filters[servers_groups]=\$ApiGroup&filters[count\
ry_id]=\$ApiCountry&filters[servers_technologies][identifier]=ikev2&limit=\
1\") output=user as-value ]->\"data\");\
\n :if (\$RecPeer = \"[]\") do={\
\n \$LogPrintExit2 info \$0 (\"server not found for the current c\
onfiguration\") true;\
\n } else={\
\n :local NewLoad [:pick [:pick \$RecPeer [:find \$RecPeer \"nord\
vpn.com\"] [:find \$RecPeer \",\\\"status\"]] 20 99];\
\n :set RecPeer [:pick [:pick \$RecPeer [:find \$RecPeer \"hostna\
me\"] [:find \$RecPeer \"\\\",\\\"load\"]] 11 99];\
\n :if ((\$CurrentPeer != \$RecPeer) && (\$CurrentLoad > \$NewLoa\
d)) do={\
\n\t\t\t:set NewPeer \$RecPeer;\
\n }\
\n }\
\n }\
\n } else={\
\n \$LogPrintExit2 error \$0 (\"variables must be numbers\") true;\
\n }\
\n } else={\
\n :if ([:typeof [:find \$Peer \".nordvpn.com\"]] = \"nil\") do={\
\n \$LogPrintExit2 error \$0 (\"Invalid NordVPN peer: \" . \$Peer) tr\
ue;\
\n }\
\n :do {\
\n :resolve \$Peer;\
\n } on-error={\
\n \$LogPrintExit2 error \$0 (\"Invalid NordVPN peer: \" . \$Peer) tr\
ue;\
\n }\
\n :set NewPeer \$Peer;\
\n }\
\n / ip ipsec active-peers remove [ find id=\$CurrentPeer ];\
\n # / ip ipsec peer set [ find name=NordVPN ] disabled=yes;\
\n / ip firewall connection remove [ find reply-dst-address~\"^\$CurrentS\
rcAddress\" ];\
\n :if ((\$CurrentPeer != \$NewPeer) && ([ :len \$NewPeer ] > 0)) do={\
\n \$LogPrintExit2 info \$0 (\"NordVPN new peer: \" . \$NewPeer) false;\
\n / ip ipsec peer set [ find name=NordVPN ] address=\$NewPeer;\
\n \$SendTelegram ([ \$SymbolForNotification \"white-heavy-check-mark\"\
\_] . \"NordVPN peer changed\") (\"from: \" . \$CurrentPeer . \" to: \" . \
\$NewPeer);\
\n }\
\n # / ip ipsec peer set [ find name=NordVPN ] disabled=no;\
\n}\
\n"
/tool e-mail
set address=smtp.gmail.com from="Mikrotik Router" port=587 start-tls=yes \
user=xxxxx@gmail.com
/tool mac-server
set allowed-interface-list=LAN
/tool mac-server mac-winbox
set allowed-interface-list=LAN
/tool netwatch
add disabled=yes down-script="/ sys sch add name=logf" host=192.168.234.5
add comment="notify, hostname=api.nordvpn.com, down-hook=:global NordVpnStart;\
\_\$NordVpnStart;" disabled=yes host=104.17.50.74
add comment="notify, count=1, hostname=api.nordvpn.com, down-hook=:global Nord\
VpnStart; \$NordVpnStart;" host=104.18.19.110