diff options
125 files changed, 5622 insertions, 5175 deletions
diff --git a/INITIAL-COMMANDS.md b/INITIAL-COMMANDS.md index a31112a..a53ae0f 100644 --- a/INITIAL-COMMANDS.md +++ b/INITIAL-COMMANDS.md @@ -19,7 +19,7 @@ Run the complete base installation: /file/remove "letsencrypt-R3.pem"; :delay 1s; :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ - /system/script/add name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script) output=user as-value]->"data"); + /system/script/add name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data"); }; /system/script { run global-config; run global-functions; }; /system/scheduler/add name="global-scripts" start-time=startup on-event="/system/script { run global-config; run global-functions; }"; @@ -2,9 +2,9 @@ # template scripts -> final scripts # markdown files -> html files -TEMPLATE = $(wildcard *.template) -CAPSMAN = $(TEMPLATE:.template=.capsman) -LOCAL = $(TEMPLATE:.template=.local) +TEMPLATE = $(wildcard *.template.rsc) +CAPSMAN = $(TEMPLATE:.template.rsc=.capsman.rsc) +LOCAL = $(TEMPLATE:.template.rsc=.local.rsc) MARKDOWN = $(wildcard *.md doc/*.md doc/mod/*.md) HTML = $(MARKDOWN:.md=.html) @@ -14,13 +14,13 @@ all: $(CAPSMAN) $(LOCAL) $(HTML) %.html: %.md Makefile markdown $< | sed 's/href="\([-_\./[:alnum:]]*\)\.md"/href="\1.html"/g' > $@ -%.local: %.template Makefile - sed -e '/\/caps-man/d' -e 's|%PATH%|interface\/wireless|' -e 's|%TEMPL%|$(suffix $@)|' \ +%.local.rsc: %.template.rsc Makefile + sed -e '/\/caps-man/d' -e 's|%PATH%|interface\/wireless|' -e 's|%TEMPL%|.local|' \ -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \ < $< > $@ -%.capsman: %.template Makefile - sed -e '/\/interface\/wireless/d' -e 's/%PATH%/caps-man/' -e 's/%TEMPL%/$(suffix $@)/' \ +%.capsman.rsc: %.template.rsc Makefile + sed -e '/\/interface\/wireless/d' -e 's|%PATH%|caps-man|' -e 's|%TEMPL%|.capsman|' \ -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \ < $< > $@ @@ -87,7 +87,7 @@ date and time is set correctly! Now let's download the main scripts and add them in configuration on the fly. - :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ /system/script/add name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script) output=user as-value]->"data"); }; + :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ /system/script/add name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data"); }; ![screenshot: import scripts](README.d/04-import-scripts.avif) @@ -112,7 +112,7 @@ Editing configuration The configuration needs to be tweaked for your needs. Edit `global-config-overlay`, copy relevant configuration from -[`global-config`](global-config) (the one without `-overlay`). +[`global-config`](global-config.rsc) (the one without `-overlay`). Save changes and exit with `Ctrl-o`. /system/script/edit global-config-overlay source; @@ -247,7 +247,7 @@ still use my scripts to manage and deploy yours, by specifying `base-url` This will fetch and install a script `hello-world.rsc` from the given url: - $ScriptInstallUpdate hello-world.rsc "base-url=https://git.eworm.de/cgit/routeros-scripts-custom/plain/"; + $ScriptInstallUpdate hello-world "base-url=https://git.eworm.de/cgit/routeros-scripts-custom/plain/"; ![screenshot: install custom script](README.d/13-install-custom-script.avif) diff --git a/accesslist-duplicates.capsman b/accesslist-duplicates.capsman index 8831cc6..2da00ca 100644 --- a/accesslist-duplicates.capsman +++ b/accesslist-duplicates.capsman @@ -1,42 +1,3 @@ #!rsc by RouterOS -# RouterOS script: accesslist-duplicates.capsman -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# print duplicate antries in wireless access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "accesslist-duplicates.capsman"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Read; - -:local Seen ({}); -:local Shown ({}); - -:foreach AccList in=[ /caps-man/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ - :local Mac [ /caps-man/access-list/get $AccList mac-address ]; - :foreach SeenMac in=$Seen do={ - :if ($SeenMac = $Mac) do={ - :local Skip 0; - :foreach ShownMac in=$Shown do={ - :if ($ShownMac = $Mac) do={ :set Skip 1; } - } - :if ($Skip = 0) do={ - /caps-man/access-list/print where mac-address=$Mac; - :set Shown ($Shown, $Mac); - - :put "\nNumeric id to remove, any key to skip!"; - :local Remove [ :tonum [ $Read ] ]; - :if ([ :typeof $Remove ] = "num") do={ - :put ("Removing numeric id " . $Remove . "...\n"); - /caps-man/access-list/remove $Remove; - } - } - } - } - :set Seen ($Seen, $Mac); -} +# dummy for migration diff --git a/accesslist-duplicates.capsman.rsc b/accesslist-duplicates.capsman.rsc new file mode 100644 index 0000000..8831cc6 --- /dev/null +++ b/accesslist-duplicates.capsman.rsc @@ -0,0 +1,42 @@ +#!rsc by RouterOS +# RouterOS script: accesslist-duplicates.capsman +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# print duplicate antries in wireless access list +# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md +# +# !! Do not edit this file, it is generated from template! + +:local 0 "accesslist-duplicates.capsman"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Read; + +:local Seen ({}); +:local Shown ({}); + +:foreach AccList in=[ /caps-man/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ + :local Mac [ /caps-man/access-list/get $AccList mac-address ]; + :foreach SeenMac in=$Seen do={ + :if ($SeenMac = $Mac) do={ + :local Skip 0; + :foreach ShownMac in=$Shown do={ + :if ($ShownMac = $Mac) do={ :set Skip 1; } + } + :if ($Skip = 0) do={ + /caps-man/access-list/print where mac-address=$Mac; + :set Shown ($Shown, $Mac); + + :put "\nNumeric id to remove, any key to skip!"; + :local Remove [ :tonum [ $Read ] ]; + :if ([ :typeof $Remove ] = "num") do={ + :put ("Removing numeric id " . $Remove . "...\n"); + /caps-man/access-list/remove $Remove; + } + } + } + } + :set Seen ($Seen, $Mac); +} diff --git a/accesslist-duplicates.local b/accesslist-duplicates.local index d4b8867..2da00ca 100644 --- a/accesslist-duplicates.local +++ b/accesslist-duplicates.local @@ -1,42 +1,3 @@ #!rsc by RouterOS -# RouterOS script: accesslist-duplicates.local -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# print duplicate antries in wireless access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "accesslist-duplicates.local"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Read; - -:local Seen ({}); -:local Shown ({}); - -:foreach AccList in=[ /interface/wireless/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ - :local Mac [ /interface/wireless/access-list/get $AccList mac-address ]; - :foreach SeenMac in=$Seen do={ - :if ($SeenMac = $Mac) do={ - :local Skip 0; - :foreach ShownMac in=$Shown do={ - :if ($ShownMac = $Mac) do={ :set Skip 1; } - } - :if ($Skip = 0) do={ - /interface/wireless/access-list/print where mac-address=$Mac; - :set Shown ($Shown, $Mac); - - :put "\nNumeric id to remove, any key to skip!"; - :local Remove [ :tonum [ $Read ] ]; - :if ([ :typeof $Remove ] = "num") do={ - :put ("Removing numeric id " . $Remove . "...\n"); - /interface/wireless/access-list/remove $Remove; - } - } - } - } - :set Seen ($Seen, $Mac); -} +# dummy for migration diff --git a/accesslist-duplicates.local.rsc b/accesslist-duplicates.local.rsc new file mode 100644 index 0000000..d4b8867 --- /dev/null +++ b/accesslist-duplicates.local.rsc @@ -0,0 +1,42 @@ +#!rsc by RouterOS +# RouterOS script: accesslist-duplicates.local +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# print duplicate antries in wireless access list +# https://git.eworm.de/cgit/routeros-scripts/about/doc/accesslist-duplicates.md +# +# !! Do not edit this file, it is generated from template! + +:local 0 "accesslist-duplicates.local"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Read; + +:local Seen ({}); +:local Shown ({}); + +:foreach AccList in=[ /interface/wireless/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ + :local Mac [ /interface/wireless/access-list/get $AccList mac-address ]; + :foreach SeenMac in=$Seen do={ + :if ($SeenMac = $Mac) do={ + :local Skip 0; + :foreach ShownMac in=$Shown do={ + :if ($ShownMac = $Mac) do={ :set Skip 1; } + } + :if ($Skip = 0) do={ + /interface/wireless/access-list/print where mac-address=$Mac; + :set Shown ($Shown, $Mac); + + :put "\nNumeric id to remove, any key to skip!"; + :local Remove [ :tonum [ $Read ] ]; + :if ([ :typeof $Remove ] = "num") do={ + :put ("Removing numeric id " . $Remove . "...\n"); + /interface/wireless/access-list/remove $Remove; + } + } + } + } + :set Seen ($Seen, $Mac); +} diff --git a/accesslist-duplicates.template b/accesslist-duplicates.template.rsc index 80c47a9..80c47a9 100644 --- a/accesslist-duplicates.template +++ b/accesslist-duplicates.template.rsc diff --git a/backup-cloud b/backup-cloud index b75d5cb..2da00ca 100644 --- a/backup-cloud +++ b/backup-cloud @@ -1,58 +1,3 @@ #!rsc by RouterOS -# RouterOS script: backup-cloud -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: backup-script -# -# upload backup to MikroTik cloud -# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-cloud.md - -:local 0 "backup-cloud"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global BackupPassword; -:global BackupRandomDelay; -:global Identity; - -:global DeviceInfo; -:global LogPrintExit2; -:global RandomDelay; -:global ScriptFromTerminal; -:global SendNotification2; -:global SymbolForNotification; -:global WaitFullyConnected; - -$WaitFullyConnected; - -:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; -} - -:do { - # we are not interested in output, but print is - # required to fetch information from cloud - /system/backup/cloud/print as-value; - :if ([ :len [ /system/backup/cloud/find ] ] > 0) do={ - /system/backup/cloud/upload-file action=create-and-upload \ - password=$BackupPassword replace=[ get ([ find ]->0) name ]; - } else={ - /system/backup/cloud/upload-file action=create-and-upload \ - password=$BackupPassword; - } - :local Cloud [ /system/backup/cloud/get ([ find ]->0) ]; - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "floppy-disk,cloud" ] . "Cloud backup"); \ - message=("Uploaded backup for " . $Identity . " to cloud.\n\n" . \ - [ $DeviceInfo ] . "\n\n" . \ - "Name: " . $Cloud->"name" . "\n" . \ - "Size: " . $Cloud->"size" . " B (" . ($Cloud->"size" / 1024) . " KiB)\n" . \ - "Download key: " . $Cloud->"secret-download-key"); silent=true }); -} on-error={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Cloud backup failed"); \ - message=("Failed uploading backup for " . $Identity . " to cloud!\n\n" . [ $DeviceInfo ]) }); - $LogPrintExit2 error $0 ("Failed uploading backup for " . $Identity . " to cloud!") true; -} +# dummy for migration diff --git a/backup-cloud.rsc b/backup-cloud.rsc new file mode 100644 index 0000000..b75d5cb --- /dev/null +++ b/backup-cloud.rsc @@ -0,0 +1,58 @@ +#!rsc by RouterOS +# RouterOS script: backup-cloud +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: backup-script +# +# upload backup to MikroTik cloud +# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-cloud.md + +:local 0 "backup-cloud"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global BackupPassword; +:global BackupRandomDelay; +:global Identity; + +:global DeviceInfo; +:global LogPrintExit2; +:global RandomDelay; +:global ScriptFromTerminal; +:global SendNotification2; +:global SymbolForNotification; +:global WaitFullyConnected; + +$WaitFullyConnected; + +:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ + $RandomDelay $BackupRandomDelay; +} + +:do { + # we are not interested in output, but print is + # required to fetch information from cloud + /system/backup/cloud/print as-value; + :if ([ :len [ /system/backup/cloud/find ] ] > 0) do={ + /system/backup/cloud/upload-file action=create-and-upload \ + password=$BackupPassword replace=[ get ([ find ]->0) name ]; + } else={ + /system/backup/cloud/upload-file action=create-and-upload \ + password=$BackupPassword; + } + :local Cloud [ /system/backup/cloud/get ([ find ]->0) ]; + + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "floppy-disk,cloud" ] . "Cloud backup"); \ + message=("Uploaded backup for " . $Identity . " to cloud.\n\n" . \ + [ $DeviceInfo ] . "\n\n" . \ + "Name: " . $Cloud->"name" . "\n" . \ + "Size: " . $Cloud->"size" . " B (" . ($Cloud->"size" / 1024) . " KiB)\n" . \ + "Download key: " . $Cloud->"secret-download-key"); silent=true }); +} on-error={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Cloud backup failed"); \ + message=("Failed uploading backup for " . $Identity . " to cloud!\n\n" . [ $DeviceInfo ]) }); + $LogPrintExit2 error $0 ("Failed uploading backup for " . $Identity . " to cloud!") true; +} diff --git a/backup-email b/backup-email index ba12494..2da00ca 100644 --- a/backup-email +++ b/backup-email @@ -1,107 +1,3 @@ #!rsc by RouterOS -# RouterOS script: backup-email -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: backup-script -# -# create and email backup and config file -# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-email.md - -:local 0 "backup-email"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global BackupPassword; -:global BackupRandomDelay; -:global BackupSendBinary; -:global BackupSendExport; -:global BackupSendGlobalConfig; -:global Domain; -:global Identity; - -:global CharacterReplace; -:global DeviceInfo; -:global LogPrintExit2; -:global MkDir; -:global RandomDelay; -:global ScriptFromTerminal; -:global SendEMail2; -:global SymbolForNotification; -:global WaitForFile; -:global WaitFullyConnected; - -:if ([ :typeof $SendEMail2 ] = "nothing") do={ - $LogPrintExit2 error $0 ("The module for sending notifications via e-mail is not installed.") true; -} - -:if ($BackupSendBinary != true && \ - $BackupSendExport != true) do={ - $LogPrintExit2 error $0 ("Configured to send neither backup nor config export.") true; -} - -$WaitFullyConnected; - -:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; -} - -# filename based on identity -:local DirName ("tmpfs/" . $0); -:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; -:local FilePath ($DirName . "/" . $FileName); -:local BackupFile "none"; -:local ExportFile "none"; -:local ConfigFile "none"; -:local Attach ({}); - -:if ([ $MkDir $DirName ] = false) do={ - $LogPrintExit2 error $0 ("Failed creating directory!") true; -} - -# binary backup -:if ($BackupSendBinary = true) do={ - /system/backup/save encryption=aes-sha256 name=$FilePath password=$BackupPassword; - $WaitForFile ($FilePath . ".backup"); - :set BackupFile ($FileName . ".backup"); - :set Attach ($Attach, ($FilePath . ".backup")); -} - -# create configuration export -:if ($BackupSendExport = true) do={ - /export terse show-sensitive file=$FilePath; - $WaitForFile ($FilePath . ".rsc"); - :set ExportFile ($FileName . ".rsc"); - :set Attach ($Attach, ($FilePath . ".rsc")); -} - -# global-config-overlay -:if ($BackupSendGlobalConfig = true) do={ - :execute script={ :put [ /system/script/get global-config-overlay source ]; } \ - file=($FilePath . ".conf"); - $WaitForFile ($FilePath . ".conf.txt"); - :set ConfigFile ($FileName . ".conf.txt"); - :set Attach ($Attach, ($FilePath . ".conf.txt")); -} - -# send email with status and files -$SendEMail2 ({ origin=$0; \ - subject=([ $SymbolForNotification "floppy-disk,incoming-envelope" ] . \ - "Backup & Config"); \ - message=("See attached files for backup and config export for " . \ - $Identity . ".\n\n" . \ - [ $DeviceInfo ] . "\n\n" . \ - "Backup file: " . $BackupFile . "\n" . \ - "Export file: " . $ExportFile . "\n" . \ - "Config file: " . $ConfigFile); \ - attach=$Attach; remove-attach=true }); - -# wait for the mail to be sent -:local I 0; -:while ([ :len [ /file/find where name ~ ($FilePath . "\\.(backup|rsc)\$") ] ] > 0) do={ - :if ($I >= 120) do={ - $LogPrintExit2 warning $0 ("Files are still available, sending e-mail failed.") true; - } - :delay 1s; - :set I ($I + 1); -} +# dummy for migration diff --git a/backup-email.rsc b/backup-email.rsc new file mode 100644 index 0000000..ba12494 --- /dev/null +++ b/backup-email.rsc @@ -0,0 +1,107 @@ +#!rsc by RouterOS +# RouterOS script: backup-email +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: backup-script +# +# create and email backup and config file +# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-email.md + +:local 0 "backup-email"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global BackupPassword; +:global BackupRandomDelay; +:global BackupSendBinary; +:global BackupSendExport; +:global BackupSendGlobalConfig; +:global Domain; +:global Identity; + +:global CharacterReplace; +:global DeviceInfo; +:global LogPrintExit2; +:global MkDir; +:global RandomDelay; +:global ScriptFromTerminal; +:global SendEMail2; +:global SymbolForNotification; +:global WaitForFile; +:global WaitFullyConnected; + +:if ([ :typeof $SendEMail2 ] = "nothing") do={ + $LogPrintExit2 error $0 ("The module for sending notifications via e-mail is not installed.") true; +} + +:if ($BackupSendBinary != true && \ + $BackupSendExport != true) do={ + $LogPrintExit2 error $0 ("Configured to send neither backup nor config export.") true; +} + +$WaitFullyConnected; + +:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ + $RandomDelay $BackupRandomDelay; +} + +# filename based on identity +:local DirName ("tmpfs/" . $0); +:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; +:local FilePath ($DirName . "/" . $FileName); +:local BackupFile "none"; +:local ExportFile "none"; +:local ConfigFile "none"; +:local Attach ({}); + +:if ([ $MkDir $DirName ] = false) do={ + $LogPrintExit2 error $0 ("Failed creating directory!") true; +} + +# binary backup +:if ($BackupSendBinary = true) do={ + /system/backup/save encryption=aes-sha256 name=$FilePath password=$BackupPassword; + $WaitForFile ($FilePath . ".backup"); + :set BackupFile ($FileName . ".backup"); + :set Attach ($Attach, ($FilePath . ".backup")); +} + +# create configuration export +:if ($BackupSendExport = true) do={ + /export terse show-sensitive file=$FilePath; + $WaitForFile ($FilePath . ".rsc"); + :set ExportFile ($FileName . ".rsc"); + :set Attach ($Attach, ($FilePath . ".rsc")); +} + +# global-config-overlay +:if ($BackupSendGlobalConfig = true) do={ + :execute script={ :put [ /system/script/get global-config-overlay source ]; } \ + file=($FilePath . ".conf"); + $WaitForFile ($FilePath . ".conf.txt"); + :set ConfigFile ($FileName . ".conf.txt"); + :set Attach ($Attach, ($FilePath . ".conf.txt")); +} + +# send email with status and files +$SendEMail2 ({ origin=$0; \ + subject=([ $SymbolForNotification "floppy-disk,incoming-envelope" ] . \ + "Backup & Config"); \ + message=("See attached files for backup and config export for " . \ + $Identity . ".\n\n" . \ + [ $DeviceInfo ] . "\n\n" . \ + "Backup file: " . $BackupFile . "\n" . \ + "Export file: " . $ExportFile . "\n" . \ + "Config file: " . $ConfigFile); \ + attach=$Attach; remove-attach=true }); + +# wait for the mail to be sent +:local I 0; +:while ([ :len [ /file/find where name ~ ($FilePath . "\\.(backup|rsc)\$") ] ] > 0) do={ + :if ($I >= 120) do={ + $LogPrintExit2 warning $0 ("Files are still available, sending e-mail failed.") true; + } + :delay 1s; + :set I ($I + 1); +} diff --git a/backup-partition b/backup-partition index 824cb7e..2da00ca 100644 --- a/backup-partition +++ b/backup-partition @@ -1,36 +1,3 @@ #!rsc by RouterOS -# RouterOS script: backup-partition -# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: backup-script -# -# save configuration to fallback partition -# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-partition.md - -:local 0 "backup-partition"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; - -:if ([ :len [ /partitions/find ] ] < 2) do={ - $LogPrintExit2 error $0 ("Device does not have a fallback partition.") true; -} - -:local ActiveRunning [ /partitions/find where active running ]; - -:if ([ :len $ActiveRunning ] < 1) do={ - $LogPrintExit2 error $0 ("Device is not running from active partition.") true; -} - -:local ActiveRunningVar [ /partitions/get $ActiveRunning ]; - -:do { - /partitions/save-config-to ($ActiveRunningVar->"fallback-to"); - $LogPrintExit2 info $0 ("Saved configuration to partition '" . \ - ($ActiveRunningVar->"fallback-to") . "'.") false; -} on-error={ - $LogPrintExit2 error $0 ("Failed saving configuration to partition '" . \ - ($ActiveRunningVar->"fallback-to") . "'!") true; -} +# dummy for migration diff --git a/backup-partition.rsc b/backup-partition.rsc new file mode 100644 index 0000000..824cb7e --- /dev/null +++ b/backup-partition.rsc @@ -0,0 +1,36 @@ +#!rsc by RouterOS +# RouterOS script: backup-partition +# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: backup-script +# +# save configuration to fallback partition +# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-partition.md + +:local 0 "backup-partition"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; + +:if ([ :len [ /partitions/find ] ] < 2) do={ + $LogPrintExit2 error $0 ("Device does not have a fallback partition.") true; +} + +:local ActiveRunning [ /partitions/find where active running ]; + +:if ([ :len $ActiveRunning ] < 1) do={ + $LogPrintExit2 error $0 ("Device is not running from active partition.") true; +} + +:local ActiveRunningVar [ /partitions/get $ActiveRunning ]; + +:do { + /partitions/save-config-to ($ActiveRunningVar->"fallback-to"); + $LogPrintExit2 info $0 ("Saved configuration to partition '" . \ + ($ActiveRunningVar->"fallback-to") . "'.") false; +} on-error={ + $LogPrintExit2 error $0 ("Failed saving configuration to partition '" . \ + ($ActiveRunningVar->"fallback-to") . "'!") true; +} diff --git a/backup-upload b/backup-upload index 4c09d4a..2da00ca 100644 --- a/backup-upload +++ b/backup-upload @@ -1,129 +1,3 @@ #!rsc by RouterOS -# RouterOS script: backup-upload -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: backup-script -# -# create and upload backup and config file -# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-upload.md - -:local 0 "backup-upload"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global BackupPassword; -:global BackupRandomDelay; -:global BackupSendBinary; -:global BackupSendExport; -:global BackupSendGlobalConfig; -:global BackupUploadPass; -:global BackupUploadUrl; -:global BackupUploadUser; -:global Domain; -:global Identity; - -:global CharacterReplace; -:global DeviceInfo; -:global IfThenElse; -:global LogPrintExit2; -:global MkDir; -:global RandomDelay; -:global ScriptFromTerminal; -:global SendNotification2; -:global SymbolForNotification; -:global WaitForFile; -:global WaitFullyConnected; - -:if ($BackupSendBinary != true && \ - $BackupSendExport != true) do={ - $LogPrintExit2 error $0 ("Configured to send neither backup nor config export.") true; -} - -$WaitFullyConnected; - -:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; -} - -# filename based on identity -:local DirName ("tmpfs/" . $0); -:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; -:local FilePath ($DirName . "/" . $FileName); -:local BackupFile "none"; -:local ExportFile "none"; -:local ConfigFile "none"; -:local Failed 0; - -:if ([ $MkDir $DirName ] = false) do={ - $LogPrintExit2 error $0 ("Failed creating directory!") true; -} - -# binary backup -:if ($BackupSendBinary = true) do={ - /system/backup/save encryption=aes-sha256 name=$FilePath password=$BackupPassword; - $WaitForFile ($FilePath . ".backup"); - - :do { - /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".backup") \ - user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".backup"); - :set BackupFile ($FileName . ".backup"); - } on-error={ - $LogPrintExit2 error $0 ("Uploading backup file failed!") false; - :set BackupFile "failed"; - :set Failed 1; - } - - /file/remove ($FilePath . ".backup"); -} - -# create configuration export -:if ($BackupSendExport = true) do={ - /export terse show-sensitive file=$FilePath; - $WaitForFile ($FilePath . ".rsc"); - - :do { - /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \ - user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".rsc"); - :set ExportFile ($FileName . ".rsc"); - } on-error={ - $LogPrintExit2 error $0 ("Uploading configuration export failed!") false; - :set ExportFile "failed"; - :set Failed 1; - } - - /file/remove ($FilePath . ".rsc"); -} - -# global-config-overlay -:if ($BackupSendGlobalConfig = true) do={ - :execute script={ :put [ /system/script/get global-config-overlay source ]; } \ - file=($FilePath . ".conf"); - $WaitForFile ($FilePath . ".conf.txt"); - - :do { - /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".conf") \ - user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".conf.txt"); - :set ConfigFile ($FileName . ".conf"); - } on-error={ - $LogPrintExit2 error $0 ("Uploading global-config-overlay failed!") false; - :set ConfigFile "failed"; - :set Failed 1; - } - - /file/remove ($FilePath . ".conf.txt"); -} - -$SendNotification2 ({ origin=$0; \ - subject=[ $IfThenElse ($Failed > 0) \ - ([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Backup & Config upload with failure") \ - ([ $SymbolForNotification "floppy-disk,up-arrow" ] . "Backup & Config upload") ]; \ - message=("Backup and config export upload for " . $Identity . ".\n\n" . \ - [ $DeviceInfo ] . "\n\n" . \ - "Backup file: " . $BackupFile . "\n" . \ - "Export file: " . $ExportFile . "\n" . \ - "Config file: " . $ConfigFile); silent=true }); - -:if ($Failed = 1) do={ - :error "An error occured!"; -} +# dummy for migration diff --git a/backup-upload.rsc b/backup-upload.rsc new file mode 100644 index 0000000..4c09d4a --- /dev/null +++ b/backup-upload.rsc @@ -0,0 +1,129 @@ +#!rsc by RouterOS +# RouterOS script: backup-upload +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: backup-script +# +# create and upload backup and config file +# https://git.eworm.de/cgit/routeros-scripts/about/doc/backup-upload.md + +:local 0 "backup-upload"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global BackupPassword; +:global BackupRandomDelay; +:global BackupSendBinary; +:global BackupSendExport; +:global BackupSendGlobalConfig; +:global BackupUploadPass; +:global BackupUploadUrl; +:global BackupUploadUser; +:global Domain; +:global Identity; + +:global CharacterReplace; +:global DeviceInfo; +:global IfThenElse; +:global LogPrintExit2; +:global MkDir; +:global RandomDelay; +:global ScriptFromTerminal; +:global SendNotification2; +:global SymbolForNotification; +:global WaitForFile; +:global WaitFullyConnected; + +:if ($BackupSendBinary != true && \ + $BackupSendExport != true) do={ + $LogPrintExit2 error $0 ("Configured to send neither backup nor config export.") true; +} + +$WaitFullyConnected; + +:if ([ $ScriptFromTerminal $0 ] = false && $BackupRandomDelay > 0) do={ + $RandomDelay $BackupRandomDelay; +} + +# filename based on identity +:local DirName ("tmpfs/" . $0); +:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; +:local FilePath ($DirName . "/" . $FileName); +:local BackupFile "none"; +:local ExportFile "none"; +:local ConfigFile "none"; +:local Failed 0; + +:if ([ $MkDir $DirName ] = false) do={ + $LogPrintExit2 error $0 ("Failed creating directory!") true; +} + +# binary backup +:if ($BackupSendBinary = true) do={ + /system/backup/save encryption=aes-sha256 name=$FilePath password=$BackupPassword; + $WaitForFile ($FilePath . ".backup"); + + :do { + /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".backup") \ + user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".backup"); + :set BackupFile ($FileName . ".backup"); + } on-error={ + $LogPrintExit2 error $0 ("Uploading backup file failed!") false; + :set BackupFile "failed"; + :set Failed 1; + } + + /file/remove ($FilePath . ".backup"); +} + +# create configuration export +:if ($BackupSendExport = true) do={ + /export terse show-sensitive file=$FilePath; + $WaitForFile ($FilePath . ".rsc"); + + :do { + /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \ + user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".rsc"); + :set ExportFile ($FileName . ".rsc"); + } on-error={ + $LogPrintExit2 error $0 ("Uploading configuration export failed!") false; + :set ExportFile "failed"; + :set Failed 1; + } + + /file/remove ($FilePath . ".rsc"); +} + +# global-config-overlay +:if ($BackupSendGlobalConfig = true) do={ + :execute script={ :put [ /system/script/get global-config-overlay source ]; } \ + file=($FilePath . ".conf"); + $WaitForFile ($FilePath . ".conf.txt"); + + :do { + /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".conf") \ + user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".conf.txt"); + :set ConfigFile ($FileName . ".conf"); + } on-error={ + $LogPrintExit2 error $0 ("Uploading global-config-overlay failed!") false; + :set ConfigFile "failed"; + :set Failed 1; + } + + /file/remove ($FilePath . ".conf.txt"); +} + +$SendNotification2 ({ origin=$0; \ + subject=[ $IfThenElse ($Failed > 0) \ + ([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Backup & Config upload with failure") \ + ([ $SymbolForNotification "floppy-disk,up-arrow" ] . "Backup & Config upload") ]; \ + message=("Backup and config export upload for " . $Identity . ".\n\n" . \ + [ $DeviceInfo ] . "\n\n" . \ + "Backup file: " . $BackupFile . "\n" . \ + "Export file: " . $ExportFile . "\n" . \ + "Config file: " . $ConfigFile); silent=true }); + +:if ($Failed = 1) do={ + :error "An error occured!"; +} diff --git a/capsman-download-packages b/capsman-download-packages index 08edd59..2da00ca 100644 --- a/capsman-download-packages +++ b/capsman-download-packages @@ -1,86 +1,3 @@ #!rsc by RouterOS -# RouterOS script: capsman-download-packages -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# download and cleanup packages for CAP installation from CAPsMAN -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-download-packages.md - -:local 0 "capsman-download-packages"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global CleanFilePath; -:global DownloadPackage; -:global LogPrintExit2; -:global MkDir; -:global ScriptLock; -:global WaitFullyConnected; - -$ScriptLock $0; -$WaitFullyConnected; - -:local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ]; -:local InstalledVersion [ /system/package/update/get installed-version ]; -:local Updated false; - -:if ([ :len $PackagePath ] = 0) do={ - $LogPrintExit2 warning $0 ("The CAPsMAN package path is not defined, can not download packages.") true; -} - -:if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ - :if ([ $MkDir $PackagePath ] = false) do={ - $LogPrintExit2 warning $0 ("Creating directory at CAPsMAN package path (" . \ - $PackagePath . ") failed!") true; - } - $LogPrintExit2 info $0 ("Created directory at CAPsMAN package path (" . $PackagePath . \ - "). Please place your packages!") false; -} - -:foreach Package in=[ /file/find where type=package \ - package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={ - :local File [ /file/get $Package ]; - :if ($File->"package-architecture" = "mips") do={ - :set ($File->"package-architecture") "mipsbe"; - } - :if ([ $DownloadPackage ($File->"package-name") $InstalledVersion \ - ($File->"package-architecture") $PackagePath ] = true) do={ - :set Updated true; - /file/remove $Package; - } -} - -:if ([ :len [ /system/logging/find where topics~"error" !(topics~"!error") \ - !(topics~"!caps") action=memory !disabled !invalid ] ] < 1) do={ - $LogPrintExit2 warning $0 ("Looks like error messages for 'caps' are not sent to memory. " . \ - "Probably can not download packages automatically.") false; -} else={ - :if ($Updated = false && [ /system/resource/get uptime ] < 2m) do={ - $LogPrintExit2 info $0 ("No packages downloaded, yet. Delaying for logs.") false; - :delay 2m; - } -} - -:foreach Log in=[ /log/find where topics=({"caps"; "error"}) \ - message~("upgrade status: failed, failed to download file '.*-" . $InstalledVersion . \ - "-.*\\.npk', no such file") ] do={ - :local Message [ /log/get $Log message ]; - :local Package [ :pick $Message \ - ([ :find $Message "'" ] + 1) \ - [ :find $Message ("-" . $InstalledVersion . "-") ] ]; - :local Arch [ :pick $Message \ - ([ :find $Message ("-" . $InstalledVersion . "-") ] + 2 + [ :len $InstalledVersion ]) \ - [ :find $Message ".npk" ] ]; - :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={ - :set Updated true; - } -} - -:if ($Updated = true) do={ - :if ([ :len [ /system/script/find where name="capsman-rolling-upgrade" ] ] > 0) do={ - /system/script/run capsman-rolling-upgrade; - } else={ - /caps-man/remote-cap/upgrade [ find where version!=$InstalledVersion ]; - } -} +# dummy for migration diff --git a/capsman-download-packages.rsc b/capsman-download-packages.rsc new file mode 100644 index 0000000..08edd59 --- /dev/null +++ b/capsman-download-packages.rsc @@ -0,0 +1,86 @@ +#!rsc by RouterOS +# RouterOS script: capsman-download-packages +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# download and cleanup packages for CAP installation from CAPsMAN +# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-download-packages.md + +:local 0 "capsman-download-packages"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global CleanFilePath; +:global DownloadPackage; +:global LogPrintExit2; +:global MkDir; +:global ScriptLock; +:global WaitFullyConnected; + +$ScriptLock $0; +$WaitFullyConnected; + +:local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ]; +:local InstalledVersion [ /system/package/update/get installed-version ]; +:local Updated false; + +:if ([ :len $PackagePath ] = 0) do={ + $LogPrintExit2 warning $0 ("The CAPsMAN package path is not defined, can not download packages.") true; +} + +:if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ + :if ([ $MkDir $PackagePath ] = false) do={ + $LogPrintExit2 warning $0 ("Creating directory at CAPsMAN package path (" . \ + $PackagePath . ") failed!") true; + } + $LogPrintExit2 info $0 ("Created directory at CAPsMAN package path (" . $PackagePath . \ + "). Please place your packages!") false; +} + +:foreach Package in=[ /file/find where type=package \ + package-version!=$InstalledVersion name~("^" . $PackagePath) ] do={ + :local File [ /file/get $Package ]; + :if ($File->"package-architecture" = "mips") do={ + :set ($File->"package-architecture") "mipsbe"; + } + :if ([ $DownloadPackage ($File->"package-name") $InstalledVersion \ + ($File->"package-architecture") $PackagePath ] = true) do={ + :set Updated true; + /file/remove $Package; + } +} + +:if ([ :len [ /system/logging/find where topics~"error" !(topics~"!error") \ + !(topics~"!caps") action=memory !disabled !invalid ] ] < 1) do={ + $LogPrintExit2 warning $0 ("Looks like error messages for 'caps' are not sent to memory. " . \ + "Probably can not download packages automatically.") false; +} else={ + :if ($Updated = false && [ /system/resource/get uptime ] < 2m) do={ + $LogPrintExit2 info $0 ("No packages downloaded, yet. Delaying for logs.") false; + :delay 2m; + } +} + +:foreach Log in=[ /log/find where topics=({"caps"; "error"}) \ + message~("upgrade status: failed, failed to download file '.*-" . $InstalledVersion . \ + "-.*\\.npk', no such file") ] do={ + :local Message [ /log/get $Log message ]; + :local Package [ :pick $Message \ + ([ :find $Message "'" ] + 1) \ + [ :find $Message ("-" . $InstalledVersion . "-") ] ]; + :local Arch [ :pick $Message \ + ([ :find $Message ("-" . $InstalledVersion . "-") ] + 2 + [ :len $InstalledVersion ]) \ + [ :find $Message ".npk" ] ]; + :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={ + :set Updated true; + } +} + +:if ($Updated = true) do={ + :if ([ :len [ /system/script/find where name="capsman-rolling-upgrade" ] ] > 0) do={ + /system/script/run capsman-rolling-upgrade; + } else={ + /caps-man/remote-cap/upgrade [ find where version!=$InstalledVersion ]; + } +} diff --git a/capsman-rolling-upgrade b/capsman-rolling-upgrade index 1f4a51c..2da00ca 100644 --- a/capsman-rolling-upgrade +++ b/capsman-rolling-upgrade @@ -1,36 +1,3 @@ #!rsc by RouterOS -# RouterOS script: capsman-rolling-upgrade -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# upgrade CAPs one after another -# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-rolling-upgrade.md - -:local 0 "capsman-rolling-upgrade"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; -:global ScriptLock; - -$ScriptLock $0; - -:local InstalledVersion [ /system/package/update/get installed-version ]; - -:local RemoteCapCount [ :len [ /caps-man/remote-cap/find ] ]; -:if ($RemoteCapCount > 0) do={ - :local Delay (600 / $RemoteCapCount); - :if ($Delay > 120) do={ :set Delay 120; } - :foreach RemoteCap in=[ /caps-man/remote-cap/find where version!=$InstalledVersion ] do={ - :local RemoteCapVal [ /caps-man/remote-cap/get $RemoteCap ]; - :if ([ :len $RemoteCapVal ] > 1) do={ - $LogPrintExit2 info $0 ("Starting upgrade for " . $RemoteCapVal->"name" . \ - " (" . $RemoteCapVal->"identity" . ")...") false; - /caps-man/remote-cap/upgrade $RemoteCap; - } else={ - $LogPrintExit2 warning $0 ("Remote CAP vanished, skipping upgrade.") false; - } - :delay ($Delay . "s"); - } -} +# dummy for migration diff --git a/capsman-rolling-upgrade.rsc b/capsman-rolling-upgrade.rsc new file mode 100644 index 0000000..1f4a51c --- /dev/null +++ b/capsman-rolling-upgrade.rsc @@ -0,0 +1,36 @@ +#!rsc by RouterOS +# RouterOS script: capsman-rolling-upgrade +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# upgrade CAPs one after another +# https://git.eworm.de/cgit/routeros-scripts/about/doc/capsman-rolling-upgrade.md + +:local 0 "capsman-rolling-upgrade"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; +:global ScriptLock; + +$ScriptLock $0; + +:local InstalledVersion [ /system/package/update/get installed-version ]; + +:local RemoteCapCount [ :len [ /caps-man/remote-cap/find ] ]; +:if ($RemoteCapCount > 0) do={ + :local Delay (600 / $RemoteCapCount); + :if ($Delay > 120) do={ :set Delay 120; } + :foreach RemoteCap in=[ /caps-man/remote-cap/find where version!=$InstalledVersion ] do={ + :local RemoteCapVal [ /caps-man/remote-cap/get $RemoteCap ]; + :if ([ :len $RemoteCapVal ] > 1) do={ + $LogPrintExit2 info $0 ("Starting upgrade for " . $RemoteCapVal->"name" . \ + " (" . $RemoteCapVal->"identity" . ")...") false; + /caps-man/remote-cap/upgrade $RemoteCap; + } else={ + $LogPrintExit2 warning $0 ("Remote CAP vanished, skipping upgrade.") false; + } + :delay ($Delay . "s"); + } +} diff --git a/certificate-renew-issued b/certificate-renew-issued index c297b15..2da00ca 100644 --- a/certificate-renew-issued +++ b/certificate-renew-issued @@ -1,38 +1,3 @@ #!rsc by RouterOS -# RouterOS script: certificate-renew-issued -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# renew locally issued certificates -# https://git.eworm.de/cgit/routeros-scripts/about/doc/certificate-renew-issued.md - -:local 0 "certificate-renew-issued"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global CertIssuedExportPass; - -:global LogPrintExit2; -:global MkDir; - -:foreach Cert in=[ /certificate/find where issued expires-after<3w ] do={ - :local CertVal [ /certificate/get $Cert ]; - /certificate/issued-revoke $Cert; - /certificate/set name=($CertVal->"name" . "-revoked-" . [ /system/clock/get date ]) $Cert; - /certificate/add name=($CertVal->"name") common-name=($CertVal->"common-name") \ - key-usage=($CertVal->"key-usage") subject-alt-name=($CertVal->"subject-alt-name"); - /certificate/sign ($CertVal->"name") ca=($CertVal->"ca"); - :if ([ :typeof ($CertIssuedExportPass->($CertVal->"common-name")) ] = "str") do={ - :if ([ $MkDir "cert-issued" ] = true) do={ - /certificate/export-certificate ($CertVal->"name") type=pkcs12 \ - file-name=("cert-issued/" . $CertVal->"common-name") \ - export-passphrase=($CertIssuedExportPass->($CertVal->"common-name")); - $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . \ - "\", exported to \"cert-issued/" . $CertVal->"common-name" . ".p12\".") false; - } else={ - $LogPrintExit2 warning $0 ("Failed creating directory, not exporting certificate.") false; - } - } else={ - $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . "\".") false; - } -} +# dummy for migration diff --git a/certificate-renew-issued.rsc b/certificate-renew-issued.rsc new file mode 100644 index 0000000..c297b15 --- /dev/null +++ b/certificate-renew-issued.rsc @@ -0,0 +1,38 @@ +#!rsc by RouterOS +# RouterOS script: certificate-renew-issued +# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# renew locally issued certificates +# https://git.eworm.de/cgit/routeros-scripts/about/doc/certificate-renew-issued.md + +:local 0 "certificate-renew-issued"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global CertIssuedExportPass; + +:global LogPrintExit2; +:global MkDir; + +:foreach Cert in=[ /certificate/find where issued expires-after<3w ] do={ + :local CertVal [ /certificate/get $Cert ]; + /certificate/issued-revoke $Cert; + /certificate/set name=($CertVal->"name" . "-revoked-" . [ /system/clock/get date ]) $Cert; + /certificate/add name=($CertVal->"name") common-name=($CertVal->"common-name") \ + key-usage=($CertVal->"key-usage") subject-alt-name=($CertVal->"subject-alt-name"); + /certificate/sign ($CertVal->"name") ca=($CertVal->"ca"); + :if ([ :typeof ($CertIssuedExportPass->($CertVal->"common-name")) ] = "str") do={ + :if ([ $MkDir "cert-issued" ] = true) do={ + /certificate/export-certificate ($CertVal->"name") type=pkcs12 \ + file-name=("cert-issued/" . $CertVal->"common-name") \ + export-passphrase=($CertIssuedExportPass->($CertVal->"common-name")); + $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . \ + "\", exported to \"cert-issued/" . $CertVal->"common-name" . ".p12\".") false; + } else={ + $LogPrintExit2 warning $0 ("Failed creating directory, not exporting certificate.") false; + } + } else={ + $LogPrintExit2 info $0 ("Issued a new certificate for \"" . $CertVal->"common-name" . "\".") false; + } +} diff --git a/check-certificates b/check-certificates index 8a06f8b..2da00ca 100644 --- a/check-certificates +++ b/check-certificates @@ -1,138 +1,3 @@ #!rsc by RouterOS -# RouterOS script: check-certificates -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# check for certificate validity -# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-certificates.md - -:local 0 "check-certificates"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global CertRenewPass; -:global CertRenewTime; -:global CertRenewUrl; -:global CertWarnTime; -:global Identity; - -:global CertificateAvailable -:global CertificateNameByCN; -:global IfThenElse; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitForFile; -:global WaitFullyConnected; - -:local FormatExpire do={ - :global CharacterReplace; - :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ]; -} - -$WaitFullyConnected; - -:foreach Cert in=[ /certificate/find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={ - :local CertVal [ /certificate/get $Cert ]; - - :do { - :if ([ :len $CertRenewUrl ] = 0) do={ - $LogPrintExit2 info $0 ("No CertRenewUrl given.") true; - } - $LogPrintExit2 info $0 ("Attempting to renew certificate " . ($CertVal->"name") . ".") false; - - :foreach Type in={ ".pem"; ".p12" } do={ - :local CertFileName ([ $UrlEncode ($CertVal->"common-name") ] . $Type); - :do { - /tool/fetch check-certificate=yes-without-crl \ - ($CertRenewUrl . $CertFileName) dst-path=$CertFileName as-value; - $WaitForFile $CertFileName; - - :local DecryptionFailed true; - :foreach PassPhrase in=$CertRenewPass do={ - :local Result [ /certificate/import file-name=$CertFileName passphrase=$PassPhrase as-value ]; - :if ($Result->"decryption-failures" = 0) do={ - :set DecryptionFailed false; - } - } - /file/remove [ find where name=$CertFileName ]; - - :if ($DecryptionFailed = true) do={ - $LogPrintExit2 warning $0 ("Decryption failed for certificate file " . $CertFileName) false; - } - - :foreach CertInChain in=[ /certificate/find where name~("^" . $CertFileName . "_[0-9]+\$") common-name!=($CertVal->"common-name") ] do={ - $CertificateNameByCN [ /certificate/get $CertInChain common-name ]; - } - } on-error={ - $LogPrintExit2 debug $0 ("Could not download certificate file " . $CertFileName) false; - } - } - - :local CertNew [ /certificate/find where common-name=($CertVal->"common-name") fingerprint!=[ :tostr ($CertVal->"fingerprint") ] expires-after>$CertRenewTime ]; - :local CertNewVal [ /certificate/get $CertNew ]; - - :if ([ $CertificateAvailable ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") ] = false) do={ - $LogPrintExit2 warning $0 ("The certificate chain is not available!") false; - } - - :if ($Cert != $CertNew) do={ - $LogPrintExit2 debug $0 ("Certificate '" . $CertVal->"name" . "' was not updated, but replaced.") false; - - :if (($CertVal->"private-key") = true && ($CertVal->"private-key") != ($CertNewVal->"private-key")) do={ - /certificate/remove $CertNew; - $LogPrintExit2 warning $0 ("Old certificate '" . ($CertVal->"name") . "' has a private key, new certificate does not. Aborting renew.") true; - } - - /ip/service/set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ]; - - /ip/ipsec/identity/set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ]; - /ip/ipsec/identity/set remote-certificate=($CertNewVal->"name") [ find where remote-certificate=($CertVal->"name") ]; - - /ip/hotspot/profile/set ssl-certificate=($CertNewVal->"name") [ find where ssl-certificate=($CertVal->"name") ]; - - /certificate/remove $Cert; - /certificate/set $CertNew name=($CertVal->"name"); - } - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "lock-with-ink-pen" ] . "Certificate renewed"); \ - message=("A certificate on " . $Identity . " has been renewed.\n\n" . \ - "Name: " . ($CertVal->"name") . "\n" . \ - "CommonName: " . ($CertNewVal->"common-name") . "\n" . \ - "Private key: " . [ $IfThenElse (($CertNewVal->"private-key") = true) "available" "missing" ] . "\n" . \ - "Fingerprint: " . ($CertNewVal->"fingerprint") . "\n" . \ - "Issuer: " . ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") . "\n" . \ - "Validity: " . ($CertNewVal->"invalid-before") . " to " . ($CertNewVal->"invalid-after") . "\n" . \ - "Expires in: " . [ $FormatExpire ($CertNewVal->"expires-after") ]); silent=true }); - $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " has been renewed.") false; - } on-error={ - $LogPrintExit2 debug $0 ("Could not renew certificate " . ($CertVal->"name") . ".") false; - } -} - -:foreach Cert in=[ /certificate/find where !revoked !scep-url !(expires-after=[]) \ - expires-after<$CertWarnTime !(fingerprint=[]) ] do={ - :local CertVal [ /certificate/get $Cert ]; - - :if ([ :len [ /certificate/scep-server/find where ca-cert=($CertVal->"ca") ] ] > 0) do={ - $LogPrintExit2 debug $0 ("Certificate \"" . ($CertVal->"name") . "\" is handled by SCEP, skipping.") false; - } else={ - :local State [ $IfThenElse (($CertVal->"expired") = true) "expired" "is about to expire" ]; - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "warning-sign" ] . "Certificate warning!"); \ - message=("A certificate on " . $Identity . " " . $State . ".\n\n" . \ - "Name: " . ($CertVal->"name") . "\n" . \ - "CommonName: " . ($CertVal->"common-name") . "\n" . \ - "Private key: " . [ $IfThenElse (($CertVal->"private-key") = true) "available" "missing" ] . "\n" . \ - "Fingerprint: " . ($CertVal->"fingerprint") . "\n" . \ - "Issuer: " . ($CertVal->"ca") . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\n" . \ - "Validity: " . ($CertVal->"invalid-before") . " to " . ($CertVal->"invalid-after") . "\n" . \ - "Expires in: " . [ $IfThenElse (($CertVal->"expired") = true) "expired" [ $FormatExpire ($CertVal->"expires-after") ] ]) }); - $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " " . $State . \ - ", it is invalid after " . ($CertVal->"invalid-after") . ".") false; - } -} +# dummy for migration diff --git a/check-certificates.rsc b/check-certificates.rsc new file mode 100644 index 0000000..8a06f8b --- /dev/null +++ b/check-certificates.rsc @@ -0,0 +1,138 @@ +#!rsc by RouterOS +# RouterOS script: check-certificates +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# check for certificate validity +# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-certificates.md + +:local 0 "check-certificates"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global CertRenewPass; +:global CertRenewTime; +:global CertRenewUrl; +:global CertWarnTime; +:global Identity; + +:global CertificateAvailable +:global CertificateNameByCN; +:global IfThenElse; +:global LogPrintExit2; +:global ParseKeyValueStore; +:global SendNotification2; +:global SymbolForNotification; +:global UrlEncode; +:global WaitForFile; +:global WaitFullyConnected; + +:local FormatExpire do={ + :global CharacterReplace; + :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ]; +} + +$WaitFullyConnected; + +:foreach Cert in=[ /certificate/find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={ + :local CertVal [ /certificate/get $Cert ]; + + :do { + :if ([ :len $CertRenewUrl ] = 0) do={ + $LogPrintExit2 info $0 ("No CertRenewUrl given.") true; + } + $LogPrintExit2 info $0 ("Attempting to renew certificate " . ($CertVal->"name") . ".") false; + + :foreach Type in={ ".pem"; ".p12" } do={ + :local CertFileName ([ $UrlEncode ($CertVal->"common-name") ] . $Type); + :do { + /tool/fetch check-certificate=yes-without-crl \ + ($CertRenewUrl . $CertFileName) dst-path=$CertFileName as-value; + $WaitForFile $CertFileName; + + :local DecryptionFailed true; + :foreach PassPhrase in=$CertRenewPass do={ + :local Result [ /certificate/import file-name=$CertFileName passphrase=$PassPhrase as-value ]; + :if ($Result->"decryption-failures" = 0) do={ + :set DecryptionFailed false; + } + } + /file/remove [ find where name=$CertFileName ]; + + :if ($DecryptionFailed = true) do={ + $LogPrintExit2 warning $0 ("Decryption failed for certificate file " . $CertFileName) false; + } + + :foreach CertInChain in=[ /certificate/find where name~("^" . $CertFileName . "_[0-9]+\$") common-name!=($CertVal->"common-name") ] do={ + $CertificateNameByCN [ /certificate/get $CertInChain common-name ]; + } + } on-error={ + $LogPrintExit2 debug $0 ("Could not download certificate file " . $CertFileName) false; + } + } + + :local CertNew [ /certificate/find where common-name=($CertVal->"common-name") fingerprint!=[ :tostr ($CertVal->"fingerprint") ] expires-after>$CertRenewTime ]; + :local CertNewVal [ /certificate/get $CertNew ]; + + :if ([ $CertificateAvailable ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") ] = false) do={ + $LogPrintExit2 warning $0 ("The certificate chain is not available!") false; + } + + :if ($Cert != $CertNew) do={ + $LogPrintExit2 debug $0 ("Certificate '" . $CertVal->"name" . "' was not updated, but replaced.") false; + + :if (($CertVal->"private-key") = true && ($CertVal->"private-key") != ($CertNewVal->"private-key")) do={ + /certificate/remove $CertNew; + $LogPrintExit2 warning $0 ("Old certificate '" . ($CertVal->"name") . "' has a private key, new certificate does not. Aborting renew.") true; + } + + /ip/service/set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ]; + + /ip/ipsec/identity/set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ]; + /ip/ipsec/identity/set remote-certificate=($CertNewVal->"name") [ find where remote-certificate=($CertVal->"name") ]; + + /ip/hotspot/profile/set ssl-certificate=($CertNewVal->"name") [ find where ssl-certificate=($CertVal->"name") ]; + + /certificate/remove $Cert; + /certificate/set $CertNew name=($CertVal->"name"); + } + + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "lock-with-ink-pen" ] . "Certificate renewed"); \ + message=("A certificate on " . $Identity . " has been renewed.\n\n" . \ + "Name: " . ($CertVal->"name") . "\n" . \ + "CommonName: " . ($CertNewVal->"common-name") . "\n" . \ + "Private key: " . [ $IfThenElse (($CertNewVal->"private-key") = true) "available" "missing" ] . "\n" . \ + "Fingerprint: " . ($CertNewVal->"fingerprint") . "\n" . \ + "Issuer: " . ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") . "\n" . \ + "Validity: " . ($CertNewVal->"invalid-before") . " to " . ($CertNewVal->"invalid-after") . "\n" . \ + "Expires in: " . [ $FormatExpire ($CertNewVal->"expires-after") ]); silent=true }); + $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " has been renewed.") false; + } on-error={ + $LogPrintExit2 debug $0 ("Could not renew certificate " . ($CertVal->"name") . ".") false; + } +} + +:foreach Cert in=[ /certificate/find where !revoked !scep-url !(expires-after=[]) \ + expires-after<$CertWarnTime !(fingerprint=[]) ] do={ + :local CertVal [ /certificate/get $Cert ]; + + :if ([ :len [ /certificate/scep-server/find where ca-cert=($CertVal->"ca") ] ] > 0) do={ + $LogPrintExit2 debug $0 ("Certificate \"" . ($CertVal->"name") . "\" is handled by SCEP, skipping.") false; + } else={ + :local State [ $IfThenElse (($CertVal->"expired") = true) "expired" "is about to expire" ]; + + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "warning-sign" ] . "Certificate warning!"); \ + message=("A certificate on " . $Identity . " " . $State . ".\n\n" . \ + "Name: " . ($CertVal->"name") . "\n" . \ + "CommonName: " . ($CertVal->"common-name") . "\n" . \ + "Private key: " . [ $IfThenElse (($CertVal->"private-key") = true) "available" "missing" ] . "\n" . \ + "Fingerprint: " . ($CertVal->"fingerprint") . "\n" . \ + "Issuer: " . ($CertVal->"ca") . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\n" . \ + "Validity: " . ($CertVal->"invalid-before") . " to " . ($CertVal->"invalid-after") . "\n" . \ + "Expires in: " . [ $IfThenElse (($CertVal->"expired") = true) "expired" [ $FormatExpire ($CertVal->"expires-after") ] ]) }); + $LogPrintExit2 info $0 ("The certificate " . ($CertVal->"name") . " " . $State . \ + ", it is invalid after " . ($CertVal->"invalid-after") . ".") false; + } +} diff --git a/check-health b/check-health index e208bac..2da00ca 100644 --- a/check-health +++ b/check-health @@ -1,167 +1,3 @@ #!rsc by RouterOS -# RouterOS script: check-health -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# check for RouterOS health state -# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-health.md - -:local 0 "check-health"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global CheckHealthCPUUtilization; -:global CheckHealthCPUUtilizationNotified; -:global CheckHealthFreeRAMNotified; -:global CheckHealthLast; -:global CheckHealthTemperature; -:global CheckHealthTemperatureDeviation; -:global CheckHealthTemperatureNotified; -:global CheckHealthVoltageLow; -:global CheckHealthVoltagePercent; -:global Identity; - -:global IfThenElse; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -:local TempToNum do={ - :global CharacterReplace; - :local T [ :toarray [ $CharacterReplace $1 "." "," ] ]; - :return ($T->0 * 10 + $T->1); -} - -$ScriptLock $0; - -:local Resource [ /system/resource/get ]; - -:set CheckHealthCPUUtilization (($CheckHealthCPUUtilization * 4 + ($Resource->"cpu-load") * 10) / 5); -:if ($CheckHealthCPUUtilization > 750 && $CheckHealthCPUUtilizationNotified != true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "abacus,chart-increasing" ] . "Health warning: CPU utilization"); \ - message=("The average CPU utilization on " . $Identity . " is at " . ($CheckHealthCPUUtilization / 10) . "%!") }); - :set CheckHealthCPUUtilizationNotified true; -} -:if ($CheckHealthCPUUtilization < 650 && $CheckHealthCPUUtilizationNotified = true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "abacus,chart-decreasing" ] . "Health recovery: CPU utilization"); \ - message=("The average CPU utilization on " . $Identity . " decreased to " . ($CheckHealthCPUUtilization / 10) . "%.") }); - :set CheckHealthCPUUtilizationNotified false; -} - -:local CheckHealthFreeRAM ($Resource->"free-memory" * 100 / $Resource->"total-memory"); -:if ($CheckHealthFreeRAM < 20 && $CheckHealthFreeRAMNotified != true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "card-file-box,chart-decreasing" ] . "Health warning: free RAM"); \ - message=("The available free RAM on " . $Identity . " is at " . $CheckHealthFreeRAM . "% (" . \ - ($Resource->"free-memory" / 1024 / 1024) . "MiB)!") }); - :set CheckHealthFreeRAMNotified true; -} -:if ($CheckHealthFreeRAM > 30 && $CheckHealthFreeRAMNotified = true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "card-file-box,chart-increasing" ] . "Health recovery: free RAM"); \ - message=("The available free RAM on " . $Identity . " increased to " . $CheckHealthFreeRAM . "% (" . \ - ($Resource->"free-memory" / 1024 / 1024) . "MiB).") }); - :set CheckHealthFreeRAMNotified false; -} - -:if ([ :len [ /system/health/find ] ] = 0) do={ - $LogPrintExit2 debug $0 ("Your device does not provide any health values.") true; -} - -:if ([ :typeof $CheckHealthLast ] != "array") do={ - :set CheckHealthLast ({}); -} -:if ([ :typeof $CheckHealthTemperatureNotified ] != "array") do={ - :set CheckHealthTemperatureNotified ({}); -} - - -:foreach Voltage in=[ /system/health/find where type="V" ] do={ - :local Name [ /system/health/get $Voltage name ]; - :local Value [ /system/health/get $Voltage value ]; - - :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ - :local NumCurr [ $TempToNum $Value ]; - :local NumLast [ $TempToNum ($CheckHealthLast->$Name) ]; - - :if ($NumLast * (100 + $CheckHealthVoltagePercent) < $NumCurr * 100 || \ - $NumLast * 100 > $NumCurr * (100 + $CheckHealthVoltagePercent)) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification ("high-voltage-sign,chart-" . [ $IfThenElse ($NumLast < \ - $NumCurr) "in" "de" ] . "creasing") ] . "Health warning: " . $Name); \ - message=("The " . $Name . " on " . $Identity . " jumped more than " . $CheckHealthVoltagePercent . "%.\n\n" . \ - "old value: " . ($CheckHealthLast->$Name) . " V\n" . \ - "new value: " . $Value . " V") }); - } else={ - :if ($NumCurr <= $CheckHealthVoltageLow && $NumLast > $CheckHealthVoltageLow) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "high-voltage-sign,chart-decreasing" ] . "Health warning: Low " . $Name); \ - message=("The " . $Name . " on " . $Identity . " dropped to " . $Value . " V below hard limit.") }); - } - :if ($NumCurr > $CheckHealthVoltageLow && $NumLast <= $CheckHealthVoltageLow) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "high-voltage-sign,chart-increasing" ] . "Health recovery: Low " . $Name); \ - message=("The " . $Name . " on " . $Identity . " recovered to " . $Value . " V above hard limit.") }); - } - } - } - :set ($CheckHealthLast->$Name) $Value; -} - -:foreach PSU in=[ /system/health/find where name~"^psu.*-state\$" ] do={ - :local Name [ /system/health/get $PSU name ]; - :local Value [ /system/health/get $PSU value ]; - - :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ - :if ($CheckHealthLast->$Name = "ok" && \ - $Value != "ok") do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "cross-mark" ] . "Health warning: " . $Name); \ - message=("The power supply unit '" . $Name . "' on " . $Identity . " failed!") }); - } - :if ($CheckHealthLast->$Name != "ok" && \ - $Value = "ok") do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ - message=("The power supply unit '" . $Name . "' on " . $Identity . " recovered!") }); - } - } - :set ($CheckHealthLast->$Name) $Value; -} - -:foreach Temperature in=[ /system/health/find where type="C" ] do={ - :local Name [ /system/health/get $Temperature name ]; - :local Value [ /system/health/get $Temperature value ]; - - :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ - :if ([ :typeof ($CheckHealthTemperature->$Name) ] != "num" ) do={ - $LogPrintExit2 info $0 ("No threshold given for " . $Name . ", assuming 50C.") false; - :set ($CheckHealthTemperature->$Name) 50; - } - :local Validate [ /system/health/get [ find where name=$Name ] value ]; - :while ($Value != $Validate) do={ - :set Value $Validate; - :set Validate [ /system/health/get [ find where name=$Name ] value ]; - } - :if ($Value > $CheckHealthTemperature->$Name && \ - $CheckHealthTemperatureNotified->$Name != true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "fire" ] . "Health warning: " . $Name); \ - message=("The " . $Name . " on " . $Identity . " is above threshold: " . \ - $Value . "\C2\B0" . "C") }); - :set ($CheckHealthTemperatureNotified->$Name) true; - } - :if ($Value <= ($CheckHealthTemperature->$Name - $CheckHealthTemperatureDeviation) && \ - $CheckHealthTemperatureNotified->$Name = true) do={ - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ - message=("The " . $Name . " on " . $Identity . " dropped below threshold: " . \ - $Value . "\C2\B0" . "C") }); - :set ($CheckHealthTemperatureNotified->$Name) false; - } - } - :set ($CheckHealthLast->$Name) $Value; -} +# dummy for migration diff --git a/check-health.rsc b/check-health.rsc new file mode 100644 index 0000000..e208bac --- /dev/null +++ b/check-health.rsc @@ -0,0 +1,167 @@ +#!rsc by RouterOS +# RouterOS script: check-health +# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# check for RouterOS health state +# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-health.md + +:local 0 "check-health"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global CheckHealthCPUUtilization; +:global CheckHealthCPUUtilizationNotified; +:global CheckHealthFreeRAMNotified; +:global CheckHealthLast; +:global CheckHealthTemperature; +:global CheckHealthTemperatureDeviation; +:global CheckHealthTemperatureNotified; +:global CheckHealthVoltageLow; +:global CheckHealthVoltagePercent; +:global Identity; + +:global IfThenElse; +:global LogPrintExit2; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; + +:local TempToNum do={ + :global CharacterReplace; + :local T [ :toarray [ $CharacterReplace $1 "." "," ] ]; + :return ($T->0 * 10 + $T->1); +} + +$ScriptLock $0; + +:local Resource [ /system/resource/get ]; + +:set CheckHealthCPUUtilization (($CheckHealthCPUUtilization * 4 + ($Resource->"cpu-load") * 10) / 5); +:if ($CheckHealthCPUUtilization > 750 && $CheckHealthCPUUtilizationNotified != true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "abacus,chart-increasing" ] . "Health warning: CPU utilization"); \ + message=("The average CPU utilization on " . $Identity . " is at " . ($CheckHealthCPUUtilization / 10) . "%!") }); + :set CheckHealthCPUUtilizationNotified true; +} +:if ($CheckHealthCPUUtilization < 650 && $CheckHealthCPUUtilizationNotified = true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "abacus,chart-decreasing" ] . "Health recovery: CPU utilization"); \ + message=("The average CPU utilization on " . $Identity . " decreased to " . ($CheckHealthCPUUtilization / 10) . "%.") }); + :set CheckHealthCPUUtilizationNotified false; +} + +:local CheckHealthFreeRAM ($Resource->"free-memory" * 100 / $Resource->"total-memory"); +:if ($CheckHealthFreeRAM < 20 && $CheckHealthFreeRAMNotified != true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "card-file-box,chart-decreasing" ] . "Health warning: free RAM"); \ + message=("The available free RAM on " . $Identity . " is at " . $CheckHealthFreeRAM . "% (" . \ + ($Resource->"free-memory" / 1024 / 1024) . "MiB)!") }); + :set CheckHealthFreeRAMNotified true; +} +:if ($CheckHealthFreeRAM > 30 && $CheckHealthFreeRAMNotified = true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "card-file-box,chart-increasing" ] . "Health recovery: free RAM"); \ + message=("The available free RAM on " . $Identity . " increased to " . $CheckHealthFreeRAM . "% (" . \ + ($Resource->"free-memory" / 1024 / 1024) . "MiB).") }); + :set CheckHealthFreeRAMNotified false; +} + +:if ([ :len [ /system/health/find ] ] = 0) do={ + $LogPrintExit2 debug $0 ("Your device does not provide any health values.") true; +} + +:if ([ :typeof $CheckHealthLast ] != "array") do={ + :set CheckHealthLast ({}); +} +:if ([ :typeof $CheckHealthTemperatureNotified ] != "array") do={ + :set CheckHealthTemperatureNotified ({}); +} + + +:foreach Voltage in=[ /system/health/find where type="V" ] do={ + :local Name [ /system/health/get $Voltage name ]; + :local Value [ /system/health/get $Voltage value ]; + + :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ + :local NumCurr [ $TempToNum $Value ]; + :local NumLast [ $TempToNum ($CheckHealthLast->$Name) ]; + + :if ($NumLast * (100 + $CheckHealthVoltagePercent) < $NumCurr * 100 || \ + $NumLast * 100 > $NumCurr * (100 + $CheckHealthVoltagePercent)) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification ("high-voltage-sign,chart-" . [ $IfThenElse ($NumLast < \ + $NumCurr) "in" "de" ] . "creasing") ] . "Health warning: " . $Name); \ + message=("The " . $Name . " on " . $Identity . " jumped more than " . $CheckHealthVoltagePercent . "%.\n\n" . \ + "old value: " . ($CheckHealthLast->$Name) . " V\n" . \ + "new value: " . $Value . " V") }); + } else={ + :if ($NumCurr <= $CheckHealthVoltageLow && $NumLast > $CheckHealthVoltageLow) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "high-voltage-sign,chart-decreasing" ] . "Health warning: Low " . $Name); \ + message=("The " . $Name . " on " . $Identity . " dropped to " . $Value . " V below hard limit.") }); + } + :if ($NumCurr > $CheckHealthVoltageLow && $NumLast <= $CheckHealthVoltageLow) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "high-voltage-sign,chart-increasing" ] . "Health recovery: Low " . $Name); \ + message=("The " . $Name . " on " . $Identity . " recovered to " . $Value . " V above hard limit.") }); + } + } + } + :set ($CheckHealthLast->$Name) $Value; +} + +:foreach PSU in=[ /system/health/find where name~"^psu.*-state\$" ] do={ + :local Name [ /system/health/get $PSU name ]; + :local Value [ /system/health/get $PSU value ]; + + :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ + :if ($CheckHealthLast->$Name = "ok" && \ + $Value != "ok") do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "cross-mark" ] . "Health warning: " . $Name); \ + message=("The power supply unit '" . $Name . "' on " . $Identity . " failed!") }); + } + :if ($CheckHealthLast->$Name != "ok" && \ + $Value = "ok") do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ + message=("The power supply unit '" . $Name . "' on " . $Identity . " recovered!") }); + } + } + :set ($CheckHealthLast->$Name) $Value; +} + +:foreach Temperature in=[ /system/health/find where type="C" ] do={ + :local Name [ /system/health/get $Temperature name ]; + :local Value [ /system/health/get $Temperature value ]; + + :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ + :if ([ :typeof ($CheckHealthTemperature->$Name) ] != "num" ) do={ + $LogPrintExit2 info $0 ("No threshold given for " . $Name . ", assuming 50C.") false; + :set ($CheckHealthTemperature->$Name) 50; + } + :local Validate [ /system/health/get [ find where name=$Name ] value ]; + :while ($Value != $Validate) do={ + :set Value $Validate; + :set Validate [ /system/health/get [ find where name=$Name ] value ]; + } + :if ($Value > $CheckHealthTemperature->$Name && \ + $CheckHealthTemperatureNotified->$Name != true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "fire" ] . "Health warning: " . $Name); \ + message=("The " . $Name . " on " . $Identity . " is above threshold: " . \ + $Value . "\C2\B0" . "C") }); + :set ($CheckHealthTemperatureNotified->$Name) true; + } + :if ($Value <= ($CheckHealthTemperature->$Name - $CheckHealthTemperatureDeviation) && \ + $CheckHealthTemperatureNotified->$Name = true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ + message=("The " . $Name . " on " . $Identity . " dropped below threshold: " . \ + $Value . "\C2\B0" . "C") }); + :set ($CheckHealthTemperatureNotified->$Name) false; + } + } + :set ($CheckHealthLast->$Name) $Value; +} diff --git a/check-lte-firmware-upgrade b/check-lte-firmware-upgrade index 02e864b..2da00ca 100644 --- a/check-lte-firmware-upgrade +++ b/check-lte-firmware-upgrade @@ -1,82 +1,3 @@ #!rsc by RouterOS -# RouterOS script: check-lte-firmware-upgrade -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# check for LTE firmware upgrade, send notification -# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-lte-firmware-upgrade.md - -:local 0 "check-lte-firmware-upgrade"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global SentLteFirmwareUpgradeNotification; - -:if ([ :typeof $SentLteFirmwareUpgradeNotification ] != "array") do={ - :global SentLteFirmwareUpgradeNotification ({}); -} - -:local CheckInterface do={ - :local Interface $1; - - :global Identity; - :global SentLteFirmwareUpgradeNotification; - - :global CharacterReplace; - :global LogPrintExit2; - :global ScriptFromTerminal; - :global SendNotification2; - :global SymbolForNotification; - - :local IntName [ /interface/lte/get $Interface name ]; - :local Firmware; - :local Info; - :do { - :set Firmware [ /interface/lte/firmware-upgrade $Interface once as-value ]; - :set Info [ /interface/lte/monitor $Interface once as-value ]; - } on-error={ - $LogPrintExit2 debug $0 ("Could not get latest LTE firmware version for interface " . \ - $IntName . ".") false; - :return false; - } - - :if (($Firmware->"installed") = ($Firmware->"latest")) do={ - :if ([ $ScriptFromTerminal $0 ] = true) do={ - $LogPrintExit2 info $0 ("No firmware upgrade available for LTE interface " . $IntName . ".") false; - } - :return true; - } - - :if ([ $ScriptFromTerminal $0 ] = true && \ - [ :len [ /system/script/find where name="unattended-lte-firmware-upgrade" ] ] > 0) do={ - :put ("Do you want to start unattended lte firmware upgrade for interface " . $IntName . "? [y/N]"); - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - /system/script/run unattended-lte-firmware-upgrade; - $LogPrintExit2 info $0 ("Scheduled lte firmware upgrade for interface " . $IntName . "...") false; - :return true; - } else={ - :put "Canceled..."; - } - } - - :if (($SentLteFirmwareUpgradeNotification->$IntName) = ($Firmware->"latest")) do={ - $LogPrintExit2 debug $0 ("Already sent the LTE firmware upgrade notification for version " . \ - ($Firmware->"latest") . ".") false; - :return false; - } - - $LogPrintExit2 info $0 ("A new firmware version " . ($Firmware->"latest") . " is available for " . \ - "LTE interface " . $IntName . ".") false; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "sparkles" ] . "LTE firmware upgrade"); \ - message=("A new firmware version " . ($Firmware->"latest") . " is available for " . \ - "LTE interface " . $IntName . " on " . $Identity . ".\n\n" . \ - "Interface: " . [ $CharacterReplace ($Info->"manufacturer" . " " . $Info->"model") ("\"") "" ] . "\n" . \ - "Installed: " . ($Firmware->"installed") . "\n" . \ - "Available: " . ($Firmware->"latest")); silent=true }); - :set ($SentLteFirmwareUpgradeNotification->$IntName) ($Firmware->"latest"); -} - -:foreach Interface in=[ /interface/lte/find ] do={ - $CheckInterface $Interface; -} +# dummy for migration diff --git a/check-lte-firmware-upgrade.rsc b/check-lte-firmware-upgrade.rsc new file mode 100644 index 0000000..02e864b --- /dev/null +++ b/check-lte-firmware-upgrade.rsc @@ -0,0 +1,82 @@ +#!rsc by RouterOS +# RouterOS script: check-lte-firmware-upgrade +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# check for LTE firmware upgrade, send notification +# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-lte-firmware-upgrade.md + +:local 0 "check-lte-firmware-upgrade"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global SentLteFirmwareUpgradeNotification; + +:if ([ :typeof $SentLteFirmwareUpgradeNotification ] != "array") do={ + :global SentLteFirmwareUpgradeNotification ({}); +} + +:local CheckInterface do={ + :local Interface $1; + + :global Identity; + :global SentLteFirmwareUpgradeNotification; + + :global CharacterReplace; + :global LogPrintExit2; + :global ScriptFromTerminal; + :global SendNotification2; + :global SymbolForNotification; + + :local IntName [ /interface/lte/get $Interface name ]; + :local Firmware; + :local Info; + :do { + :set Firmware [ /interface/lte/firmware-upgrade $Interface once as-value ]; + :set Info [ /interface/lte/monitor $Interface once as-value ]; + } on-error={ + $LogPrintExit2 debug $0 ("Could not get latest LTE firmware version for interface " . \ + $IntName . ".") false; + :return false; + } + + :if (($Firmware->"installed") = ($Firmware->"latest")) do={ + :if ([ $ScriptFromTerminal $0 ] = true) do={ + $LogPrintExit2 info $0 ("No firmware upgrade available for LTE interface " . $IntName . ".") false; + } + :return true; + } + + :if ([ $ScriptFromTerminal $0 ] = true && \ + [ :len [ /system/script/find where name="unattended-lte-firmware-upgrade" ] ] > 0) do={ + :put ("Do you want to start unattended lte firmware upgrade for interface " . $IntName . "? [y/N]"); + :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ + /system/script/run unattended-lte-firmware-upgrade; + $LogPrintExit2 info $0 ("Scheduled lte firmware upgrade for interface " . $IntName . "...") false; + :return true; + } else={ + :put "Canceled..."; + } + } + + :if (($SentLteFirmwareUpgradeNotification->$IntName) = ($Firmware->"latest")) do={ + $LogPrintExit2 debug $0 ("Already sent the LTE firmware upgrade notification for version " . \ + ($Firmware->"latest") . ".") false; + :return false; + } + + $LogPrintExit2 info $0 ("A new firmware version " . ($Firmware->"latest") . " is available for " . \ + "LTE interface " . $IntName . ".") false; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "sparkles" ] . "LTE firmware upgrade"); \ + message=("A new firmware version " . ($Firmware->"latest") . " is available for " . \ + "LTE interface " . $IntName . " on " . $Identity . ".\n\n" . \ + "Interface: " . [ $CharacterReplace ($Info->"manufacturer" . " " . $Info->"model") ("\"") "" ] . "\n" . \ + "Installed: " . ($Firmware->"installed") . "\n" . \ + "Available: " . ($Firmware->"latest")); silent=true }); + :set ($SentLteFirmwareUpgradeNotification->$IntName) ($Firmware->"latest"); +} + +:foreach Interface in=[ /interface/lte/find ] do={ + $CheckInterface $Interface; +} diff --git a/check-routeros-update b/check-routeros-update index d1c82c5..2da00ca 100644 --- a/check-routeros-update +++ b/check-routeros-update @@ -1,147 +1,3 @@ #!rsc by RouterOS -# RouterOS script: check-routeros-update -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# check for RouterOS update, send notification and/or install -# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-routeros-update.md - -:local 0 "check-routeros-update"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Identity; -:global SafeUpdateAll; -:global SafeUpdateNeighbor; -:global SafeUpdatePatch; -:global SafeUpdateUrl; -:global SentRouterosUpdateNotification; - -:global DeviceInfo; -:global LogPrintExit2; -:global ScriptFromTerminal; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global VersionToNum; -:global WaitFullyConnected; - -:local DoUpdate do={ - :if ([ :len [ /system/script/find where name="packages-update" ] ] > 0) do={ - /system/script/run packages-update; - } else={ - /system/package/update/install without-paging; - } - :error "Waiting for system to reboot."; -} - -$ScriptLock $0; - -$WaitFullyConnected; - -:if ([ :len [ /system/scheduler/find where name="reboot-for-update" ] ] > 0) do={ - :error "A reboot for update is already scheduled."; -} - -$LogPrintExit2 debug $0 ("Checking for updates...") false; -/system/package/update/check-for-updates without-paging as-value; -:local Update [ /system/package/update/get ]; - -:if ([ $ScriptFromTerminal $0 ] = true && ($Update->"installed-version") = ($Update->"latest-version")) do={ - $LogPrintExit2 info $0 ("System is already up to date.") true; -} - -:local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; -:local NumLatest [ $VersionToNum ($Update->"latest-version") ]; -:local Link ("https://mikrotik.com/download/changelogs/" . $Update->"channel" . "-release-tree"); - -:if ($NumLatest < 117505792) do={ - $LogPrintExit2 info $0 ("The version '" . ($Update->"latest-version") . "' is not a valid version.") true; -} - -:if ($NumInstalled < $NumLatest) do={ - :if ($SafeUpdateAll ~ "^YES,? ?PLEASE!?\$") do={ - $LogPrintExit2 info $0 ("Installing ALL versions automatically, including " . \ - $Update->"latest-version" . "...") false; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ - message=("Installing ALL versions automatically, including " . $Update->"latest-version" . \ - "... Updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; - } - - :if ($SafeUpdatePatch = true && ($NumInstalled & 0xffff0000) = ($NumLatest & 0xffff0000)) do={ - $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is a patch release, updating...") false; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ - message=("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \ - ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; - } - - :if ($SafeUpdateNeighbor = true && [ :len [ /ip/neighbor/find where \ - version=($Update->"latest-version" . " (" . $Update->"channel" . ")") ] ] > 0) do={ - $LogPrintExit2 info $0 ("Seen a neighbor running version " . $Update->"latest-version" . ", updating...") false; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ - message=("Seen a neighbor running version " . $Update->"latest-version" . " from " . $Update->"channel" . \ - ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; - } - - :if ([ :len $SafeUpdateUrl ] > 0) do={ - :local Result; - :do { - :set Result [ /tool/fetch check-certificate=yes-without-crl \ - ($SafeUpdateUrl . $Update->"channel" . "?installed=" . $Update->"installed-version" . \ - "&latest=" . $Update->"latest-version") output=user as-value ]; - } on-error={ - $LogPrintExit2 warning $0 ("Failed receiving safe version for " . $Update->"channel" . ".") false; - } - :if ($Result->"status" = "finished" && $Result->"data" = $Update->"latest-version") do={ - $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is considered safe, updating...") false; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ - message=("Version " . $Update->"latest-version" . " is considered safe for " . $Update->"channel" . \ - ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate; - } - } - - :if ([ $ScriptFromTerminal $0 ] = true) do={ - :put ("Do you want to install RouterOS version " . $Update->"latest-version" . "? [y/N]"); - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - $DoUpdate; - } else={ - :put "Canceled..."; - } - } - - :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={ - $LogPrintExit2 info $0 ("Already sent the RouterOS update notification for version " . \ - $Update->"latest-version" . ".") true; - } - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ - message=("A new RouterOS version " . ($Update->"latest-version") . \ - " is available for " . $Identity . ".\n\n" . \ - [ $DeviceInfo ]); link=$Link; silent=true }); - :set SentRouterosUpdateNotification ($Update->"latest-version"); -} - -:if ($NumInstalled > $NumLatest) do={ - :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={ - $LogPrintExit2 info $0 ("Already sent the RouterOS downgrade notification for version " . \ - $Update->"latest-version" . ".") true; - } - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "warning-sign" ] . "RouterOS version"); \ - message=("A different RouterOS version " . ($Update->"latest-version") . \ - " is available for " . $Identity . ", but it is a downgrade.\n\n" . \ - [ $DeviceInfo ]); link=$Link; silent=true }); - $LogPrintExit2 info $0 ("A different RouterOS version " . ($Update->"latest-version") . \ - " is available for downgrade.") false; - :set SentRouterosUpdateNotification ($Update->"latest-version"); -} +# dummy for migration diff --git a/check-routeros-update.rsc b/check-routeros-update.rsc new file mode 100644 index 0000000..d1c82c5 --- /dev/null +++ b/check-routeros-update.rsc @@ -0,0 +1,147 @@ +#!rsc by RouterOS +# RouterOS script: check-routeros-update +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# check for RouterOS update, send notification and/or install +# https://git.eworm.de/cgit/routeros-scripts/about/doc/check-routeros-update.md + +:local 0 "check-routeros-update"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Identity; +:global SafeUpdateAll; +:global SafeUpdateNeighbor; +:global SafeUpdatePatch; +:global SafeUpdateUrl; +:global SentRouterosUpdateNotification; + +:global DeviceInfo; +:global LogPrintExit2; +:global ScriptFromTerminal; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; +:global VersionToNum; +:global WaitFullyConnected; + +:local DoUpdate do={ + :if ([ :len [ /system/script/find where name="packages-update" ] ] > 0) do={ + /system/script/run packages-update; + } else={ + /system/package/update/install without-paging; + } + :error "Waiting for system to reboot."; +} + +$ScriptLock $0; + +$WaitFullyConnected; + +:if ([ :len [ /system/scheduler/find where name="reboot-for-update" ] ] > 0) do={ + :error "A reboot for update is already scheduled."; +} + +$LogPrintExit2 debug $0 ("Checking for updates...") false; +/system/package/update/check-for-updates without-paging as-value; +:local Update [ /system/package/update/get ]; + +:if ([ $ScriptFromTerminal $0 ] = true && ($Update->"installed-version") = ($Update->"latest-version")) do={ + $LogPrintExit2 info $0 ("System is already up to date.") true; +} + +:local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; +:local NumLatest [ $VersionToNum ($Update->"latest-version") ]; +:local Link ("https://mikrotik.com/download/changelogs/" . $Update->"channel" . "-release-tree"); + +:if ($NumLatest < 117505792) do={ + $LogPrintExit2 info $0 ("The version '" . ($Update->"latest-version") . "' is not a valid version.") true; +} + +:if ($NumInstalled < $NumLatest) do={ + :if ($SafeUpdateAll ~ "^YES,? ?PLEASE!?\$") do={ + $LogPrintExit2 info $0 ("Installing ALL versions automatically, including " . \ + $Update->"latest-version" . "...") false; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ + message=("Installing ALL versions automatically, including " . $Update->"latest-version" . \ + "... Updating on " . $Identity . "..."); link=$Link; silent=true }); + $DoUpdate; + } + + :if ($SafeUpdatePatch = true && ($NumInstalled & 0xffff0000) = ($NumLatest & 0xffff0000)) do={ + $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is a patch release, updating...") false; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ + message=("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \ + ", updating on " . $Identity . "..."); link=$Link; silent=true }); + $DoUpdate; + } + + :if ($SafeUpdateNeighbor = true && [ :len [ /ip/neighbor/find where \ + version=($Update->"latest-version" . " (" . $Update->"channel" . ")") ] ] > 0) do={ + $LogPrintExit2 info $0 ("Seen a neighbor running version " . $Update->"latest-version" . ", updating...") false; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ + message=("Seen a neighbor running version " . $Update->"latest-version" . " from " . $Update->"channel" . \ + ", updating on " . $Identity . "..."); link=$Link; silent=true }); + $DoUpdate; + } + + :if ([ :len $SafeUpdateUrl ] > 0) do={ + :local Result; + :do { + :set Result [ /tool/fetch check-certificate=yes-without-crl \ + ($SafeUpdateUrl . $Update->"channel" . "?installed=" . $Update->"installed-version" . \ + "&latest=" . $Update->"latest-version") output=user as-value ]; + } on-error={ + $LogPrintExit2 warning $0 ("Failed receiving safe version for " . $Update->"channel" . ".") false; + } + :if ($Result->"status" = "finished" && $Result->"data" = $Update->"latest-version") do={ + $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is considered safe, updating...") false; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ + message=("Version " . $Update->"latest-version" . " is considered safe for " . $Update->"channel" . \ + ", updating on " . $Identity . "..."); link=$Link; silent=true }); + $DoUpdate; + } + } + + :if ([ $ScriptFromTerminal $0 ] = true) do={ + :put ("Do you want to install RouterOS version " . $Update->"latest-version" . "? [y/N]"); + :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ + $DoUpdate; + } else={ + :put "Canceled..."; + } + } + + :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={ + $LogPrintExit2 info $0 ("Already sent the RouterOS update notification for version " . \ + $Update->"latest-version" . ".") true; + } + + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update"); \ + message=("A new RouterOS version " . ($Update->"latest-version") . \ + " is available for " . $Identity . ".\n\n" . \ + [ $DeviceInfo ]); link=$Link; silent=true }); + :set SentRouterosUpdateNotification ($Update->"latest-version"); +} + +:if ($NumInstalled > $NumLatest) do={ + :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={ + $LogPrintExit2 info $0 ("Already sent the RouterOS downgrade notification for version " . \ + $Update->"latest-version" . ".") true; + } + + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "warning-sign" ] . "RouterOS version"); \ + message=("A different RouterOS version " . ($Update->"latest-version") . \ + " is available for " . $Identity . ", but it is a downgrade.\n\n" . \ + [ $DeviceInfo ]); link=$Link; silent=true }); + $LogPrintExit2 info $0 ("A different RouterOS version " . ($Update->"latest-version") . \ + " is available for downgrade.") false; + :set SentRouterosUpdateNotification ($Update->"latest-version"); +} diff --git a/collect-wireless-mac.capsman b/collect-wireless-mac.capsman index e814fa9..2da00ca 100644 --- a/collect-wireless-mac.capsman +++ b/collect-wireless-mac.capsman @@ -1,85 +1,3 @@ #!rsc by RouterOS -# RouterOS script: collect-wireless-mac.capsman -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# collect wireless mac adresses in access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md -# -# provides: lease-script, order=40 -# -# !! Do not edit this file, it is generated from template! - -:local 0 "collect-wireless-mac.capsman"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Identity; - -:global EitherOr; -:global GetMacVendor; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -$ScriptLock $0 false 10; - -:if ([ :len [ /caps-man/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - /caps-man/access-list/add comment="--- collected above ---" disabled=yes; - $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false; -} -:local PlaceBefore ([ /caps-man/access-list/find where comment="--- collected above ---" disabled ]->0); - -:foreach Reg in=[ /caps-man/registration-table/find ] do={ - :local RegVal; - :do { - :set RegVal [ /caps-man/registration-table/get $Reg ]; - } on-error={ - $LogPrintExit2 debug $0 ("Device already gone... Ignoring.") false; - } - - :if ([ :len ($RegVal->"mac-address") ] > 0) do={ - :local AccessList ([ /caps-man/access-list/find where mac-address=($RegVal->"mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - $LogPrintExit2 debug $0 ("MAC address " . $RegVal->"mac-address" . " already known: " . \ - [ /caps-man/access-list/get $AccessList comment ]) false; - } - - :if ([ :len $AccessList ] = 0) do={ - :local Address "no dhcp lease"; - :local DnsName "no dhcp lease"; - :local HostName "no dhcp lease"; - :local Lease ([ /ip/dhcp-server/lease/find where mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); - :if ([ :len $Lease ] > 0) do={ - :set Address [ /ip/dhcp-server/lease/get $Lease address ]; - :set HostName [ $EitherOr [ /ip/dhcp-server/lease/get $Lease host-name ] "no hostname" ]; - :set DnsName "no dns name"; - :local DnsRec ([ /ip/dns/static/find where address=$Address ]->0); - :if ([ :len $DnsRec ] > 0) do={ - :set DnsName [ /ip/dns/static/get $DnsRec name ]; - } - } - :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]); - :local Vendor [ $GetMacVendor ($RegVal->"mac-address") ]; - :local Message ("MAC address " . $RegVal->"mac-address" . " (" . $Vendor . ", " . $HostName . ") " . \ - "first seen on " . $DateTime . " connected to SSID " . $RegVal->"ssid" . ", interface " . $RegVal->"interface"); - $LogPrintExit2 info $0 $Message false; - /caps-man/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "mobile-phone" ] . $RegVal->"mac-address" . " connected to " . $RegVal->"ssid"); \ - message=("A device with unknown MAC address connected to " . $RegVal->"ssid" . " on " . $Identity . ".\n\n" . \ - "Controller: " . $Identity . "\n" . \ - "Interface: " . $RegVal->"interface" . "\n" . \ - "SSID: " . $RegVal->"ssid" . "\n" . \ - "MAC: " . $RegVal->"mac-address" . "\n" . \ - "Vendor: " . $Vendor . "\n" . \ - "Hostname: " . $HostName . "\n" . \ - "Address: " . $Address . "\n" . \ - "DNS name: " . $DnsName . "\n" . \ - "Date: " . $DateTime) }); - } - } else={ - $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; - } -} +# dummy for migration diff --git a/collect-wireless-mac.capsman.rsc b/collect-wireless-mac.capsman.rsc new file mode 100644 index 0000000..e814fa9 --- /dev/null +++ b/collect-wireless-mac.capsman.rsc @@ -0,0 +1,85 @@ +#!rsc by RouterOS +# RouterOS script: collect-wireless-mac.capsman +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# collect wireless mac adresses in access list +# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md +# +# provides: lease-script, order=40 +# +# !! Do not edit this file, it is generated from template! + +:local 0 "collect-wireless-mac.capsman"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Identity; + +:global EitherOr; +:global GetMacVendor; +:global LogPrintExit2; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; + +$ScriptLock $0 false 10; + +:if ([ :len [ /caps-man/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ + /caps-man/access-list/add comment="--- collected above ---" disabled=yes; + $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false; +} +:local PlaceBefore ([ /caps-man/access-list/find where comment="--- collected above ---" disabled ]->0); + +:foreach Reg in=[ /caps-man/registration-table/find ] do={ + :local RegVal; + :do { + :set RegVal [ /caps-man/registration-table/get $Reg ]; + } on-error={ + $LogPrintExit2 debug $0 ("Device already gone... Ignoring.") false; + } + + :if ([ :len ($RegVal->"mac-address") ] > 0) do={ + :local AccessList ([ /caps-man/access-list/find where mac-address=($RegVal->"mac-address") ]->0); + :if ([ :len $AccessList ] > 0) do={ + $LogPrintExit2 debug $0 ("MAC address " . $RegVal->"mac-address" . " already known: " . \ + [ /caps-man/access-list/get $AccessList comment ]) false; + } + + :if ([ :len $AccessList ] = 0) do={ + :local Address "no dhcp lease"; + :local DnsName "no dhcp lease"; + :local HostName "no dhcp lease"; + :local Lease ([ /ip/dhcp-server/lease/find where mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); + :if ([ :len $Lease ] > 0) do={ + :set Address [ /ip/dhcp-server/lease/get $Lease address ]; + :set HostName [ $EitherOr [ /ip/dhcp-server/lease/get $Lease host-name ] "no hostname" ]; + :set DnsName "no dns name"; + :local DnsRec ([ /ip/dns/static/find where address=$Address ]->0); + :if ([ :len $DnsRec ] > 0) do={ + :set DnsName [ /ip/dns/static/get $DnsRec name ]; + } + } + :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]); + :local Vendor [ $GetMacVendor ($RegVal->"mac-address") ]; + :local Message ("MAC address " . $RegVal->"mac-address" . " (" . $Vendor . ", " . $HostName . ") " . \ + "first seen on " . $DateTime . " connected to SSID " . $RegVal->"ssid" . ", interface " . $RegVal->"interface"); + $LogPrintExit2 info $0 $Message false; + /caps-man/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "mobile-phone" ] . $RegVal->"mac-address" . " connected to " . $RegVal->"ssid"); \ + message=("A device with unknown MAC address connected to " . $RegVal->"ssid" . " on " . $Identity . ".\n\n" . \ + "Controller: " . $Identity . "\n" . \ + "Interface: " . $RegVal->"interface" . "\n" . \ + "SSID: " . $RegVal->"ssid" . "\n" . \ + "MAC: " . $RegVal->"mac-address" . "\n" . \ + "Vendor: " . $Vendor . "\n" . \ + "Hostname: " . $HostName . "\n" . \ + "Address: " . $Address . "\n" . \ + "DNS name: " . $DnsName . "\n" . \ + "Date: " . $DateTime) }); + } + } else={ + $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; + } +} diff --git a/collect-wireless-mac.local b/collect-wireless-mac.local index ee07f54..2da00ca 100644 --- a/collect-wireless-mac.local +++ b/collect-wireless-mac.local @@ -1,86 +1,3 @@ #!rsc by RouterOS -# RouterOS script: collect-wireless-mac.local -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# collect wireless mac adresses in access list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md -# -# provides: lease-script, order=40 -# -# !! Do not edit this file, it is generated from template! - -:local 0 "collect-wireless-mac.local"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Identity; - -:global EitherOr; -:global GetMacVendor; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -$ScriptLock $0 false 10; - -:if ([ :len [ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - /interface/wireless/access-list/add comment="--- collected above ---" disabled=yes; - $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false; -} -:local PlaceBefore ([ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ]->0); - -:foreach Reg in=[ /interface/wireless/registration-table/find ] do={ - :local RegVal; - :do { - :set RegVal [ /interface/wireless/registration-table/get $Reg ]; - } on-error={ - $LogPrintExit2 debug $0 ("Device already gone... Ignoring.") false; - } - - :if ([ :len ($RegVal->"mac-address") ] > 0) do={ - :local AccessList ([ /interface/wireless/access-list/find where mac-address=($RegVal->"mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - $LogPrintExit2 debug $0 ("MAC address " . $RegVal->"mac-address" . " already known: " . \ - [ /interface/wireless/access-list/get $AccessList comment ]) false; - } - - :if ([ :len $AccessList ] = 0) do={ - :local Address "no dhcp lease"; - :local DnsName "no dhcp lease"; - :local HostName "no dhcp lease"; - :local Lease ([ /ip/dhcp-server/lease/find where mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); - :if ([ :len $Lease ] > 0) do={ - :set Address [ /ip/dhcp-server/lease/get $Lease address ]; - :set HostName [ $EitherOr [ /ip/dhcp-server/lease/get $Lease host-name ] "no hostname" ]; - :set DnsName "no dns name"; - :local DnsRec ([ /ip/dns/static/find where address=$Address ]->0); - :if ([ :len $DnsRec ] > 0) do={ - :set DnsName [ /ip/dns/static/get $DnsRec name ]; - } - } - :set ($RegVal->"ssid") [ /interface/wireless/get [ find where name=($RegVal->"interface") ] ssid ]; - :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]); - :local Vendor [ $GetMacVendor ($RegVal->"mac-address") ]; - :local Message ("MAC address " . $RegVal->"mac-address" . " (" . $Vendor . ", " . $HostName . ") " . \ - "first seen on " . $DateTime . " connected to SSID " . $RegVal->"ssid" . ", interface " . $RegVal->"interface"); - $LogPrintExit2 info $0 $Message false; - /interface/wireless/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "mobile-phone" ] . $RegVal->"mac-address" . " connected to " . $RegVal->"ssid"); \ - message=("A device with unknown MAC address connected to " . $RegVal->"ssid" . " on " . $Identity . ".\n\n" . \ - "Controller: " . $Identity . "\n" . \ - "Interface: " . $RegVal->"interface" . "\n" . \ - "SSID: " . $RegVal->"ssid" . "\n" . \ - "MAC: " . $RegVal->"mac-address" . "\n" . \ - "Vendor: " . $Vendor . "\n" . \ - "Hostname: " . $HostName . "\n" . \ - "Address: " . $Address . "\n" . \ - "DNS name: " . $DnsName . "\n" . \ - "Date: " . $DateTime) }); - } - } else={ - $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; - } -} +# dummy for migration diff --git a/collect-wireless-mac.local.rsc b/collect-wireless-mac.local.rsc new file mode 100644 index 0000000..ee07f54 --- /dev/null +++ b/collect-wireless-mac.local.rsc @@ -0,0 +1,86 @@ +#!rsc by RouterOS +# RouterOS script: collect-wireless-mac.local +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# collect wireless mac adresses in access list +# https://git.eworm.de/cgit/routeros-scripts/about/doc/collect-wireless-mac.md +# +# provides: lease-script, order=40 +# +# !! Do not edit this file, it is generated from template! + +:local 0 "collect-wireless-mac.local"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Identity; + +:global EitherOr; +:global GetMacVendor; +:global LogPrintExit2; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; + +$ScriptLock $0 false 10; + +:if ([ :len [ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ + /interface/wireless/access-list/add comment="--- collected above ---" disabled=yes; + $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false; +} +:local PlaceBefore ([ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ]->0); + +:foreach Reg in=[ /interface/wireless/registration-table/find ] do={ + :local RegVal; + :do { + :set RegVal [ /interface/wireless/registration-table/get $Reg ]; + } on-error={ + $LogPrintExit2 debug $0 ("Device already gone... Ignoring.") false; + } + + :if ([ :len ($RegVal->"mac-address") ] > 0) do={ + :local AccessList ([ /interface/wireless/access-list/find where mac-address=($RegVal->"mac-address") ]->0); + :if ([ :len $AccessList ] > 0) do={ + $LogPrintExit2 debug $0 ("MAC address " . $RegVal->"mac-address" . " already known: " . \ + [ /interface/wireless/access-list/get $AccessList comment ]) false; + } + + :if ([ :len $AccessList ] = 0) do={ + :local Address "no dhcp lease"; + :local DnsName "no dhcp lease"; + :local HostName "no dhcp lease"; + :local Lease ([ /ip/dhcp-server/lease/find where mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); + :if ([ :len $Lease ] > 0) do={ + :set Address [ /ip/dhcp-server/lease/get $Lease address ]; + :set HostName [ $EitherOr [ /ip/dhcp-server/lease/get $Lease host-name ] "no hostname" ]; + :set DnsName "no dns name"; + :local DnsRec ([ /ip/dns/static/find where address=$Address ]->0); + :if ([ :len $DnsRec ] > 0) do={ + :set DnsName [ /ip/dns/static/get $DnsRec name ]; + } + } + :set ($RegVal->"ssid") [ /interface/wireless/get [ find where name=($RegVal->"interface") ] ssid ]; + :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]); + :local Vendor [ $GetMacVendor ($RegVal->"mac-address") ]; + :local Message ("MAC address " . $RegVal->"mac-address" . " (" . $Vendor . ", " . $HostName . ") " . \ + "first seen on " . $DateTime . " connected to SSID " . $RegVal->"ssid" . ", interface " . $RegVal->"interface"); + $LogPrintExit2 info $0 $Message false; + /interface/wireless/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "mobile-phone" ] . $RegVal->"mac-address" . " connected to " . $RegVal->"ssid"); \ + message=("A device with unknown MAC address connected to " . $RegVal->"ssid" . " on " . $Identity . ".\n\n" . \ + "Controller: " . $Identity . "\n" . \ + "Interface: " . $RegVal->"interface" . "\n" . \ + "SSID: " . $RegVal->"ssid" . "\n" . \ + "MAC: " . $RegVal->"mac-address" . "\n" . \ + "Vendor: " . $Vendor . "\n" . \ + "Hostname: " . $HostName . "\n" . \ + "Address: " . $Address . "\n" . \ + "DNS name: " . $DnsName . "\n" . \ + "Date: " . $DateTime) }); + } + } else={ + $LogPrintExit2 debug $0 ("No mac address available... Ignoring.") false; + } +} diff --git a/collect-wireless-mac.template b/collect-wireless-mac.template.rsc index c315385..c315385 100644 --- a/collect-wireless-mac.template +++ b/collect-wireless-mac.template.rsc diff --git a/daily-psk.capsman b/daily-psk.capsman index 17a09e1..2da00ca 100644 --- a/daily-psk.capsman +++ b/daily-psk.capsman @@ -1,96 +1,3 @@ #!rsc by RouterOS -# RouterOS script: daily-psk.capsman -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# update daily PSK (pre shared key) -# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "daily-psk.capsman"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global DailyPskMatchComment; -:global DailyPskQrCodeUrl; -:global Identity; - -:global LogPrintExit2; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitForFile; -:global WaitFullyConnected; - -$WaitFullyConnected; - -# return pseudo-random string for PSK -:local GeneratePSK do={ - :local Date [ :tostr $1 ]; - - :global DailyPskSecrets; - - :local Months { "jan"; "feb"; "mar"; "apr"; "may"; "jun"; - "jul"; "aug"; "sep"; "oct"; "nov"; "dec" }; - - :local Month [ :pick $Date 0 3 ]; - :local Day [ :tonum [ :pick $Date 4 6 ] ]; - :local Year [ :pick $Date 7 11 ]; - - :for MIndex from=0 to=[ :len $Months ] do={ - :if ($Months->$MIndex = $Month) do={ - :set Month ($MIndex + 1); - } - } - - :local A ((14 - $Month) / 12); - :local B ($Year - $A); - :local C ($Month + 12 * $A - 2); - :local WeekDay (7000 + $Day + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); - :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); - - :return (($DailyPskSecrets->0->($Day - 1)) . \ - ($DailyPskSecrets->1->($Month - 1)) . \ - ($DailyPskSecrets->2->$WeekDay)); -} - -:local Seen ({}); -:local Date [ /system/clock/get date ]; -:local NewPsk [ $GeneratePSK $Date ]; - -:foreach AccList in=[ /caps-man/access-list/find where comment~$DailyPskMatchComment ] do={ - :local SsidRegExp [ /caps-man/access-list/get $AccList ssid-regexp ]; - :local Configuration ([ /caps-man/configuration/find where ssid~$SsidRegExp ]->0); - :local Ssid [ /caps-man/configuration/get $Configuration ssid ]; - :local OldPsk [ /caps-man/access-list/get $AccList private-passphrase ]; - :local Skip 0; - - :if ($NewPsk != $OldPsk) do={ - $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false; - /caps-man/access-list/set $AccList private-passphrase=$NewPsk; - - :if ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ - :foreach SeenSsid in=$Seen do={ - :if ($SeenSsid = $Ssid) do={ - $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; - :set Skip 1; - } - } - - :if ($Skip = 0) do={ - :set Seen ($Seen, $Ssid); - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - "SSID: " . $Ssid . "\n" . \ - "PSK: " . $NewPsk . "\n" . \ - "Date: " . $Date . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); - } - } - } -} +# dummy for migration diff --git a/daily-psk.capsman.rsc b/daily-psk.capsman.rsc new file mode 100644 index 0000000..17a09e1 --- /dev/null +++ b/daily-psk.capsman.rsc @@ -0,0 +1,96 @@ +#!rsc by RouterOS +# RouterOS script: daily-psk.capsman +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# update daily PSK (pre shared key) +# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md +# +# !! Do not edit this file, it is generated from template! + +:local 0 "daily-psk.capsman"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global DailyPskMatchComment; +:global DailyPskQrCodeUrl; +:global Identity; + +:global LogPrintExit2; +:global SendNotification2; +:global SymbolForNotification; +:global UrlEncode; +:global WaitForFile; +:global WaitFullyConnected; + +$WaitFullyConnected; + +# return pseudo-random string for PSK +:local GeneratePSK do={ + :local Date [ :tostr $1 ]; + + :global DailyPskSecrets; + + :local Months { "jan"; "feb"; "mar"; "apr"; "may"; "jun"; + "jul"; "aug"; "sep"; "oct"; "nov"; "dec" }; + + :local Month [ :pick $Date 0 3 ]; + :local Day [ :tonum [ :pick $Date 4 6 ] ]; + :local Year [ :pick $Date 7 11 ]; + + :for MIndex from=0 to=[ :len $Months ] do={ + :if ($Months->$MIndex = $Month) do={ + :set Month ($MIndex + 1); + } + } + + :local A ((14 - $Month) / 12); + :local B ($Year - $A); + :local C ($Month + 12 * $A - 2); + :local WeekDay (7000 + $Day + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); + :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); + + :return (($DailyPskSecrets->0->($Day - 1)) . \ + ($DailyPskSecrets->1->($Month - 1)) . \ + ($DailyPskSecrets->2->$WeekDay)); +} + +:local Seen ({}); +:local Date [ /system/clock/get date ]; +:local NewPsk [ $GeneratePSK $Date ]; + +:foreach AccList in=[ /caps-man/access-list/find where comment~$DailyPskMatchComment ] do={ + :local SsidRegExp [ /caps-man/access-list/get $AccList ssid-regexp ]; + :local Configuration ([ /caps-man/configuration/find where ssid~$SsidRegExp ]->0); + :local Ssid [ /caps-man/configuration/get $Configuration ssid ]; + :local OldPsk [ /caps-man/access-list/get $AccList private-passphrase ]; + :local Skip 0; + + :if ($NewPsk != $OldPsk) do={ + $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false; + /caps-man/access-list/set $AccList private-passphrase=$NewPsk; + + :if ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ + :foreach SeenSsid in=$Seen do={ + :if ($SeenSsid = $Ssid) do={ + $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; + :set Skip 1; + } + } + + :if ($Skip = 0) do={ + :set Seen ($Seen, $Ssid); + :local Link ($DailyPskQrCodeUrl . \ + "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ + message=("This is the daily PSK on " . $Identity . ":\n\n" . \ + "SSID: " . $Ssid . "\n" . \ + "PSK: " . $NewPsk . "\n" . \ + "Date: " . $Date . "\n\n" . \ + "A client device specific rule must not exist!"); link=$Link }); + } + } + } +} diff --git a/daily-psk.local b/daily-psk.local index 17a60f7..2da00ca 100644 --- a/daily-psk.local +++ b/daily-psk.local @@ -1,95 +1,3 @@ #!rsc by RouterOS -# RouterOS script: daily-psk.local -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# update daily PSK (pre shared key) -# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "daily-psk.local"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global DailyPskMatchComment; -:global DailyPskQrCodeUrl; -:global Identity; - -:global LogPrintExit2; -:global SendNotification2; -:global SymbolForNotification; -:global UrlEncode; -:global WaitForFile; -:global WaitFullyConnected; - -$WaitFullyConnected; - -# return pseudo-random string for PSK -:local GeneratePSK do={ - :local Date [ :tostr $1 ]; - - :global DailyPskSecrets; - - :local Months { "jan"; "feb"; "mar"; "apr"; "may"; "jun"; - "jul"; "aug"; "sep"; "oct"; "nov"; "dec" }; - - :local Month [ :pick $Date 0 3 ]; - :local Day [ :tonum [ :pick $Date 4 6 ] ]; - :local Year [ :pick $Date 7 11 ]; - - :for MIndex from=0 to=[ :len $Months ] do={ - :if ($Months->$MIndex = $Month) do={ - :set Month ($MIndex + 1); - } - } - - :local A ((14 - $Month) / 12); - :local B ($Year - $A); - :local C ($Month + 12 * $A - 2); - :local WeekDay (7000 + $Day + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); - :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); - - :return (($DailyPskSecrets->0->($Day - 1)) . \ - ($DailyPskSecrets->1->($Month - 1)) . \ - ($DailyPskSecrets->2->$WeekDay)); -} - -:local Seen ({}); -:local Date [ /system/clock/get date ]; -:local NewPsk [ $GeneratePSK $Date ]; - -:foreach AccList in=[ /interface/wireless/access-list/find where comment~$DailyPskMatchComment ] do={ - :local IntName [ /interface/wireless/access-list/get $AccList interface ]; - :local Ssid [ /interface/wireless/get $IntName ssid ]; - :local OldPsk [ /interface/wireless/access-list/get $AccList private-pre-shared-key ]; - :local Skip 0; - - :if ($NewPsk != $OldPsk) do={ - $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false; - /interface/wireless/access-list/set $AccList private-pre-shared-key=$NewPsk; - - :if ([ :len [ /interface/wireless/find where name=$IntName !disabled ] ] = 1) do={ - :foreach SeenSsid in=$Seen do={ - :if ($SeenSsid = $Ssid) do={ - $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; - :set Skip 1; - } - } - - :if ($Skip = 0) do={ - :set Seen ($Seen, $Ssid); - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - "SSID: " . $Ssid . "\n" . \ - "PSK: " . $NewPsk . "\n" . \ - "Date: " . $Date . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); - } - } - } -} +# dummy for migration diff --git a/daily-psk.local.rsc b/daily-psk.local.rsc new file mode 100644 index 0000000..17a60f7 --- /dev/null +++ b/daily-psk.local.rsc @@ -0,0 +1,95 @@ +#!rsc by RouterOS +# RouterOS script: daily-psk.local +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# update daily PSK (pre shared key) +# https://git.eworm.de/cgit/routeros-scripts/about/doc/daily-psk.md +# +# !! Do not edit this file, it is generated from template! + +:local 0 "daily-psk.local"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global DailyPskMatchComment; +:global DailyPskQrCodeUrl; +:global Identity; + +:global LogPrintExit2; +:global SendNotification2; +:global SymbolForNotification; +:global UrlEncode; +:global WaitForFile; +:global WaitFullyConnected; + +$WaitFullyConnected; + +# return pseudo-random string for PSK +:local GeneratePSK do={ + :local Date [ :tostr $1 ]; + + :global DailyPskSecrets; + + :local Months { "jan"; "feb"; "mar"; "apr"; "may"; "jun"; + "jul"; "aug"; "sep"; "oct"; "nov"; "dec" }; + + :local Month [ :pick $Date 0 3 ]; + :local Day [ :tonum [ :pick $Date 4 6 ] ]; + :local Year [ :pick $Date 7 11 ]; + + :for MIndex from=0 to=[ :len $Months ] do={ + :if ($Months->$MIndex = $Month) do={ + :set Month ($MIndex + 1); + } + } + + :local A ((14 - $Month) / 12); + :local B ($Year - $A); + :local C ($Month + 12 * $A - 2); + :local WeekDay (7000 + $Day + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); + :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); + + :return (($DailyPskSecrets->0->($Day - 1)) . \ + ($DailyPskSecrets->1->($Month - 1)) . \ + ($DailyPskSecrets->2->$WeekDay)); +} + +:local Seen ({}); +:local Date [ /system/clock/get date ]; +:local NewPsk [ $GeneratePSK $Date ]; + +:foreach AccList in=[ /interface/wireless/access-list/find where comment~$DailyPskMatchComment ] do={ + :local IntName [ /interface/wireless/access-list/get $AccList interface ]; + :local Ssid [ /interface/wireless/get $IntName ssid ]; + :local OldPsk [ /interface/wireless/access-list/get $AccList private-pre-shared-key ]; + :local Skip 0; + + :if ($NewPsk != $OldPsk) do={ + $LogPrintExit2 info $0 ("Updating daily PSK for " . $Ssid . " to " . $NewPsk . " (was " . $OldPsk . ")") false; + /interface/wireless/access-list/set $AccList private-pre-shared-key=$NewPsk; + + :if ([ :len [ /interface/wireless/find where name=$IntName !disabled ] ] = 1) do={ + :foreach SeenSsid in=$Seen do={ + :if ($SeenSsid = $Ssid) do={ + $LogPrintExit2 debug $0 ("Already sent a mail for SSID " . $Ssid . ", skipping.") false; + :set Skip 1; + } + } + + :if ($Skip = 0) do={ + :set Seen ($Seen, $Ssid); + :local Link ($DailyPskQrCodeUrl . \ + "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ + message=("This is the daily PSK on " . $Identity . ":\n\n" . \ + "SSID: " . $Ssid . "\n" . \ + "PSK: " . $NewPsk . "\n" . \ + "Date: " . $Date . "\n\n" . \ + "A client device specific rule must not exist!"); link=$Link }); + } + } + } +} diff --git a/daily-psk.template b/daily-psk.template.rsc index 7e41a1f..7e41a1f 100644 --- a/daily-psk.template +++ b/daily-psk.template.rsc diff --git a/dhcp-lease-comment.capsman b/dhcp-lease-comment.capsman index 55b76bf..2da00ca 100644 --- a/dhcp-lease-comment.capsman +++ b/dhcp-lease-comment.capsman @@ -1,30 +1,3 @@ #!rsc by RouterOS -# RouterOS script: dhcp-lease-comment.capsman -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: lease-script, order=60 -# -# update dhcp-server lease comment with infos from access-list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "dhcp-lease-comment.capsman"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; - -:foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic=yes status=bound ] do={ - :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - :local NewComment; - :local AccessList ([ /caps-man/access-list/find where mac-address=($LeaseVal->"mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - :set NewComment [ /caps-man/access-list/get $AccessList comment ]; - } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"mac-address" . ": " . $NewComment) false; - /ip/dhcp-server/lease/set comment=$NewComment $Lease; - } -} +# dummy for migration diff --git a/dhcp-lease-comment.capsman.rsc b/dhcp-lease-comment.capsman.rsc new file mode 100644 index 0000000..55b76bf --- /dev/null +++ b/dhcp-lease-comment.capsman.rsc @@ -0,0 +1,30 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-lease-comment.capsman +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: lease-script, order=60 +# +# update dhcp-server lease comment with infos from access-list +# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md +# +# !! Do not edit this file, it is generated from template! + +:local 0 "dhcp-lease-comment.capsman"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; + +:foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic=yes status=bound ] do={ + :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; + :local NewComment; + :local AccessList ([ /caps-man/access-list/find where mac-address=($LeaseVal->"mac-address") ]->0); + :if ([ :len $AccessList ] > 0) do={ + :set NewComment [ /caps-man/access-list/get $AccessList comment ]; + } + :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ + $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"mac-address" . ": " . $NewComment) false; + /ip/dhcp-server/lease/set comment=$NewComment $Lease; + } +} diff --git a/dhcp-lease-comment.local b/dhcp-lease-comment.local index 3f4d6c5..2da00ca 100644 --- a/dhcp-lease-comment.local +++ b/dhcp-lease-comment.local @@ -1,30 +1,3 @@ #!rsc by RouterOS -# RouterOS script: dhcp-lease-comment.local -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: lease-script, order=60 -# -# update dhcp-server lease comment with infos from access-list -# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md -# -# !! Do not edit this file, it is generated from template! - -:local 0 "dhcp-lease-comment.local"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; - -:foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic=yes status=bound ] do={ - :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - :local NewComment; - :local AccessList ([ /interface/wireless/access-list/find where mac-address=($LeaseVal->"mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - :set NewComment [ /interface/wireless/access-list/get $AccessList comment ]; - } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"mac-address" . ": " . $NewComment) false; - /ip/dhcp-server/lease/set comment=$NewComment $Lease; - } -} +# dummy for migration diff --git a/dhcp-lease-comment.local.rsc b/dhcp-lease-comment.local.rsc new file mode 100644 index 0000000..3f4d6c5 --- /dev/null +++ b/dhcp-lease-comment.local.rsc @@ -0,0 +1,30 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-lease-comment.local +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: lease-script, order=60 +# +# update dhcp-server lease comment with infos from access-list +# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-lease-comment.md +# +# !! Do not edit this file, it is generated from template! + +:local 0 "dhcp-lease-comment.local"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; + +:foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic=yes status=bound ] do={ + :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; + :local NewComment; + :local AccessList ([ /interface/wireless/access-list/find where mac-address=($LeaseVal->"mac-address") ]->0); + :if ([ :len $AccessList ] > 0) do={ + :set NewComment [ /interface/wireless/access-list/get $AccessList comment ]; + } + :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ + $LogPrintExit2 info $0 ("Updating comment for DHCP lease " . $LeaseVal->"mac-address" . ": " . $NewComment) false; + /ip/dhcp-server/lease/set comment=$NewComment $Lease; + } +} diff --git a/dhcp-lease-comment.template b/dhcp-lease-comment.template.rsc index 9de5f97..9de5f97 100644 --- a/dhcp-lease-comment.template +++ b/dhcp-lease-comment.template.rsc diff --git a/dhcp-to-dns b/dhcp-to-dns index 48f96b2..2da00ca 100644 --- a/dhcp-to-dns +++ b/dhcp-to-dns @@ -1,97 +1,3 @@ #!rsc by RouterOS -# RouterOS script: dhcp-to-dns -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: lease-script, order=20 -# -# check DHCP leases and add/remove/update DNS entries -# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-to-dns.md - -:local 0 "dhcp-to-dns"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Domain; -:global HostNameInZone; -:global Identity; -:global PrefixInZone; -:global ServerNameInZone; - -:global CharacterReplace; -:global IfThenElse; -:global LogPrintExit2; -:global ScriptLock; - -$ScriptLock $0 false 10; - -:local Zone \ - ([ $IfThenElse ($PrefixInZone = true) "dhcp." ] . \ - [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain); -:local Ttl 5m; -:local CommentPrefix ("managed by " . $0 . " for "); -:local CommentString ("--- " . $0 . " above ---"); - -:if ([ :len [ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ] ] = 0) do={ - /ip/dns/static/add comment=$CommentString name=- type=NXDOMAIN disabled=yes; - $LogPrintExit2 warning $0 ("Added disabled static dns record with comment '" . $CommentString . "'.") false; -} -:local PlaceBefore ([ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ]->0); - -:foreach DnsRecord in=[ /ip/dns/static/find where comment ~ $CommentPrefix ] do={ - :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; - :local MacAddress [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ]; - :if ([ :len [ /ip/dhcp-server/lease/find where mac-address=$MacAddress address=($DnsRecordVal->"address") status=bound ] ] > 0) do={ - $LogPrintExit2 debug $0 ("Lease for " . $MacAddress . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry.") false; - } else={ - :local Found false; - $LogPrintExit2 info $0 ("Lease expired for " . $MacAddress . " (" . $DnsRecordVal->"name" . "), deleting DNS entry.") false; - /ip/dns/static/remove $DnsRecord; - } -} - -:foreach Lease in=[ /ip/dhcp-server/lease/find where status=bound ] do={ - :local LeaseVal; - :do { - :set LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - } on-error={ - $LogPrintExit2 debug $0 ("A lease just vanished, ignoring.") false; - } - - :if ([ :len ($LeaseVal->"address") ] > 0) do={ - :local Comment ($CommentPrefix . $LeaseVal->"mac-address"); - :local HostName [ $IfThenElse ([ :len ($LeaseVal->"host-name") ] = 0) \ - [ $CharacterReplace ($LeaseVal->"mac-address") ":" "-" ] \ - [ $CharacterReplace ($LeaseVal->"host-name") " " "" ] ]; - - :local Fqdn ($HostName . "." . [ $IfThenElse ($ServerNameInZone = true) ($LeaseVal->"server" . ".") ] . $Zone); - :local DnsRecord [ /ip/dns/static/find where name=$Fqdn ]; - :if ([ :len $DnsRecord ] > 0) do={ - :local DnsIp [ /ip/dns/static/get $DnsRecord address ]; - - :local DupMacLeases [ /ip/dhcp-server/lease/find where mac-address=($LeaseVal->"mac-address") status=bound ]; - :if ([ :len $DupMacLeases ] > 1) do={ - :set ($LeaseVal->"address") [ /ip/dhcp-server/lease/get ($DupMacLeases->([ :len $DupMacLeases ] - 1)) address ]; - } - - :if ([ :len ($LeaseVal->"host-name") ] > 0) do={ - :local HostNameLeases [ /ip/dhcp-server/lease/find where host-name=($LeaseVal->"host-name") status=bound ]; - :if ([ :len $HostNameLeases ] > 1) do={ - :set ($LeaseVal->"address") [ /ip/dhcp-server/lease/get ($HostNameLeases->0) address ]; - } - } - - :if ($DnsIp = $LeaseVal->"address") do={ - $LogPrintExit2 debug $0 ("DNS entry for " . $Fqdn . " does not need updating.") false; - } else={ - $LogPrintExit2 info $0 ("Replacing DNS entry for " . $Fqdn . ", new address is " . $LeaseVal->"address" . ".") false; - /ip/dns/static/set name=$Fqdn address=($LeaseVal->"address") ttl=$Ttl comment=$Comment $DnsRecord; - } - } else={ - $LogPrintExit2 info $0 ("Adding new DNS entry for " . $Fqdn . ", address is " . $LeaseVal->"address" . ".") false; - /ip/dns/static/add name=$Fqdn address=($LeaseVal->"address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - } - } else={ - $LogPrintExit2 debug $0 ("No address available... Ignoring.") false; - } -} +# dummy for migration diff --git a/dhcp-to-dns.rsc b/dhcp-to-dns.rsc new file mode 100644 index 0000000..48f96b2 --- /dev/null +++ b/dhcp-to-dns.rsc @@ -0,0 +1,97 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-to-dns +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: lease-script, order=20 +# +# check DHCP leases and add/remove/update DNS entries +# https://git.eworm.de/cgit/routeros-scripts/about/doc/dhcp-to-dns.md + +:local 0 "dhcp-to-dns"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Domain; +:global HostNameInZone; +:global Identity; +:global PrefixInZone; +:global ServerNameInZone; + +:global CharacterReplace; +:global IfThenElse; +:global LogPrintExit2; +:global ScriptLock; + +$ScriptLock $0 false 10; + +:local Zone \ + ([ $IfThenElse ($PrefixInZone = true) "dhcp." ] . \ + [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain); +:local Ttl 5m; +:local CommentPrefix ("managed by " . $0 . " for "); +:local CommentString ("--- " . $0 . " above ---"); + +:if ([ :len [ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ] ] = 0) do={ + /ip/dns/static/add comment=$CommentString name=- type=NXDOMAIN disabled=yes; + $LogPrintExit2 warning $0 ("Added disabled static dns record with comment '" . $CommentString . "'.") false; +} +:local PlaceBefore ([ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ]->0); + +:foreach DnsRecord in=[ /ip/dns/static/find where comment ~ $CommentPrefix ] do={ + :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; + :local MacAddress [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ]; + :if ([ :len [ /ip/dhcp-server/lease/find where mac-address=$MacAddress address=($DnsRecordVal->"address") status=bound ] ] > 0) do={ + $LogPrintExit2 debug $0 ("Lease for " . $MacAddress . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry.") false; + } else={ + :local Found false; + $LogPrintExit2 info $0 ("Lease expired for " . $MacAddress . " (" . $DnsRecordVal->"name" . "), deleting DNS entry.") false; + /ip/dns/static/remove $DnsRecord; + } +} + +:foreach Lease in=[ /ip/dhcp-server/lease/find where status=bound ] do={ + :local LeaseVal; + :do { + :set LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; + } on-error={ + $LogPrintExit2 debug $0 ("A lease just vanished, ignoring.") false; + } + + :if ([ :len ($LeaseVal->"address") ] > 0) do={ + :local Comment ($CommentPrefix . $LeaseVal->"mac-address"); + :local HostName [ $IfThenElse ([ :len ($LeaseVal->"host-name") ] = 0) \ + [ $CharacterReplace ($LeaseVal->"mac-address") ":" "-" ] \ + [ $CharacterReplace ($LeaseVal->"host-name") " " "" ] ]; + + :local Fqdn ($HostName . "." . [ $IfThenElse ($ServerNameInZone = true) ($LeaseVal->"server" . ".") ] . $Zone); + :local DnsRecord [ /ip/dns/static/find where name=$Fqdn ]; + :if ([ :len $DnsRecord ] > 0) do={ + :local DnsIp [ /ip/dns/static/get $DnsRecord address ]; + + :local DupMacLeases [ /ip/dhcp-server/lease/find where mac-address=($LeaseVal->"mac-address") status=bound ]; + :if ([ :len $DupMacLeases ] > 1) do={ + :set ($LeaseVal->"address") [ /ip/dhcp-server/lease/get ($DupMacLeases->([ :len $DupMacLeases ] - 1)) address ]; + } + + :if ([ :len ($LeaseVal->"host-name") ] > 0) do={ + :local HostNameLeases [ /ip/dhcp-server/lease/find where host-name=($LeaseVal->"host-name") status=bound ]; + :if ([ :len $HostNameLeases ] > 1) do={ + :set ($LeaseVal->"address") [ /ip/dhcp-server/lease/get ($HostNameLeases->0) address ]; + } + } + + :if ($DnsIp = $LeaseVal->"address") do={ + $LogPrintExit2 debug $0 ("DNS entry for " . $Fqdn . " does not need updating.") false; + } else={ + $LogPrintExit2 info $0 ("Replacing DNS entry for " . $Fqdn . ", new address is " . $LeaseVal->"address" . ".") false; + /ip/dns/static/set name=$Fqdn address=($LeaseVal->"address") ttl=$Ttl comment=$Comment $DnsRecord; + } + } else={ + $LogPrintExit2 info $0 ("Adding new DNS entry for " . $Fqdn . ", address is " . $LeaseVal->"address" . ".") false; + /ip/dns/static/add name=$Fqdn address=($LeaseVal->"address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore; + } + } else={ + $LogPrintExit2 debug $0 ("No address available... Ignoring.") false; + } +} diff --git a/doc/mod/scriptrunonce.md b/doc/mod/scriptrunonce.md index 5472bef..20760fb 100644 --- a/doc/mod/scriptrunonce.md +++ b/doc/mod/scriptrunonce.md @@ -28,8 +28,8 @@ The optional configuration goes to `global-config-overlay`. * `ScriptRunOnceUrlSuffix`: url suffix, appended to parameter If the parameter passed to the function is not a complete URL (starting -with protocol `ftp://`, `http://`, `https://` or `sftp://`) the values are -prepended and appended. +with protocol `ftp://`, `http://`, `https://` or `sftp://`) the base-url is +prepended, and file extension `.rsc` and url-suffix are appended. Usage and invocation -------------------- diff --git a/firmware-upgrade-reboot b/firmware-upgrade-reboot index cc45c38..2da00ca 100644 --- a/firmware-upgrade-reboot +++ b/firmware-upgrade-reboot @@ -1,42 +1,3 @@ #!rsc by RouterOS -# RouterOS script: firmware-upgrade-reboot -# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# install firmware upgrade, and reboot -# https://git.eworm.de/cgit/routeros-scripts/about/doc/firmware-upgrade-reboot.md - -:local 0 "firmware-upgrade-reboot"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; -:global VersionToNum; - -:local RouterBoard [ /system/routerboard/get ]; -:if ($RouterBoard->"current-firmware" = $RouterBoard->"upgrade-firmware") do={ - $LogPrintExit2 info $0 ("Current and upgrade firmware match with version " . \ - $RouterBoard->"current-firmware" . ".") true; -} -:if ([ $VersionToNum ($RouterBoard->"current-firmware") ] > [ $VersionToNum ($RouterBoard->"upgrade-firmware") ]) do={ - $LogPrintExit2 info $0 ("Different firmware version is available, but it is a downgrade. Ignoring.") true; -} - -:if ([ /system/routerboard/settings/get auto-upgrade ] = false) do={ - $LogPrintExit2 info $0 ("Firmware version " . $RouterBoard->"upgrade-firmware" . \ - " is available, upgrading.") false; - /system/routerboard/upgrade; -} - -:while ([ :len [ /log/find where topics=({"system";"info";"critical"}) \ - message="Firmware upgraded successfully, please reboot for changes to take effect!" ] ] = 0) do={ - :delay 1s; -} - -:local Uptime [ /system/resource/get uptime ]; -:if ($Uptime < 1m) do={ - :delay $Uptime; -} - -$LogPrintExit2 info $0 ("Firmware upgrade successful, rebooting.") false; -/system/reboot; +# dummy for migration diff --git a/firmware-upgrade-reboot.rsc b/firmware-upgrade-reboot.rsc new file mode 100644 index 0000000..cc45c38 --- /dev/null +++ b/firmware-upgrade-reboot.rsc @@ -0,0 +1,42 @@ +#!rsc by RouterOS +# RouterOS script: firmware-upgrade-reboot +# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# install firmware upgrade, and reboot +# https://git.eworm.de/cgit/routeros-scripts/about/doc/firmware-upgrade-reboot.md + +:local 0 "firmware-upgrade-reboot"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; +:global VersionToNum; + +:local RouterBoard [ /system/routerboard/get ]; +:if ($RouterBoard->"current-firmware" = $RouterBoard->"upgrade-firmware") do={ + $LogPrintExit2 info $0 ("Current and upgrade firmware match with version " . \ + $RouterBoard->"current-firmware" . ".") true; +} +:if ([ $VersionToNum ($RouterBoard->"current-firmware") ] > [ $VersionToNum ($RouterBoard->"upgrade-firmware") ]) do={ + $LogPrintExit2 info $0 ("Different firmware version is available, but it is a downgrade. Ignoring.") true; +} + +:if ([ /system/routerboard/settings/get auto-upgrade ] = false) do={ + $LogPrintExit2 info $0 ("Firmware version " . $RouterBoard->"upgrade-firmware" . \ + " is available, upgrading.") false; + /system/routerboard/upgrade; +} + +:while ([ :len [ /log/find where topics=({"system";"info";"critical"}) \ + message="Firmware upgraded successfully, please reboot for changes to take effect!" ] ] = 0) do={ + :delay 1s; +} + +:local Uptime [ /system/resource/get uptime ]; +:if ($Uptime < 1m) do={ + :delay $Uptime; +} + +$LogPrintExit2 info $0 ("Firmware upgrade successful, rebooting.") false; +/system/reboot; diff --git a/global-config b/global-config index 770efd0..2da00ca 100644 --- a/global-config +++ b/global-config @@ -1,220 +1,3 @@ #!rsc by RouterOS -# RouterOS script: global-config -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# global configuration -# https://git.eworm.de/cgit/routeros-scripts/about/ - -# Set this to 'true' to disable news and change notifications. -:global NoNewsAndChangesNotification false; - -# Add extra text (or emojis) in notification tags. -:global IdentityExtra ""; - -# This is used for DNS and backup file. -:global Domain "example.com"; -:global HostNameInZone true; -:global PrefixInZone true; -:global ServerNameInZone false; - -# You can send e-mail notifications. Configure the system's mail settings -# (/tool/e-mail), then install the module: -# $ScriptInstallUpdate mod/notification-email -# The to-address needs to be filled; cc-address can be empty, one address -# or a comma separated list of addresses. -:global EmailGeneralTo ""; -:global EmailGeneralCc ""; -#:global EmailGeneralTo "mail@example.com"; -#:global EmailGeneralCc "another@example.com,third@example.com"; - -# You can send Telegram notifications. Register a bot -# and add the token and chat ids here, then install the module: -# $ScriptInstallUpdate mod/notification-telegram -:global TelegramTokenId ""; -:global TelegramChatId ""; -#:global TelegramTokenId "123456:ABCDEF-GHI"; -#:global TelegramChatId "12345678"; -# Using telegram-chat you have to define trusted chat ids (not group ids!) -# or user names. Groups allow to chat with devices simultaneously. -#:global TelegramChatIdsTrusted { -# "12345678"; -# "example_user"; -#}; -:global TelegramChatGroups "(all)"; -#:global TelegramChatGroups "(all|home|office)"; -# This is whether or not to send Telegram messages with fixed-width font. -:global TelegramFixedWidthFont true; - -# You can send Matrix notifications. Configure these settings and -# install the module: -# $ScriptInstallUpdate mod/notification-matrix -:global MatrixHomeServer ""; -:global MatrixAccessToken ""; -:global MatrixRoom ""; -#:global MatrixHomeServer "matrix.org"; -#:global MatrixAccessToken "123456ABCDEFGHI..."; -#:global MatrixRoom "!example:matrix.org"; - -# It is possible to override e-mail, Telegram and Matrix setting for every -# script. This is done in arrays, where 'Override' is appended to the -# variable name, like this: -#:global EmailGeneralToOverride { -# "check-certificates"="override@example.com"; -# "backup-email"="backup@example.com"; -#} - -# Toggle this to disable symbols in notifications. -:global NotificationsWithSymbols true; -# Toggle this to disable color output in terminal/cli. -:global TerminalColorOutput true; - -# This defines what backups to generate and what password to use. -:global BackupSendBinary false; -:global BackupSendExport true; -:global BackupSendGlobalConfig true; -:global BackupPassword "v3ry-s3cr3t"; -:global BackupRandomDelay 0; -# These credentials are used to upload backup and config export files. -# SFTP authentication is tricky, you may have to limit authentication -# methods for your SSH server. -:global BackupUploadUrl "sftp://example.com/backup/"; -:global BackupUploadUser "mikrotik"; -:global BackupUploadPass "v3ry-s3cr3t"; - -# This defines what log messages to filter or include by topic or message -# text. Regular expressions are supported. Do *NOT* set an empty string, -# that will filter or include everything! -# These are filters, so excluding messages from forwarding. -:global LogForwardFilter "(debug|info)"; -:global LogForwardFilterMessage []; -#:global LogForwardFilterMessage "message text"; -#:global LogForwardFilterMessage "(message text|another text|...)"; -# ... and another setting with reverse logic. This includes messages even -# if filtered above. -:global LogForwardInclude []; -:global LogForwardIncludeMessage []; -#:global LogForwardInclude "account"; -#:global LogForwardIncludeMessage "message text"; - -# Specify an address to enable auto update to version assumed safe. -# The configured channel (bugfix, current, release-candidate) is appended. -:global SafeUpdateUrl ""; -#:global SafeUpdateUrl "https://example.com/ros/safe-update/"; -# Allow to install patch updates automatically. -:global SafeUpdatePatch false; -# Allow to install updates automatically if seen in neighbor list. -:global SafeUpdateNeighbor false; -# Install *ALL* updates automatically! -# Set to all upper-case "Yes, please!" to enable. -:global SafeUpdateAll "no"; - -# These thresholds control when to send health notification -# on temperature and voltage. -:global CheckHealthTemperature { - temperature=50; - cpu-temperature=70; - board-temperature1=50; - board-temperature2=50; -} -# This is deviation on recovery threshold against notification flooding. -:global CheckHealthTemperatureDeviation 3; -:global CheckHealthVoltageLow 115; -:global CheckHealthVoltagePercent 10; - -# Access-list entries matching this comment are updated -# with daily pseudo-random PSK. -:global DailyPskMatchComment "Daily PSK"; -:global DailyPskQrCodeUrl "https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi"; -:global DailyPskSecrets { - { "Abusive"; "Aggressive"; "Bored"; "Chemical"; "Cold"; - "Cruel"; "Curved"; "Delightful"; "Discreet"; "Elite"; - "Evasive"; "Faded"; "Flat"; "Future"; "Grandiose"; - "Hanging"; "Humorous"; "Interesting"; "Magenta"; - "Magnificent"; "Numerous"; "Optimal"; "Pathetic"; - "Possessive"; "Remarkable"; "Rightful"; "Ruthless"; - "Stale"; "Unusual"; "Useless"; "Various" }; - { "Adhesive"; "Amusing"; "Astonishing"; "Frantic"; - "Kindhearted"; "Limping"; "Roasted"; "Robust"; - "Staking"; "Thundering"; "Ultra"; "Unreal" }; - { "Belief"; "Button"; "Curtain"; "Edge"; "Jewel"; - "String"; "Whistle" } -} - -# Run different commands with multiple mode-button presses. -:global ModeButton { - 1="/system/script/run leds-toggle-mode;"; - 2=":global Identity; :global SendNotification; :global SymbolForNotification; \$SendNotification ([ \$SymbolForNotification \"earth\" ] . \"Hello...\") (\"Hello world, \" . \$Identity . \" calling!\");"; - 3="/system/shutdown;"; - 4="/system/reboot;"; - 5=":global BridgePortVlan; \$BridgePortVlan alt;"; -# add more here... -}; -# This led gives visual feedback if type is 'on' or 'off'. -:global ModeButtonLED "user-led"; - -# Run commands on SMS action. -:global SmsAction { - bridge-port-vlan-alt=":global BridgePortVlan; \$BridgePortVlan alt;"; - reboot="/system/reboot;"; - shutdown="/system/shutdown;"; -# add more here... -}; - -# Run commands by hooking into SMS forward. -:global SmsForwardHooks { - { match="magic string"; - allowed-number="12345678"; - command="/system/script/run ..." }; -# add more here... -}; - -# This is the address used to send gps data to. -:global GpsTrackUrl "https://example.com/index.php"; - -# Enable this to fetch scripts from given url. -:global ScriptUpdatesFetch true; -:global ScriptUpdatesBaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/"; -# alternative urls - main: stable code - next: currently in development -#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/main/"; -#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/next/"; -#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/main/"; -#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/next/"; -:global ScriptUpdatesUrlSuffix ""; -# use next branch with default url (git.eworm.de) -#:global ScriptUpdatesUrlSuffix "\?h=next"; - -# Use this for defaults with $ScriptRunOnce -# Install module with: -# $ScriptInstallUpdate mod/scriptrunonce -:global ScriptRunOnceBaseUrl ""; -:global ScriptRunOnceUrlSuffix ""; - -# This project is developed in private spare time and usage is free of charge -# for you. If you like the scripts and think this is of value for you or your -# business please consider a donation: -# https://git.eworm.de/cgit/routeros-scripts/about/#donate -# Enable this to silence donation hint. -:global IDonate false; - -# Use this for certificate auto-renew -:global CertRenewUrl ""; -#:global CertRenewUrl "https://example.com/certificates/"; -:global CertRenewTime 3w; -:global CertRenewPass { - "v3ry-s3cr3t"; - "4n0th3r-s3cr3t"; -} -:global CertWarnTime 2w; -:global CertIssuedExportPass { - "cert1-cn"="v3ry-s3cr3t"; - "cert2-cn"="4n0th3r-s3cr3t"; -} - -# load custom settings from overlay -# Warning: Do *NOT* copy this code to overlay! -:do { - /system/script/run global-config-overlay; -} on-error={ - :log error ("Loading configuration from overlay failed!"); -} +# dummy for migration diff --git a/global-config-overlay b/global-config-overlay.rsc index a95f9cc..a95f9cc 100644 --- a/global-config-overlay +++ b/global-config-overlay.rsc diff --git a/global-config.changes b/global-config.changes index 3e0372a..be34365 100644 --- a/global-config.changes +++ b/global-config.changes @@ -103,6 +103,7 @@ 92="Made qr-code url configurable for 'daily-psk'."; 93="Added support to backup global-config-overlay in 'backup-email' and 'backup-upload'."; 94="Added support for host addresses in address-list for 'ipv6-update'."; + 95="Renamed script files in repository, running migration. No user interaction is required."; }; # Migration steps to be applied on script updates @@ -118,4 +119,5 @@ 81=":global NtpPool; :if ([ :len [ /system/script/find where name=\"rotate-ntp\" ] ] > 0) do={ /system/script/remove [ find where name=\"rotate-ntp\" ]; /system/scheduler/remove [ find where name=\"rotate-ntp\" ]; /system/ntp/client/set servers=\$NtpPool; };"; 82=":global CharacterReplace; :foreach Netwatch in=[ /tool/netwatch/find where comment~\"notify\" !disabled ] do={ /tool/netwatch/set \$Netwatch comment=[ \$CharacterReplace [ get \$Netwatch comment ] \"hostname=\" \"name=\" ]; };"; 84=":global ScriptInstallUpdate; :global EmailGeneralTo; :if ([ /tool/e-mail/get address ] != \"0.0.0.0\" && [ :len \$EmailGeneralTo ] > 0) do={ \$ScriptInstallUpdate mod/notification-email; }"; + 95=":global ScriptInstallUpdate; :global CharacterReplace; :foreach Script in=[ /system/script/find where name~\"\\\\.rsc\\\$\" source~\"^#!rsc by RouterOS\\n\" ] do={ /system/script/set \$Script name=[ \$CharacterReplace [ get \$Script name ] \".rsc\" \"\" ]; }; \$ScriptInstallUpdate;"; }; diff --git a/global-config.rsc b/global-config.rsc new file mode 100644 index 0000000..770efd0 --- /dev/null +++ b/global-config.rsc @@ -0,0 +1,220 @@ +#!rsc by RouterOS +# RouterOS script: global-config +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# global configuration +# https://git.eworm.de/cgit/routeros-scripts/about/ + +# Set this to 'true' to disable news and change notifications. +:global NoNewsAndChangesNotification false; + +# Add extra text (or emojis) in notification tags. +:global IdentityExtra ""; + +# This is used for DNS and backup file. +:global Domain "example.com"; +:global HostNameInZone true; +:global PrefixInZone true; +:global ServerNameInZone false; + +# You can send e-mail notifications. Configure the system's mail settings +# (/tool/e-mail), then install the module: +# $ScriptInstallUpdate mod/notification-email +# The to-address needs to be filled; cc-address can be empty, one address +# or a comma separated list of addresses. +:global EmailGeneralTo ""; +:global EmailGeneralCc ""; +#:global EmailGeneralTo "mail@example.com"; +#:global EmailGeneralCc "another@example.com,third@example.com"; + +# You can send Telegram notifications. Register a bot +# and add the token and chat ids here, then install the module: +# $ScriptInstallUpdate mod/notification-telegram +:global TelegramTokenId ""; +:global TelegramChatId ""; +#:global TelegramTokenId "123456:ABCDEF-GHI"; +#:global TelegramChatId "12345678"; +# Using telegram-chat you have to define trusted chat ids (not group ids!) +# or user names. Groups allow to chat with devices simultaneously. +#:global TelegramChatIdsTrusted { +# "12345678"; +# "example_user"; +#}; +:global TelegramChatGroups "(all)"; +#:global TelegramChatGroups "(all|home|office)"; +# This is whether or not to send Telegram messages with fixed-width font. +:global TelegramFixedWidthFont true; + +# You can send Matrix notifications. Configure these settings and +# install the module: +# $ScriptInstallUpdate mod/notification-matrix +:global MatrixHomeServer ""; +:global MatrixAccessToken ""; +:global MatrixRoom ""; +#:global MatrixHomeServer "matrix.org"; +#:global MatrixAccessToken "123456ABCDEFGHI..."; +#:global MatrixRoom "!example:matrix.org"; + +# It is possible to override e-mail, Telegram and Matrix setting for every +# script. This is done in arrays, where 'Override' is appended to the +# variable name, like this: +#:global EmailGeneralToOverride { +# "check-certificates"="override@example.com"; +# "backup-email"="backup@example.com"; +#} + +# Toggle this to disable symbols in notifications. +:global NotificationsWithSymbols true; +# Toggle this to disable color output in terminal/cli. +:global TerminalColorOutput true; + +# This defines what backups to generate and what password to use. +:global BackupSendBinary false; +:global BackupSendExport true; +:global BackupSendGlobalConfig true; +:global BackupPassword "v3ry-s3cr3t"; +:global BackupRandomDelay 0; +# These credentials are used to upload backup and config export files. +# SFTP authentication is tricky, you may have to limit authentication +# methods for your SSH server. +:global BackupUploadUrl "sftp://example.com/backup/"; +:global BackupUploadUser "mikrotik"; +:global BackupUploadPass "v3ry-s3cr3t"; + +# This defines what log messages to filter or include by topic or message +# text. Regular expressions are supported. Do *NOT* set an empty string, +# that will filter or include everything! +# These are filters, so excluding messages from forwarding. +:global LogForwardFilter "(debug|info)"; +:global LogForwardFilterMessage []; +#:global LogForwardFilterMessage "message text"; +#:global LogForwardFilterMessage "(message text|another text|...)"; +# ... and another setting with reverse logic. This includes messages even +# if filtered above. +:global LogForwardInclude []; +:global LogForwardIncludeMessage []; +#:global LogForwardInclude "account"; +#:global LogForwardIncludeMessage "message text"; + +# Specify an address to enable auto update to version assumed safe. +# The configured channel (bugfix, current, release-candidate) is appended. +:global SafeUpdateUrl ""; +#:global SafeUpdateUrl "https://example.com/ros/safe-update/"; +# Allow to install patch updates automatically. +:global SafeUpdatePatch false; +# Allow to install updates automatically if seen in neighbor list. +:global SafeUpdateNeighbor false; +# Install *ALL* updates automatically! +# Set to all upper-case "Yes, please!" to enable. +:global SafeUpdateAll "no"; + +# These thresholds control when to send health notification +# on temperature and voltage. +:global CheckHealthTemperature { + temperature=50; + cpu-temperature=70; + board-temperature1=50; + board-temperature2=50; +} +# This is deviation on recovery threshold against notification flooding. +:global CheckHealthTemperatureDeviation 3; +:global CheckHealthVoltageLow 115; +:global CheckHealthVoltagePercent 10; + +# Access-list entries matching this comment are updated +# with daily pseudo-random PSK. +:global DailyPskMatchComment "Daily PSK"; +:global DailyPskQrCodeUrl "https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi"; +:global DailyPskSecrets { + { "Abusive"; "Aggressive"; "Bored"; "Chemical"; "Cold"; + "Cruel"; "Curved"; "Delightful"; "Discreet"; "Elite"; + "Evasive"; "Faded"; "Flat"; "Future"; "Grandiose"; + "Hanging"; "Humorous"; "Interesting"; "Magenta"; + "Magnificent"; "Numerous"; "Optimal"; "Pathetic"; + "Possessive"; "Remarkable"; "Rightful"; "Ruthless"; + "Stale"; "Unusual"; "Useless"; "Various" }; + { "Adhesive"; "Amusing"; "Astonishing"; "Frantic"; + "Kindhearted"; "Limping"; "Roasted"; "Robust"; + "Staking"; "Thundering"; "Ultra"; "Unreal" }; + { "Belief"; "Button"; "Curtain"; "Edge"; "Jewel"; + "String"; "Whistle" } +} + +# Run different commands with multiple mode-button presses. +:global ModeButton { + 1="/system/script/run leds-toggle-mode;"; + 2=":global Identity; :global SendNotification; :global SymbolForNotification; \$SendNotification ([ \$SymbolForNotification \"earth\" ] . \"Hello...\") (\"Hello world, \" . \$Identity . \" calling!\");"; + 3="/system/shutdown;"; + 4="/system/reboot;"; + 5=":global BridgePortVlan; \$BridgePortVlan alt;"; +# add more here... +}; +# This led gives visual feedback if type is 'on' or 'off'. +:global ModeButtonLED "user-led"; + +# Run commands on SMS action. +:global SmsAction { + bridge-port-vlan-alt=":global BridgePortVlan; \$BridgePortVlan alt;"; + reboot="/system/reboot;"; + shutdown="/system/shutdown;"; +# add more here... +}; + +# Run commands by hooking into SMS forward. +:global SmsForwardHooks { + { match="magic string"; + allowed-number="12345678"; + command="/system/script/run ..." }; +# add more here... +}; + +# This is the address used to send gps data to. +:global GpsTrackUrl "https://example.com/index.php"; + +# Enable this to fetch scripts from given url. +:global ScriptUpdatesFetch true; +:global ScriptUpdatesBaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/"; +# alternative urls - main: stable code - next: currently in development +#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/main/"; +#:global ScriptUpdatesBaseUrl "https://raw.githubusercontent.com/eworm-de/routeros-scripts/next/"; +#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/main/"; +#:global ScriptUpdatesBaseUrl "https://gitlab.com/eworm-de/routeros-scripts/raw/next/"; +:global ScriptUpdatesUrlSuffix ""; +# use next branch with default url (git.eworm.de) +#:global ScriptUpdatesUrlSuffix "\?h=next"; + +# Use this for defaults with $ScriptRunOnce +# Install module with: +# $ScriptInstallUpdate mod/scriptrunonce +:global ScriptRunOnceBaseUrl ""; +:global ScriptRunOnceUrlSuffix ""; + +# This project is developed in private spare time and usage is free of charge +# for you. If you like the scripts and think this is of value for you or your +# business please consider a donation: +# https://git.eworm.de/cgit/routeros-scripts/about/#donate +# Enable this to silence donation hint. +:global IDonate false; + +# Use this for certificate auto-renew +:global CertRenewUrl ""; +#:global CertRenewUrl "https://example.com/certificates/"; +:global CertRenewTime 3w; +:global CertRenewPass { + "v3ry-s3cr3t"; + "4n0th3r-s3cr3t"; +} +:global CertWarnTime 2w; +:global CertIssuedExportPass { + "cert1-cn"="v3ry-s3cr3t"; + "cert2-cn"="4n0th3r-s3cr3t"; +} + +# load custom settings from overlay +# Warning: Do *NOT* copy this code to overlay! +:do { + /system/script/run global-config-overlay; +} on-error={ + :log error ("Loading configuration from overlay failed!"); +} diff --git a/global-functions b/global-functions index 996a6e1..366ef94 100644 --- a/global-functions +++ b/global-functions @@ -6,708 +6,27 @@ # # requires RouterOS, version=7.7 # -# global functions -# https://git.eworm.de/cgit/routeros-scripts/about/ - -:local 0 "global-functions"; +# WARNING: If you find this stripped version of global-functions +# on your Router something went wrong and migration failed. To +# recover run this function: $RouterOSScriptsRecover # expected configuration version -:global ExpectedConfigVersion 94; - -# global variables not to be changed by user -:global GlobalFunctionsReady false; -:global Identity [ /system/identity/get name ]; +:global ExpectedConfigVersion 95; # global functions -:global CertificateAvailable; -:global CertificateDownload; -:global CertificateNameByCN; -:global CharacterReplace; -:global CleanFilePath; -:global DeviceInfo; -:global Dos2Unix; -:global DownloadPackage; -:global EitherOr; -:global EscapeForRegEx; -:global GetMacVendor; -:global GetRandom20CharAlNum; -:global GetRandom20CharHex; -:global GetRandomNumber; -:global Grep; -:global HexToNum; -:global IfThenElse; -:global IsDefaultRouteReachable; -:global IsDNSResolving; -:global IsFullyConnected; -:global IsMacLocallyAdministered; -:global IsTimeSync; -:global LogPrintExit2; -:global MkDir; -:global NotificationFunctions; -:global ParseKeyValueStore; -:global PrettyPrint; -:global RandomDelay; -:global Read; -:global RequiredRouterOS; -:global ScriptFromTerminal; +:global RouterOSScriptsRecover; :global ScriptInstallUpdate; -:global ScriptLock; -:global SendNotification; -:global SendNotification2; -:global SymbolByUnicodeName; -:global SymbolForNotification; -:global Unix2Dos; -:global UrlEncode; -:global ValidateSyntax; -:global VersionToNum; -:global WaitDefaultRouteReachable; -:global WaitDNSResolving; -:global WaitForFile; -:global WaitFullyConnected; -:global WaitTimeSync; - -# check and download required certificate -:set CertificateAvailable do={ - :local CommonName [ :tostr $1 ]; - - :global CertificateDownload; - :global LogPrintExit2; - :global ParseKeyValueStore; - - :if ([ /system/resource/get free-hdd-space ] < 8388608 && \ - [ /certificate/settings/get crl-download ] = true && \ - [ /certificate/settings/get crl-store ] = "system") do={ - $LogPrintExit2 warning $0 ("This system has low free flash space but " . \ - "is configured to download certificate CRLs to system!") false; - } - - :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={ - $LogPrintExit2 info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available.") false; - :if ([ $CertificateDownload $CommonName ] = false) do={ - :return false; - } - } - - :local CertVal [ /certificate/get [ find where common-name=$CommonName ] ]; - :while (($CertVal->"akid") != "" && ($CertVal->"akid") != ($CertVal->"skid")) do={ - :if ([ :len [ /certificate/find where skid=($CertVal->"akid") ] ] = 0) do={ - $LogPrintExit2 info $0 ("Certificate chain for \"" . $CommonName . \ - "\" is incomplete, missing \"" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\".") false; - :if ([ $CertificateDownload $CommonName ] = false) do={ - :return false; - } - } - :set CertVal [ /certificate/get [ find where skid=($CertVal->"akid") ] ]; - } - :return true; -} - -# download and import certificate -:set CertificateDownload do={ - :local CommonName [ :tostr $1 ]; - - :global ScriptUpdatesBaseUrl; - :global ScriptUpdatesUrlSuffix; - - :global CertificateNameByCN; - :global LogPrintExit2; - :global UrlEncode; - :global WaitForFile; - - $LogPrintExit2 info $0 ("Downloading and importing certificate with " . \ - "CommonName \"" . $CommonName . "\".") false; - :do { - :local LocalFileName ($CommonName . ".pem"); - :local UrlFileName ([ $UrlEncode $CommonName ] . ".pem"); - /tool/fetch check-certificate=yes-without-crl \ - ($ScriptUpdatesBaseUrl . "certs/" . \ - $UrlFileName . $ScriptUpdatesUrlSuffix) \ - dst-path=$LocalFileName as-value; - $WaitForFile $LocalFileName; - /certificate/import file-name=$LocalFileName passphrase="" as-value; - /file/remove $LocalFileName; - - :foreach Cert in=[ /certificate/find where name~("^" . $LocalFileName . "_[0-9]+\$") ] do={ - $CertificateNameByCN [ /certificate/get $Cert common-name ]; - } - } on-error={ - $LogPrintExit2 warning $0 ("Failed importing certificate with " . \ - "CommonName \"" . $CommonName . "\"!") false; - :return false; - } - :return true; -} - -# name a certificate by its common-name -:set CertificateNameByCN do={ - :local CommonName [ :tostr $1 ]; - - :global CharacterReplace; - - :local Cert [ /certificate/find where common-name=$CommonName ]; - /certificate/set $Cert \ - name=[ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $CommonName "'" "-" ] " " "-" ] "---" "-" ]; -} - -# character replace -:set CharacterReplace do={ - :local String [ :tostr $1 ]; - :local ReplaceFrom [ :tostr $2 ]; - :local ReplaceWith [ :tostr $3 ]; - :local Return ""; - - :if ($ReplaceFrom = "") do={ - :return $String; - } - - :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={ - :local Pos [ :find $String $ReplaceFrom ]; - :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith); - :set String [ :pick $String ($Pos + [ :len $ReplaceFrom ]) [ :len $String ] ]; - } - - :return ($Return . $String); -} - -# clean file path -:set CleanFilePath do={ - :local Path [ :tostr $1 ]; - - :global CharacterReplace; - - :while ($Path ~ "//") do={ - :set $Path [ $CharacterReplace $Path "//" "/" ]; - } - :if ([ :pick $Path 0 ] = "/") do={ - :set Path [ :pick $Path 1 [ :len $Path ] ]; - } - :if ([ :pick $Path ([ :len $Path ] - 1) ] = "/") do={ - :set Path [ :pick $Path 0 ([ :len $Path ] - 1) ]; - } - - :return $Path; -} - -# get readable device info -:set DeviceInfo do={ - :global ExpectedConfigVersion; - :global Identity; - - :global IfThenElse; - - :local Resource [ /system/resource/get ]; - :local RouterBoard; - :do { - :set RouterBoard [[ :parse "/system/routerboard/get" ]]; - } on-error={ } - :local License [ /system/license/get ]; - :local Update [ /system/package/update/get ]; - - :return ( \ - "Hostname: " . $Identity . \ - "\nBoard name: " . $Resource->"board-name" . \ - "\nArchitecture: " . $Resource->"architecture-name" . \ - [ $IfThenElse ($RouterBoard->"routerboard" = true) \ - ("\nModel: " . $RouterBoard->"model" . \ - [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \ - (" " . $RouterBoard->"revision") ] . \ - "\nSerial number: " . $RouterBoard->"serial-number") ] . \ - [ $IfThenElse ([ :len ($License->"level") ] > 0) \ - ("\nLicense: " . $License->"level") ] . \ - "\nRouterOS:" . \ - "\n Channel: " . $Update->"channel" . \ - "\n Installed: " . $Update->"installed-version" . \ - [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \ - $Update->"installed-version" != $Update->"latest-version") \ - ("\n Available: " . $Update->"latest-version") ] . \ - [ $IfThenElse ($RouterBoard->"routerboard" = true && \ - $RouterBoard->"current-firmware" != $RouterBoard->"upgrade-firmware") \ - ("\n Firmware: " . $RouterBoard->"current-firmware") ] . \ - "\nRouterOS-Scripts:" . \ - "\n Version: " . $ExpectedConfigVersion); -} - -# convert line endings, DOS -> UNIX -:set Dos2Unix do={ - :local Input [ :tostr $1 ]; - - :global CharacterReplace; - - :return [ $CharacterReplace $Input ("\r\n") ("\n") ]; -} - -# download package from upgrade server -:set DownloadPackage do={ - :local PkgName [ :tostr $1 ]; - :local PkgVer [ :tostr $2 ]; - :local PkgArch [ :tostr $3 ]; - :local PkgDir [ :tostr $4 ]; - - :global CertificateAvailable; - :global CleanFilePath; - :global LogPrintExit2; - :global MkDir; - :global WaitForFile; - - :if ([ :len $PkgName ] = 0) do={ :return false; } - :if ([ :len $PkgVer ] = 0) do={ :set PkgVer [ /system/package/update/get installed-version ]; } - :if ([ :len $PkgArch ] = 0) do={ :set PkgArch [ /system/resource/get architecture-name ]; } - - :if ($PkgName = "system") do={ :set PkgName "routeros"; } - - :local PkgFile ($PkgName . "-" . $PkgVer . "-" . $PkgArch . ".npk"); - :if ($PkgArch = "x86_64") do={ :set PkgFile ($PkgName . "-" . $PkgVer . ".npk"); } - :local PkgDest [ $CleanFilePath ($PkgDir . "/" . $PkgFile) ]; - - :if ([ $MkDir $PkgDir ] = false) do={ - $LogPrintExit2 warning $0 ("Failed creating directory, not downloading package.") false; - :return false; - } - - :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={ - $LogPrintExit2 info $0 ("Package file " . $PkgName . " already exists.") false; - :return true; - } - - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; - } - - :local Url ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile); - $LogPrintExit2 info $0 ("Downloading package file '" . $PkgName . "'...") false; - $LogPrintExit2 debug $0 ("... from url: " . $Url) false; - :local Retry 3; - :while ($Retry > 0) do={ - :do { - /tool/fetch check-certificate=yes-without-crl $Url dst-path=$PkgDest; - $WaitForFile $PkgDest; - - :if ([ /file/get [ find where name=$PkgDest ] type ] = "package") do={ - :return true; - } - } on-error={ - $LogPrintExit2 debug $0 ("Downloading package file failed.") false; - } - - /file/remove [ find where name=$PkgDest ]; - :set Retry ($Retry - 1); - } - - $LogPrintExit2 warning $0 ("Downloading package file '" . $PkgName . "' failed.") false; - :return false; -} - -# return either first (if "true") or second -:set EitherOr do={ - :global IfThenElse; - - :if ([ :typeof $1 ] = "num") do={ - :return [ $IfThenElse ($1 != 0) $1 $2 ]; - } - :return [ $IfThenElse ([ :len [ :tostr $1 ] ] > 0) $1 $2 ]; -} - -# escape for regular expression -:set EscapeForRegEx do={ - :local Input [ :tostr $1 ]; - - :if ([ :len $Input ] = 0) do={ - :return ""; - } - - :local Return ""; - :local Chars ("^.[]\$()|*+\?{}\\"); - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :if ([ :find $Chars $Char ]) do={ - :set Char ("\\" . $Char); - } - :set Return ($Return . $Char); - } - - :return $Return; -} - -# get MAC vendor -:set GetMacVendor do={ - :local Mac [ :tostr $1 ]; - - :global CertificateAvailable; - :global IsMacLocallyAdministered; - :global LogPrintExit2; - - :if ([ $IsMacLocallyAdministered $Mac ] = true) do={ - :return "locally administered"; - } - - :do { - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; - } - :local Vendor ([ /tool/fetch check-certificate=yes-without-crl \ - ("https://api.macvendors.com/" . [ :pick $Mac 0 8 ]) output=user as-value ]->"data"); - :return $Vendor; - } on-error={ - :do { - /tool/fetch check-certificate=yes-without-crl ("https://api.macvendors.com/") \ - output=none as-value; - $LogPrintExit2 debug $0 ("The mac vendor is not known in database.") false; - } on-error={ - $LogPrintExit2 warning $0 ("Failed getting mac vendor.") false; - } - :return "unknown vendor"; - } -} - -# generate random 20 chars alphabetical (A-Z & a-z) and numerical (0-9) -:set GetRandom20CharAlNum do={ - :global EitherOr; - - :return [ :rndstr length=[ $EitherOr [ :tonum $1 ] 20 ] from="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ]; -} - -# generate random 20 chars hex (0-9 and a-f) -:set GetRandom20CharHex do={ - :global EitherOr; - - :return [ :rndstr length=[ $EitherOr [ :tonum $1 ] 20 ] from="0123456789abcdef" ]; -} - -# generate random number -:set GetRandomNumber do={ - :global EitherOr; - - :return [ :rndnum from=0 to=[ $EitherOr [ :tonum $1 ] 4294967295 ] ]; -} - -# return first line that matches a pattern -:set Grep do={ - :local Input ([ :tostr $1 ] . "\n"); - :local Pattern [ :tostr $2 ]; - - :if ([ :typeof [ :find $Input $Pattern ] ] = "nil") do={ - :return []; - } - - :do { - :local Line [ :pick $Input 0 [ :find $Input "\n" ] ]; - :if ([ :typeof [ :find $Line $Pattern ] ] = "num") do={ - :return $Line; - } - :set Input [ :pick $Input ([ :find $Input "\n" ] + 1) [ :len $Input ] ]; - } while=([ :len $Input ] > 0); - - :return []; -} - -# convert from hex (string) to num -:set HexToNum do={ - :local Input [ :tostr $1 ]; - :local Hex "0123456789abcdef0123456789ABCDEF"; - :local Multi 1; - :local Return 0; - - :for I from=([ :len $Input ] - 1) to=0 do={ - :set Return ($Return + (([ :find $Hex [ :pick $Input $I ] ] % 16) * $Multi)); - :set Multi ($Multi * 16); - } - - :return $Return; -} - -# mimic conditional/ternary operator (condition ? consequent : alternative) -:set IfThenElse do={ - :if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={ - :return $2; - } - :return $3; -} - -# check if default route is reachable -:set IsDefaultRouteReachable do={ - :if ([ :len [ /ip/route/find where dst-address=0.0.0.0/0 active routing-table=main ] ] > 0) do={ - :return true; - } - :return false; -} - -# check if DNS is resolving -:set IsDNSResolving do={ - :global CharacterReplace; - - :do { - :resolve "low-ttl.eworm.de"; - } on-error={ - :return false; - } - :return true; -} - -# check if system is is fully connected (default route reachable, DNS resolving, time sync) -:set IsFullyConnected do={ - :global IsDefaultRouteReachable; - :global IsDNSResolving; - :global IsTimeSync; - - :if ([ $IsDefaultRouteReachable ] = false) do={ - :return false; - } - :if ([ $IsDNSResolving ] = false) do={ - :return false; - } - :if ([ $IsTimeSync ] = false) do={ - :return false; - } - :return true; -} - -# check if mac address is locally administered -:set IsMacLocallyAdministered do={ - :if ([ :tonum ("0x" . [ :pick $1 0 [ :find $1 ":" ] ]) ] & 2 = 2) do={ - :return true; - } - :return false; -} - -# check if system time is sync -:set IsTimeSync do={ - :global IsTimeSyncCached; - - :global LogPrintExit2; - - :if ($IsTimeSyncCached = true) do={ - :return true; - } - - :if ([ /system/ntp/client/get enabled ] = true) do={ - :if ([ /system/ntp/client/get status ] = "synchronized") do={ - :set IsTimeSyncCached true; - :return true; - } - :return false; - } - - :if ([ /system/license/get ]->"level" = "free" || \ - [ /system/resource/get ]->"board-name" = "x86") do={ - $LogPrintExit2 debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86.") false; - :return true; - } - - :if ([ /ip/cloud/get update-time ] = true) do={ - :if ([ :typeof [ /ip/cloud/get public-address ] ] = "ip") do={ - :set IsTimeSyncCached true; - :return true; - } - :return false; - } - - $LogPrintExit2 debug $0 ("No time source configured! Returning gracefully...") false; - :return true; -} - -# log and print with same text, optionally exit -:set LogPrintExit2 do={ - :local Severity [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - :local Message [ :tostr $3 ]; - :local Exit [ :tostr $4 ]; - - :global PrintDebug; - :global PrintDebugOverride; - - :global EitherOr; - - :local Debug [ $EitherOr ($PrintDebugOverride->$Name) $PrintDebug ]; - - :local PrintSeverity do={ - :global TerminalColorOutput; - - :if ($TerminalColorOutput != true) do={ - :return $1; - } - - :local Color { debug=96; info=97; warning=93; error=91 }; - :return ("\1B[" . $Color->$1 . "m" . $1 . "\1B[0m"); - } - - :local Log ([ $EitherOr $Name "<unknown>" ] . ": " . $Message); - :if ($Severity ~ ("^(debug|error|info)\$")) do={ - :if ($Severity = "debug") do={ :log debug $Log; } - :if ($Severity = "error") do={ :log error $Log; } - :if ($Severity = "info" ) do={ :log info $Log; } - } else={ - :log warning $Log; - :set Severity "warning"; - } - - :if ($Severity != "debug" || $Debug = true) do={ - :put ([ $PrintSeverity $Severity ] . ": " . $Message); - } - - :if ($Exit = "true") do={ - :error ("Hard error to exit."); - } -} - -# create directory -:set MkDir do={ - :local Path [ :tostr $1 ]; - :global CharacterReplace; - :global CleanFilePath; - :global GetRandom20CharAlNum; - :global LogPrintExit2; - :global RequiredRouterOS; - :global WaitForFile; - - :set Path [ $CleanFilePath $Path ]; - - :if ($Path = "") do={ - :return true; - } - - :if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={ - :return true; - } - - :local Error false; - :local PathNext ""; - :foreach Dir in=[ :toarray [ $CharacterReplace $Path "/" "," ] ] do={ - :local Continue false; - :set PathNext [ $CleanFilePath ($PathNext . "/" . $Dir) ]; - - :if ([ :len [ /file/find where name=$PathNext !(name="tmpfs") type="directory" ] ] = 1) do={ - :set Continue true; - } - - :if ($Continue = false && $PathNext = "tmpfs") do={ - :if ([ :len [ /disk/find where slot=tmpfs type=tmpfs ] ] = 0) do={ - $LogPrintExit2 info $0 ("Creating disk of type tmpfs.") false; - /file/remove [ find where name="tmpfs" type="directory" ]; - :do { - /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource/get total-memory ] / 3); - $WaitForFile "tmpfs"; - } on-error={ - $LogPrintExit2 warning $0 ("Creating disk of type tmpfs failed!") false; - :set Error true; - } - } - :set Continue true; - } - - :if ($Continue = false && [ :len [ /file/find where name=$PathNext ] ] = 1) do={ - $LogPrintExit2 warning $0 ("The path '" . $PathNext . "' exists, but is not a directory.") false; - :return false; - } +# recover from failed migration +:set RouterOSScriptsRecover do={ + :global ScriptInstallUpdate; - :if ($Continue = false) do={ - :local Name ($PathNext . "-" . [ $GetRandom20CharAlNum 6 ]); - :do { - /ip/smb/share/add disabled=yes directory=$PathNext name=$Name; - $WaitForFile $PathNext; - } on-error={ - $LogPrintExit2 warning $0 ("Making directory '" . $PathNext . "' failed!") false; - :set Error true; - } - /ip/smb/share/remove [ find where name=$Name ]; - :if ($Error = true) do={ - :return false; - } - } + :foreach Script in={ "global-config"; "global-functions" } do={ + /system/script/set name=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") output=user as-value]->"data"); + /system/script/run $Script; } - :return true; -} -# prepare NotificationFunctions array -:if ([ :typeof $NotificationFunctions ] != "array") do={ - :set NotificationFunctions ({}); -} - -# parse key value store -:set ParseKeyValueStore do={ - :local Source $1; - :if ([ :typeof $Source ] != "array") do={ - :set Source [ :tostr $1 ]; - } - :local Result ({}); - :foreach KeyValue in=[ :toarray $Source ] do={ - :if ([ :find $KeyValue "=" ]) do={ - :set ($Result->[ :pick $KeyValue 0 [ :find $KeyValue "=" ] ]) \ - [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ]; - } else={ - :set ($Result->$KeyValue) true; - } - } - :return $Result; -} - -# print lines with trailing carriage return -:set PrettyPrint do={ - :local Input [ :tostr $1 ]; - - :global Unix2Dos; - - :put [ $Unix2Dos $Input ]; -} - -# delay a random amount of seconds -:set RandomDelay do={ - :global EitherOr; - :global GetRandomNumber; - - :delay ([ $GetRandomNumber $1 ] . [ $EitherOr $2 "s" ]); -} - -# read input from user -:set Read do={ - :return; -} - -# check for required RouterOS version -:set RequiredRouterOS do={ - :local Caller [ :tostr $1 ]; - :local Required [ :tostr $2 ]; - :local Warn [ :tostr $3 ]; - - :global IfThenElse; - :global LogPrintExit2; - :global VersionToNum; - - :if (!($Required ~ "^\\d+\\.\\d+((beta|rc|\\.)\\d+|)\$")) do={ - $LogPrintExit2 error $0 ("No valid RouterOS version: " . $Required) false; - :return false; - } - - :if ([ $VersionToNum $Required ] > [ $VersionToNum [ /system/package/update/get installed-version ] ]) do={ - :if ($Warn = "true") do={ - $LogPrintExit2 warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \ - " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!") false; - } - :return false; - } - :return true; -} - -# check if script is run from terminal -:set ScriptFromTerminal do={ - :local Script [ :tostr $1 ]; - - :global LogPrintExit2; - - :foreach Job in=[ /system/script/job/find where script=$Script ] do={ - :set Job [ /system/script/job/get $Job ]; - :while ([ :typeof ($Job->"parent") ] = "id") do={ - :set Job [ /system/script/job/get [ find where .id=($Job->"parent") ] ]; - } - :if (($Job->"type") = "login") do={ - $LogPrintExit2 debug $0 ("Script " . $Script . " started from terminal.") false; - :return true; - } - } - $LogPrintExit2 debug $0 ("Script " . $Script . " NOT started from terminal.") false; - - :return false; + $ScriptInstallUpdate; } # install new scripts, update existing scripts @@ -779,7 +98,7 @@ :local UrlSuffix $ScriptUpdatesUrlSuffix; :if ([ :typeof ($Comment->"base-url") ] = "str") do={ :set BaseUrl ($Comment->"base-url"); } :if ([ :typeof ($Comment->"url-suffix") ] = "str") do={ :set UrlSuffix ($Comment->"url-suffix"); } - :local Url ($BaseUrl . $ScriptVal->"name" . $UrlSuffix); + :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix); $LogPrintExit2 debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url) false; :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; @@ -863,7 +182,7 @@ :local ChangeLogCode; :do { - :local Url ($ScriptUpdatesBaseUrl . "global-config.changes" . $ScriptUpdatesUrlSuffix); + :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix); $LogPrintExit2 debug $0 ("Fetching news, changes and migration: " . $Url) false; :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; :if ($Result->"status" = "finished") do={ @@ -940,353 +259,3 @@ :set GlobalConfigMigration; } } - -# lock script against multiple invocation -:set ScriptLock do={ - :local Script [ :tostr $1 ]; - :local DoReturn $2; - :local WaitMax ([ :tonum $3 ] * 10); - - :global GetRandom20CharAlNum; - :global IfThenElse; - :global LogPrintExit2; - - :global ScriptLockOrder; - :if ([ :typeof $ScriptLockOrder ] = "nothing") do={ - :set ScriptLockOrder ({}); - } - :if ([ :typeof ($ScriptLockOrder->$Script) ] = "nothing") do={ - :set ($ScriptLockOrder->$Script) ({}); - } - - :local JobCount do={ - :local Script [ :tostr $1 ]; - - :return [ :len [ /system/script/job/find where script=$Script ] ]; - } - - :local TicketCount do={ - :local Script [ :tostr $1 ]; - - :global ScriptLockOrder; - - :local Count 0; - :foreach Ticket in=($ScriptLockOrder->$Script) do={ - :if ([ :typeof $Ticket ] != "nothing") do={ - :set Count ($Count + 1); - } - } - :return $Count; - } - - :local IsFirstTicket do={ - :local Script [ :tostr $1 ]; - :local Check [ :tostr $2 ]; - - :global ScriptLockOrder; - - :foreach Ticket in=($ScriptLockOrder->$Script) do={ - :if ($Ticket = $Check) do={ :return true; } - :if ([ :typeof $Ticket ] != "nothing" && $Ticket != $Check) do={ :return false; } - } - :return false; - } - - :local AddTicket do={ - :local Script [ :tostr $1 ]; - :local Add [ :tostr $2 ]; - - :global ScriptLockOrder; - - :while (true) do={ - :local Pos [ :len ($ScriptLockOrder->$Script) ]; - :set ($ScriptLockOrder->$Script->$Pos) $Add; - :delay 10ms; - :if (($ScriptLockOrder->$Script->$Pos) = $Add) do={ :return true; } - } - } - - :local RemoveTicket do={ - :local Script [ :tostr $1 ]; - :local Remove [ :tostr $2 ]; - - :global ScriptLockOrder; - - :foreach Id,Ticket in=($ScriptLockOrder->$Script) do={ - :while (($ScriptLockOrder->$Script->$Id) = $Remove) do={ - :set ($ScriptLockOrder->$Script->$Id); - :delay 10ms; - } - } - } - - :local CleanupTickets do={ - :local Script [ :tostr $1 ]; - - :global ScriptLockOrder; - - :foreach Ticket in=($ScriptLockOrder->$Script) do={ - :if ([ :typeof $Ticket ] != "nothing") do={ - :return false; - } - } - - :set ($ScriptLockOrder->$Script) ({}); - } - - :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={ - $LogPrintExit2 error $0 ("A script named '" . $Script . "' does not exist!") true; - } - - :if ([ $JobCount $Script ] = 0) do={ - $LogPrintExit2 error $0 ("No script '" . $Script . "' is running!") true; - } - - :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={ - $LogPrintExit2 error $0 ("More tickets than running scripts '" . $Script . "', resetting!") false; - :set ($ScriptLockOrder->$Script) ({}); - /system/script/job/remove [ find where script=$Script ]; - } - - :local MyTicket [ $GetRandom20CharAlNum 6 ]; - $AddTicket $Script $MyTicket; - - :local WaitCount 0; - :while ($WaitMax > $WaitCount && ([ $IsFirstTicket $Script $MyTicket ] = false || [ $TicketCount $Script ] < [ $JobCount $Script ])) do={ - :set WaitCount ($WaitCount + 1); - :delay 100ms; - } - - :if ([ $IsFirstTicket $Script $MyTicket ] = true && [ $TicketCount $Script ] = [ $JobCount $Script ]) do={ - $RemoveTicket $Script $MyTicket; - $CleanupTickets $Script; - :return false; - } - - $RemoveTicket $Script $MyTicket; - $LogPrintExit2 info $0 ("Script '" . $Script . "' started more than once" . [ $IfThenElse ($WaitCount > 0) \ - " and timed out waiting for lock" "" ] . "... Aborting.") [ $IfThenElse ($DoReturn = true) false true ]; - :return true; -} - -# send notification via NotificationFunctions - expects at least two string arguments -:set SendNotification do={ - :global SendNotification2; - - $SendNotification2 ({ subject=$1; message=$2; link=$3; silent=$4 }); -} - -# send notification via NotificationFunctions - expects one array argument -:set SendNotification2 do={ - :local Notification $1; - - :global NotificationFunctions; - - :foreach FunctionName,Discard in=$NotificationFunctions do={ - ($NotificationFunctions->$FunctionName) \ - ("\$NotificationFunctions->\"" . $FunctionName . "\"") \ - $Notification; - } -} - -# return UTF-8 symbol for unicode name -:set SymbolByUnicodeName do={ - :local Symbols { - "abacus"="\F0\9F\A7\AE"; - "alarm-clock"="\E2\8F\B0"; - "calendar"="\F0\9F\93\85"; - "card-file-box"="\F0\9F\97\83"; - "chart-decreasing"="\F0\9F\93\89"; - "chart-increasing"="\F0\9F\93\88"; - "cloud"="\E2\98\81"; - "cross-mark"="\E2\9D\8C"; - "earth"="\F0\9F\8C\8D"; - "fire"="\F0\9F\94\A5"; - "floppy-disk"="\F0\9F\92\BE"; - "high-voltage-sign"="\E2\9A\A1"; - "incoming-envelope"="\F0\9F\93\A8"; - "link"="\F0\9F\94\97"; - "lock-with-ink-pen"="\F0\9F\94\8F"; - "memo"="\F0\9F\93\9D"; - "mobile-phone"="\F0\9F\93\B1"; - "pushpin"="\F0\9F\93\8C"; - "scissors"="\E2\9C\82"; - "sparkles"="\E2\9C\A8"; - "speech-balloon"="\F0\9F\92\AC"; - "up-arrow"="\E2\AC\86"; - "warning-sign"="\E2\9A\A0"; - "white-heavy-check-mark"="\E2\9C\85" - } - - :return (($Symbols->$1) . "\EF\B8\8F"); -} - -# return symbol for notification -:set SymbolForNotification do={ - :global NotificationsWithSymbols; - :global SymbolByUnicodeName; - - :if ($NotificationsWithSymbols != true) do={ - :return ""; - } - :local Return ""; - :foreach Symbol in=[ :toarray $1 ] do={ - :set Return ($Return . [ $SymbolByUnicodeName $Symbol ]); - } - :return ($Return . " "); -} - -# convert line endings, UNIX -> DOS -:set Unix2Dos do={ - :local Input [ :tostr $1 ]; - - :global CharacterReplace; - - :return [ $CharacterReplace [ $CharacterReplace $Input \ - ("\n") ("\r\n") ] ("\r\r\n") ("\r\n") ]; -} - -# url encoding -:set UrlEncode do={ - :local Input [ :tostr $1 ]; - - :if ([ :len $Input ] = 0) do={ - :return ""; - } - - :local Return ""; - :local Chars ("\n\r !\"#\$%&'()*+,:;<=>\?@[\\]^`{|}~"); - :local Subs { "%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27"; - "%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E"; "%3F"; - "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D"; "%7E" }; - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :local Replace [ :find $Chars $Char ]; - - :if ([ :typeof $Replace ] = "num") do={ - :set Char ($Subs->$Replace); - } - :set Return ($Return . $Char); - } - - :return $Return; -} - -# basic syntax validation -:set ValidateSyntax do={ - :local Code [ :tostr $1 ]; - - :do { - [ :parse (":local Validate do={\n" . $Code . "\n}") ]; - } on-error={ - :return false; - } - :return true; -} - -# convert version string to numeric value -:set VersionToNum do={ - :local Input [ :tostr $1 ]; - :local Multi 0x1000000; - :local Return 0; - - :global CharacterReplace; - - :set Input [ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $Input \ - "." "," ] "beta" ",beta," ] "rc" ",rc," ]; - - :foreach Value in=([ :toarray $Input ], 0) do={ - :local Num [ :tonum $Value ]; - :if ($Multi = 0x100) do={ - :if ([ :typeof $Num ] = "num") do={ - :set Return ($Return + 0xff00); - :set Multi ($Multi / 0x100); - } else={ - :if ($Value = "beta") do={ :set Return ($Return + 0x3f00); } - :if ($Value = "rc") do={ :set Return ($Return + 0x7f00); } - } - } - :if ([ :typeof $Num ] = "num") do={ :set Return ($Return + ($Value * $Multi)); } - :set Multi ($Multi / 0x100); - } - - :return $Return; -} - -# wait for default route to be reachable -:set WaitDefaultRouteReachable do={ - :global IsDefaultRouteReachable; - - :while ([ $IsDefaultRouteReachable ] = false) do={ - :delay 1s; - } -} - -# wait for DNS to resolve -:set WaitDNSResolving do={ - :global IsDNSResolving; - - :while ([ $IsDNSResolving ] = false) do={ - :delay 1s; - } -} - -# wait for file to be available -:set WaitForFile do={ - :local FileName [ :tostr $1 ]; - :local WaitTime [ :totime $2 ]; - - :global CleanFilePath; - :global EitherOr; - - :set FileName [ $CleanFilePath $FileName ]; - :local I 1; - :local Delay ([ :totime [ $EitherOr $WaitTime 2s ] ] / 20); - - :while ([ :len [ /file/find where name=$FileName ] ] = 0) do={ - :if ($I >= 20) do={ - :return false; - } - :delay $Delay; - :set I ($I + 1); - } - :return true; -} - -# wait to be fully connected (default route is reachable, time is sync, DNS resolves) -:set WaitFullyConnected do={ - :global WaitDefaultRouteReachable; - :global WaitDNSResolving; - :global WaitTimeSync; - - $WaitDefaultRouteReachable; - $WaitTimeSync; - $WaitDNSResolving; -} - -# wait for time to become synced -:set WaitTimeSync do={ - :global IsTimeSync; - - :while ([ $IsTimeSync ] = false) do={ - :delay 1s; - } -} - -# load modules -:foreach Script in=[ /system/script/find where name ~ "^mod/." ] do={ - :local ScriptVal [ /system/script/get $Script ]; - :if ([ $ValidateSyntax ($ScriptVal->"source") ] = true) do={ - :do { - /system/script/run $Script; - } on-error={ - $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed to run.") false; - } - } else={ - $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed syntax validation, skipping.") false; - } -} - -# signal we are ready -:set GlobalFunctionsReady true; diff --git a/global-functions.rsc b/global-functions.rsc new file mode 100644 index 0000000..6f3bb86 --- /dev/null +++ b/global-functions.rsc @@ -0,0 +1,1292 @@ +#!rsc by RouterOS +# RouterOS script: global-functions +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# requires RouterOS, version=7.7 +# +# global functions +# https://git.eworm.de/cgit/routeros-scripts/about/ + +:local 0 "global-functions"; + +# expected configuration version +:global ExpectedConfigVersion 95; + +# global variables not to be changed by user +:global GlobalFunctionsReady false; +:global Identity [ /system/identity/get name ]; + +# global functions +:global CertificateAvailable; +:global CertificateDownload; +:global CertificateNameByCN; +:global CharacterReplace; +:global CleanFilePath; +:global DeviceInfo; +:global Dos2Unix; +:global DownloadPackage; +:global EitherOr; +:global EscapeForRegEx; +:global GetMacVendor; +:global GetRandom20CharAlNum; +:global GetRandom20CharHex; +:global GetRandomNumber; +:global Grep; +:global HexToNum; +:global IfThenElse; +:global IsDefaultRouteReachable; +:global IsDNSResolving; +:global IsFullyConnected; +:global IsMacLocallyAdministered; +:global IsTimeSync; +:global LogPrintExit2; +:global MkDir; +:global NotificationFunctions; +:global ParseKeyValueStore; +:global PrettyPrint; +:global RandomDelay; +:global Read; +:global RequiredRouterOS; +:global ScriptFromTerminal; +:global ScriptInstallUpdate; +:global ScriptLock; +:global SendNotification; +:global SendNotification2; +:global SymbolByUnicodeName; +:global SymbolForNotification; +:global Unix2Dos; +:global UrlEncode; +:global ValidateSyntax; +:global VersionToNum; +:global WaitDefaultRouteReachable; +:global WaitDNSResolving; +:global WaitForFile; +:global WaitFullyConnected; +:global WaitTimeSync; + +# check and download required certificate +:set CertificateAvailable do={ + :local CommonName [ :tostr $1 ]; + + :global CertificateDownload; + :global LogPrintExit2; + :global ParseKeyValueStore; + + :if ([ /system/resource/get free-hdd-space ] < 8388608 && \ + [ /certificate/settings/get crl-download ] = true && \ + [ /certificate/settings/get crl-store ] = "system") do={ + $LogPrintExit2 warning $0 ("This system has low free flash space but " . \ + "is configured to download certificate CRLs to system!") false; + } + + :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={ + $LogPrintExit2 info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available.") false; + :if ([ $CertificateDownload $CommonName ] = false) do={ + :return false; + } + } + + :local CertVal [ /certificate/get [ find where common-name=$CommonName ] ]; + :while (($CertVal->"akid") != "" && ($CertVal->"akid") != ($CertVal->"skid")) do={ + :if ([ :len [ /certificate/find where skid=($CertVal->"akid") ] ] = 0) do={ + $LogPrintExit2 info $0 ("Certificate chain for \"" . $CommonName . \ + "\" is incomplete, missing \"" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\".") false; + :if ([ $CertificateDownload $CommonName ] = false) do={ + :return false; + } + } + :set CertVal [ /certificate/get [ find where skid=($CertVal->"akid") ] ]; + } + :return true; +} + +# download and import certificate +:set CertificateDownload do={ + :local CommonName [ :tostr $1 ]; + + :global ScriptUpdatesBaseUrl; + :global ScriptUpdatesUrlSuffix; + + :global CertificateNameByCN; + :global LogPrintExit2; + :global UrlEncode; + :global WaitForFile; + + $LogPrintExit2 info $0 ("Downloading and importing certificate with " . \ + "CommonName \"" . $CommonName . "\".") false; + :do { + :local LocalFileName ($CommonName . ".pem"); + :local UrlFileName ([ $UrlEncode $CommonName ] . ".pem"); + /tool/fetch check-certificate=yes-without-crl \ + ($ScriptUpdatesBaseUrl . "certs/" . \ + $UrlFileName . $ScriptUpdatesUrlSuffix) \ + dst-path=$LocalFileName as-value; + $WaitForFile $LocalFileName; + /certificate/import file-name=$LocalFileName passphrase="" as-value; + /file/remove $LocalFileName; + + :foreach Cert in=[ /certificate/find where name~("^" . $LocalFileName . "_[0-9]+\$") ] do={ + $CertificateNameByCN [ /certificate/get $Cert common-name ]; + } + } on-error={ + $LogPrintExit2 warning $0 ("Failed importing certificate with " . \ + "CommonName \"" . $CommonName . "\"!") false; + :return false; + } + :return true; +} + +# name a certificate by its common-name +:set CertificateNameByCN do={ + :local CommonName [ :tostr $1 ]; + + :global CharacterReplace; + + :local Cert [ /certificate/find where common-name=$CommonName ]; + /certificate/set $Cert \ + name=[ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $CommonName "'" "-" ] " " "-" ] "---" "-" ]; +} + +# character replace +:set CharacterReplace do={ + :local String [ :tostr $1 ]; + :local ReplaceFrom [ :tostr $2 ]; + :local ReplaceWith [ :tostr $3 ]; + :local Return ""; + + :if ($ReplaceFrom = "") do={ + :return $String; + } + + :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={ + :local Pos [ :find $String $ReplaceFrom ]; + :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith); + :set String [ :pick $String ($Pos + [ :len $ReplaceFrom ]) [ :len $String ] ]; + } + + :return ($Return . $String); +} + +# clean file path +:set CleanFilePath do={ + :local Path [ :tostr $1 ]; + + :global CharacterReplace; + + :while ($Path ~ "//") do={ + :set $Path [ $CharacterReplace $Path "//" "/" ]; + } + :if ([ :pick $Path 0 ] = "/") do={ + :set Path [ :pick $Path 1 [ :len $Path ] ]; + } + :if ([ :pick $Path ([ :len $Path ] - 1) ] = "/") do={ + :set Path [ :pick $Path 0 ([ :len $Path ] - 1) ]; + } + + :return $Path; +} + +# get readable device info +:set DeviceInfo do={ + :global ExpectedConfigVersion; + :global Identity; + + :global IfThenElse; + + :local Resource [ /system/resource/get ]; + :local RouterBoard; + :do { + :set RouterBoard [[ :parse "/system/routerboard/get" ]]; + } on-error={ } + :local License [ /system/license/get ]; + :local Update [ /system/package/update/get ]; + + :return ( \ + "Hostname: " . $Identity . \ + "\nBoard name: " . $Resource->"board-name" . \ + "\nArchitecture: " . $Resource->"architecture-name" . \ + [ $IfThenElse ($RouterBoard->"routerboard" = true) \ + ("\nModel: " . $RouterBoard->"model" . \ + [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \ + (" " . $RouterBoard->"revision") ] . \ + "\nSerial number: " . $RouterBoard->"serial-number") ] . \ + [ $IfThenElse ([ :len ($License->"level") ] > 0) \ + ("\nLicense: " . $License->"level") ] . \ + "\nRouterOS:" . \ + "\n Channel: " . $Update->"channel" . \ + "\n Installed: " . $Update->"installed-version" . \ + [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \ + $Update->"installed-version" != $Update->"latest-version") \ + ("\n Available: " . $Update->"latest-version") ] . \ + [ $IfThenElse ($RouterBoard->"routerboard" = true && \ + $RouterBoard->"current-firmware" != $RouterBoard->"upgrade-firmware") \ + ("\n Firmware: " . $RouterBoard->"current-firmware") ] . \ + "\nRouterOS-Scripts:" . \ + "\n Version: " . $ExpectedConfigVersion); +} + +# convert line endings, DOS -> UNIX +:set Dos2Unix do={ + :local Input [ :tostr $1 ]; + + :global CharacterReplace; + + :return [ $CharacterReplace $Input ("\r\n") ("\n") ]; +} + +# download package from upgrade server +:set DownloadPackage do={ + :local PkgName [ :tostr $1 ]; + :local PkgVer [ :tostr $2 ]; + :local PkgArch [ :tostr $3 ]; + :local PkgDir [ :tostr $4 ]; + + :global CertificateAvailable; + :global CleanFilePath; + :global LogPrintExit2; + :global MkDir; + :global WaitForFile; + + :if ([ :len $PkgName ] = 0) do={ :return false; } + :if ([ :len $PkgVer ] = 0) do={ :set PkgVer [ /system/package/update/get installed-version ]; } + :if ([ :len $PkgArch ] = 0) do={ :set PkgArch [ /system/resource/get architecture-name ]; } + + :if ($PkgName = "system") do={ :set PkgName "routeros"; } + + :local PkgFile ($PkgName . "-" . $PkgVer . "-" . $PkgArch . ".npk"); + :if ($PkgArch = "x86_64") do={ :set PkgFile ($PkgName . "-" . $PkgVer . ".npk"); } + :local PkgDest [ $CleanFilePath ($PkgDir . "/" . $PkgFile) ]; + + :if ([ $MkDir $PkgDir ] = false) do={ + $LogPrintExit2 warning $0 ("Failed creating directory, not downloading package.") false; + :return false; + } + + :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={ + $LogPrintExit2 info $0 ("Package file " . $PkgName . " already exists.") false; + :return true; + } + + :if ([ $CertificateAvailable "R3" ] = false) do={ + $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; + } + + :local Url ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile); + $LogPrintExit2 info $0 ("Downloading package file '" . $PkgName . "'...") false; + $LogPrintExit2 debug $0 ("... from url: " . $Url) false; + :local Retry 3; + :while ($Retry > 0) do={ + :do { + /tool/fetch check-certificate=yes-without-crl $Url dst-path=$PkgDest; + $WaitForFile $PkgDest; + + :if ([ /file/get [ find where name=$PkgDest ] type ] = "package") do={ + :return true; + } + } on-error={ + $LogPrintExit2 debug $0 ("Downloading package file failed.") false; + } + + /file/remove [ find where name=$PkgDest ]; + :set Retry ($Retry - 1); + } + + $LogPrintExit2 warning $0 ("Downloading package file '" . $PkgName . "' failed.") false; + :return false; +} + +# return either first (if "true") or second +:set EitherOr do={ + :global IfThenElse; + + :if ([ :typeof $1 ] = "num") do={ + :return [ $IfThenElse ($1 != 0) $1 $2 ]; + } + :return [ $IfThenElse ([ :len [ :tostr $1 ] ] > 0) $1 $2 ]; +} + +# escape for regular expression +:set EscapeForRegEx do={ + :local Input [ :tostr $1 ]; + + :if ([ :len $Input ] = 0) do={ + :return ""; + } + + :local Return ""; + :local Chars ("^.[]\$()|*+\?{}\\"); + + :for I from=0 to=([ :len $Input ] - 1) do={ + :local Char [ :pick $Input $I ]; + :if ([ :find $Chars $Char ]) do={ + :set Char ("\\" . $Char); + } + :set Return ($Return . $Char); + } + + :return $Return; +} + +# get MAC vendor +:set GetMacVendor do={ + :local Mac [ :tostr $1 ]; + + :global CertificateAvailable; + :global IsMacLocallyAdministered; + :global LogPrintExit2; + + :if ([ $IsMacLocallyAdministered $Mac ] = true) do={ + :return "locally administered"; + } + + :do { + :if ([ $CertificateAvailable "R3" ] = false) do={ + $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; + } + :local Vendor ([ /tool/fetch check-certificate=yes-without-crl \ + ("https://api.macvendors.com/" . [ :pick $Mac 0 8 ]) output=user as-value ]->"data"); + :return $Vendor; + } on-error={ + :do { + /tool/fetch check-certificate=yes-without-crl ("https://api.macvendors.com/") \ + output=none as-value; + $LogPrintExit2 debug $0 ("The mac vendor is not known in database.") false; + } on-error={ + $LogPrintExit2 warning $0 ("Failed getting mac vendor.") false; + } + :return "unknown vendor"; + } +} + +# generate random 20 chars alphabetical (A-Z & a-z) and numerical (0-9) +:set GetRandom20CharAlNum do={ + :global EitherOr; + + :return [ :rndstr length=[ $EitherOr [ :tonum $1 ] 20 ] from="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ]; +} + +# generate random 20 chars hex (0-9 and a-f) +:set GetRandom20CharHex do={ + :global EitherOr; + + :return [ :rndstr length=[ $EitherOr [ :tonum $1 ] 20 ] from="0123456789abcdef" ]; +} + +# generate random number +:set GetRandomNumber do={ + :global EitherOr; + + :return [ :rndnum from=0 to=[ $EitherOr [ :tonum $1 ] 4294967295 ] ]; +} + +# return first line that matches a pattern +:set Grep do={ + :local Input ([ :tostr $1 ] . "\n"); + :local Pattern [ :tostr $2 ]; + + :if ([ :typeof [ :find $Input $Pattern ] ] = "nil") do={ + :return []; + } + + :do { + :local Line [ :pick $Input 0 [ :find $Input "\n" ] ]; + :if ([ :typeof [ :find $Line $Pattern ] ] = "num") do={ + :return $Line; + } + :set Input [ :pick $Input ([ :find $Input "\n" ] + 1) [ :len $Input ] ]; + } while=([ :len $Input ] > 0); + + :return []; +} + +# convert from hex (string) to num +:set HexToNum do={ + :local Input [ :tostr $1 ]; + :local Hex "0123456789abcdef0123456789ABCDEF"; + :local Multi 1; + :local Return 0; + + :for I from=([ :len $Input ] - 1) to=0 do={ + :set Return ($Return + (([ :find $Hex [ :pick $Input $I ] ] % 16) * $Multi)); + :set Multi ($Multi * 16); + } + + :return $Return; +} + +# mimic conditional/ternary operator (condition ? consequent : alternative) +:set IfThenElse do={ + :if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={ + :return $2; + } + :return $3; +} + +# check if default route is reachable +:set IsDefaultRouteReachable do={ + :if ([ :len [ /ip/route/find where dst-address=0.0.0.0/0 active routing-table=main ] ] > 0) do={ + :return true; + } + :return false; +} + +# check if DNS is resolving +:set IsDNSResolving do={ + :global CharacterReplace; + + :do { + :resolve "low-ttl.eworm.de"; + } on-error={ + :return false; + } + :return true; +} + +# check if system is is fully connected (default route reachable, DNS resolving, time sync) +:set IsFullyConnected do={ + :global IsDefaultRouteReachable; + :global IsDNSResolving; + :global IsTimeSync; + + :if ([ $IsDefaultRouteReachable ] = false) do={ + :return false; + } + :if ([ $IsDNSResolving ] = false) do={ + :return false; + } + :if ([ $IsTimeSync ] = false) do={ + :return false; + } + :return true; +} + +# check if mac address is locally administered +:set IsMacLocallyAdministered do={ + :if ([ :tonum ("0x" . [ :pick $1 0 [ :find $1 ":" ] ]) ] & 2 = 2) do={ + :return true; + } + :return false; +} + +# check if system time is sync +:set IsTimeSync do={ + :global IsTimeSyncCached; + + :global LogPrintExit2; + + :if ($IsTimeSyncCached = true) do={ + :return true; + } + + :if ([ /system/ntp/client/get enabled ] = true) do={ + :if ([ /system/ntp/client/get status ] = "synchronized") do={ + :set IsTimeSyncCached true; + :return true; + } + :return false; + } + + :if ([ /system/license/get ]->"level" = "free" || \ + [ /system/resource/get ]->"board-name" = "x86") do={ + $LogPrintExit2 debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86.") false; + :return true; + } + + :if ([ /ip/cloud/get update-time ] = true) do={ + :if ([ :typeof [ /ip/cloud/get public-address ] ] = "ip") do={ + :set IsTimeSyncCached true; + :return true; + } + :return false; + } + + $LogPrintExit2 debug $0 ("No time source configured! Returning gracefully...") false; + :return true; +} + +# log and print with same text, optionally exit +:set LogPrintExit2 do={ + :local Severity [ :tostr $1 ]; + :local Name [ :tostr $2 ]; + :local Message [ :tostr $3 ]; + :local Exit [ :tostr $4 ]; + + :global PrintDebug; + :global PrintDebugOverride; + + :global EitherOr; + + :local Debug [ $EitherOr ($PrintDebugOverride->$Name) $PrintDebug ]; + + :local PrintSeverity do={ + :global TerminalColorOutput; + + :if ($TerminalColorOutput != true) do={ + :return $1; + } + + :local Color { debug=96; info=97; warning=93; error=91 }; + :return ("\1B[" . $Color->$1 . "m" . $1 . "\1B[0m"); + } + + :local Log ([ $EitherOr $Name "<unknown>" ] . ": " . $Message); + :if ($Severity ~ ("^(debug|error|info)\$")) do={ + :if ($Severity = "debug") do={ :log debug $Log; } + :if ($Severity = "error") do={ :log error $Log; } + :if ($Severity = "info" ) do={ :log info $Log; } + } else={ + :log warning $Log; + :set Severity "warning"; + } + + :if ($Severity != "debug" || $Debug = true) do={ + :put ([ $PrintSeverity $Severity ] . ": " . $Message); + } + + :if ($Exit = "true") do={ + :error ("Hard error to exit."); + } +} + +# create directory +:set MkDir do={ + :local Path [ :tostr $1 ]; + + :global CharacterReplace; + :global CleanFilePath; + :global GetRandom20CharAlNum; + :global LogPrintExit2; + :global RequiredRouterOS; + :global WaitForFile; + + :set Path [ $CleanFilePath $Path ]; + + :if ($Path = "") do={ + :return true; + } + + :if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={ + :return true; + } + + :local Error false; + :local PathNext ""; + :foreach Dir in=[ :toarray [ $CharacterReplace $Path "/" "," ] ] do={ + :local Continue false; + :set PathNext [ $CleanFilePath ($PathNext . "/" . $Dir) ]; + + :if ([ :len [ /file/find where name=$PathNext !(name="tmpfs") type="directory" ] ] = 1) do={ + :set Continue true; + } + + :if ($Continue = false && $PathNext = "tmpfs") do={ + :if ([ :len [ /disk/find where slot=tmpfs type=tmpfs ] ] = 0) do={ + $LogPrintExit2 info $0 ("Creating disk of type tmpfs.") false; + /file/remove [ find where name="tmpfs" type="directory" ]; + :do { + /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource/get total-memory ] / 3); + $WaitForFile "tmpfs"; + } on-error={ + $LogPrintExit2 warning $0 ("Creating disk of type tmpfs failed!") false; + :set Error true; + } + } + :set Continue true; + } + + :if ($Continue = false && [ :len [ /file/find where name=$PathNext ] ] = 1) do={ + $LogPrintExit2 warning $0 ("The path '" . $PathNext . "' exists, but is not a directory.") false; + :return false; + } + + :if ($Continue = false) do={ + :local Name ($PathNext . "-" . [ $GetRandom20CharAlNum 6 ]); + :do { + /ip/smb/share/add disabled=yes directory=$PathNext name=$Name; + $WaitForFile $PathNext; + } on-error={ + $LogPrintExit2 warning $0 ("Making directory '" . $PathNext . "' failed!") false; + :set Error true; + } + /ip/smb/share/remove [ find where name=$Name ]; + :if ($Error = true) do={ + :return false; + } + } + } + :return true; +} + +# prepare NotificationFunctions array +:if ([ :typeof $NotificationFunctions ] != "array") do={ + :set NotificationFunctions ({}); +} + +# parse key value store +:set ParseKeyValueStore do={ + :local Source $1; + :if ([ :typeof $Source ] != "array") do={ + :set Source [ :tostr $1 ]; + } + :local Result ({}); + :foreach KeyValue in=[ :toarray $Source ] do={ + :if ([ :find $KeyValue "=" ]) do={ + :set ($Result->[ :pick $KeyValue 0 [ :find $KeyValue "=" ] ]) \ + [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ]; + } else={ + :set ($Result->$KeyValue) true; + } + } + :return $Result; +} + +# print lines with trailing carriage return +:set PrettyPrint do={ + :local Input [ :tostr $1 ]; + + :global Unix2Dos; + + :put [ $Unix2Dos $Input ]; +} + +# delay a random amount of seconds +:set RandomDelay do={ + :global EitherOr; + :global GetRandomNumber; + + :delay ([ $GetRandomNumber $1 ] . [ $EitherOr $2 "s" ]); +} + +# read input from user +:set Read do={ + :return; +} + +# check for required RouterOS version +:set RequiredRouterOS do={ + :local Caller [ :tostr $1 ]; + :local Required [ :tostr $2 ]; + :local Warn [ :tostr $3 ]; + + :global IfThenElse; + :global LogPrintExit2; + :global VersionToNum; + + :if (!($Required ~ "^\\d+\\.\\d+((beta|rc|\\.)\\d+|)\$")) do={ + $LogPrintExit2 error $0 ("No valid RouterOS version: " . $Required) false; + :return false; + } + + :if ([ $VersionToNum $Required ] > [ $VersionToNum [ /system/package/update/get installed-version ] ]) do={ + :if ($Warn = "true") do={ + $LogPrintExit2 warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \ + " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!") false; + } + :return false; + } + :return true; +} + +# check if script is run from terminal +:set ScriptFromTerminal do={ + :local Script [ :tostr $1 ]; + + :global LogPrintExit2; + + :foreach Job in=[ /system/script/job/find where script=$Script ] do={ + :set Job [ /system/script/job/get $Job ]; + :while ([ :typeof ($Job->"parent") ] = "id") do={ + :set Job [ /system/script/job/get [ find where .id=($Job->"parent") ] ]; + } + :if (($Job->"type") = "login") do={ + $LogPrintExit2 debug $0 ("Script " . $Script . " started from terminal.") false; + :return true; + } + } + $LogPrintExit2 debug $0 ("Script " . $Script . " NOT started from terminal.") false; + + :return false; +} + +# install new scripts, update existing scripts +:set ScriptInstallUpdate do={ + :local Scripts [ :toarray $1 ]; + :local NewComment [ :tostr $2 ]; + + :global ExpectedConfigVersion; + :global Identity; + :global IDonate; + :global NoNewsAndChangesNotification; + :global NotificationsWithSymbols; + :global ScriptUpdatesBaseUrl; + :global ScriptUpdatesFetch; + :global ScriptUpdatesUrlSuffix; + + :global CertificateAvailable; + :global EitherOr; + :global Grep; + :global IfThenElse; + :global LogPrintExit2; + :global ParseKeyValueStore; + :global RequiredRouterOS; + :global SendNotification2; + :global SymbolForNotification; + :global ValidateSyntax; + + :if ([ $CertificateAvailable "R3" ] = false) do={ + $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; + } + + :if ([ $CertificateAvailable "E1" ] = false) do={ + $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; + } + + :foreach Script in=$Scripts do={ + :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={ + $LogPrintExit2 info $0 ("Adding new script: " . $Script) false; + /system/script/add name=$Script owner=$Script source="#!rsc by RouterOS\n" comment=$NewComment; + } + } + + :local ExpectedConfigVersionBefore $ExpectedConfigVersion; + :local ReloadGlobalFunctions false; + :local ReloadGlobalConfig false; + + :foreach Script in=[ /system/script/find where source~"^#!rsc by RouterOS\n" ] do={ + :local ScriptVal [ /system/script/get $Script ]; + :local ScriptFile [ /file/find where name=("script-updates/" . $ScriptVal->"name") . ".rsc" ]; + :local SourceNew; + :if ([ :len $ScriptFile ] > 0) do={ + :set SourceNew [ /file/get $ScriptFile contents ]; + /file/remove $ScriptFile; + } + + :foreach Scheduler in=[ /system/scheduler/find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={ + :local SchedulerVal [ /system/scheduler/get $Scheduler ]; + :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={ + $LogPrintExit2 warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \ + "' and its scheduler '" . $SchedulerVal->"name" . "'!") false; + } + } + + :if ([ :len $SourceNew ] = 0 && $ScriptUpdatesFetch = true) do={ + :local Comment [ $ParseKeyValueStore ($ScriptVal->"comment") ]; + :if (!($Comment->"ignore" = true)) do={ + :do { + :local BaseUrl $ScriptUpdatesBaseUrl; + :local UrlSuffix $ScriptUpdatesUrlSuffix; + :if ([ :typeof ($Comment->"base-url") ] = "str") do={ :set BaseUrl ($Comment->"base-url"); } + :if ([ :typeof ($Comment->"url-suffix") ] = "str") do={ :set UrlSuffix ($Comment->"url-suffix"); } + :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix); + + $LogPrintExit2 debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url) false; + :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; + :if ($Result->"status" = "finished") do={ + :set SourceNew ($Result->"data"); + } + } on-error={ + :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={ + $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . \ + "', removing dummy. Typo on installation?") false; + /system/script/remove $Script; + } else={ + $LogPrintExit2 warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!") false; + } + } + } + } + + :if ([ :len $SourceNew ] > 0) do={ + :if ($SourceNew != $ScriptVal->"source") do={ + :if ([ :pick $SourceNew 0 18 ] = "#!rsc by RouterOS\n") do={ + :local Required ([ $ParseKeyValueStore [ $Grep $SourceNew "# requires RouterOS, " ] ]->"version"); + :if ([ $RequiredRouterOS $0 [ $EitherOr $Required "0.0" ] false ] = true) do={ + :if ([ $ValidateSyntax $SourceNew ] = true) do={ + $LogPrintExit2 info $0 ("Updating script: " . $ScriptVal->"name") false; + /system/script/set owner=($ScriptVal->"name") source=$SourceNew $Script; + :if ($ScriptVal->"name" = "global-config") do={ + :set ReloadGlobalConfig true; + } + :if ($ScriptVal->"name" = "global-functions" || $ScriptVal->"name" ~ ("^mod/.")) do={ + :set ReloadGlobalFunctions true; + } + } else={ + $LogPrintExit2 warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . \ + "' failed! Ignoring!") false; + } + } else={ + $LogPrintExit2 warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \ + $Required . ", which is not met by your installation. Ignoring!") false; + } + } else={ + $LogPrintExit2 warning $0 ("Looks like new script '" . $ScriptVal->"name" . \ + "' is not valid (missing shebang). Ignoring!") false; + } + } else={ + $LogPrintExit2 debug $0 ("Script '" . $ScriptVal->"name" . "' did not change.") false; + } + } else={ + $LogPrintExit2 debug $0 ("No update for script '" . $ScriptVal->"name" . "'.") false; + } + } + + :if ($ReloadGlobalFunctions = true) do={ + $LogPrintExit2 info $0 ("Reloading global functions.") false; + :do { + /system/script/run global-functions; + } on-error={ + $LogPrintExit2 error $0 ("Reloading global functions failed!") false; + } + } + + :if ($ReloadGlobalConfig = true) do={ + $LogPrintExit2 info $0 ("Reloading global configuration.") false; + :do { + /system/script/run global-config; + } on-error={ + $LogPrintExit2 error $0 ("Reloading global configuration failed!" . \ + " Syntax error or missing overlay\?") false; + } + } + + :if ($ExpectedConfigVersionBefore > $ExpectedConfigVersion) do={ + $LogPrintExit2 warning $0 ("The configuration version decreased from " . \ + $ExpectedConfigVersionBefore . " to " . $ExpectedConfigVersion . \ + ". Installed an older version?") false; + } + + :if ($ExpectedConfigVersionBefore < $ExpectedConfigVersion) do={ + :global GlobalConfigChanges; + :global GlobalConfigMigration; + :local ChangeLogCode; + + :do { + :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix); + $LogPrintExit2 debug $0 ("Fetching news, changes and migration: " . $Url) false; + :local Result [ /tool/fetch check-certificate=yes-without-crl $Url output=user as-value ]; + :if ($Result->"status" = "finished") do={ + :set ChangeLogCode ($Result->"data"); + } + } on-error={ + $LogPrintExit2 warning $0 ("Failed fetching news, changes and migration!") false; + } + + :if ([ :len $ChangeLogCode ] > 0) do={ + :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={ + :do { + [ :parse $ChangeLogCode ]; + } on-error={ + $LogPrintExit2 warning $0 ("The changelog failed to run!") false; + } + } else={ + $LogPrintExit2 warning $0 ("The changelog failed syntax validation!") false; + } + } + + :if ([ :len $GlobalConfigMigration ] > 0) do={ + :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={ + :local Migration ($GlobalConfigMigration->[ :tostr $I ]); + :if ([ :typeof $Migration ] = "str") do={ + :if ([ $ValidateSyntax $Migration ] = true) do={ + $LogPrintExit2 info $0 ("Applying migration for change " . $I . ": " . $Migration) false; + :do { + [ :parse $Migration ]; + } on-error={ + $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed to run!") false; + } + } else={ + $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed syntax validation!") false; + } + } + } + } + + :local NotificationMessage ("The configuration version on " . $Identity . " increased " . \ + "to " . $ExpectedConfigVersion . ", current configuration may need modification. " . \ + "Please review and update global-config-overlay, then re-run global-config."); + $LogPrintExit2 info $0 ($NotificationMessage) false; + + :if ([ :len $GlobalConfigChanges ] > 0) do={ + :set NotificationMessage ($NotificationMessage . "\n\nChanges:"); + :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={ + :local Change ($GlobalConfigChanges->[ :tostr $I ]); + :set NotificationMessage ($NotificationMessage . "\n " . \ + [ $IfThenElse ($NotificationsWithSymbols = true) ("\E2\97\8F") "*" ] . " " . $Change); + $LogPrintExit2 info $0 ("Change " . $I . ": " . $Change) false; + } + } else={ + :set NotificationMessage ($NotificationMessage . "\n\nNews and changes are not available."); + } + + :if ($NoNewsAndChangesNotification != true) do={ + :local Link; + :if ($IDonate != true) do={ + :set NotificationMessage ($NotificationMessage . \ + "\n\n==== donation hint ====\n" . \ + "This project is developed in private spare time and usage is " . \ + "free of charge for you. If you like the scripts and think this is " . \ + "of value for you or your business please consider a donation."); + :set Link "https://git.eworm.de/cgit/routeros-scripts/about/#donate"; + } + + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "pushpin" ] . "News and configuration changes"); \ + message=$NotificationMessage; link=$Link }); + } + + :set GlobalConfigChanges; + :set GlobalConfigMigration; + } +} + +# lock script against multiple invocation +:set ScriptLock do={ + :local Script [ :tostr $1 ]; + :local DoReturn $2; + :local WaitMax ([ :tonum $3 ] * 10); + + :global GetRandom20CharAlNum; + :global IfThenElse; + :global LogPrintExit2; + + :global ScriptLockOrder; + :if ([ :typeof $ScriptLockOrder ] = "nothing") do={ + :set ScriptLockOrder ({}); + } + :if ([ :typeof ($ScriptLockOrder->$Script) ] = "nothing") do={ + :set ($ScriptLockOrder->$Script) ({}); + } + + :local JobCount do={ + :local Script [ :tostr $1 ]; + + :return [ :len [ /system/script/job/find where script=$Script ] ]; + } + + :local TicketCount do={ + :local Script [ :tostr $1 ]; + + :global ScriptLockOrder; + + :local Count 0; + :foreach Ticket in=($ScriptLockOrder->$Script) do={ + :if ([ :typeof $Ticket ] != "nothing") do={ + :set Count ($Count + 1); + } + } + :return $Count; + } + + :local IsFirstTicket do={ + :local Script [ :tostr $1 ]; + :local Check [ :tostr $2 ]; + + :global ScriptLockOrder; + + :foreach Ticket in=($ScriptLockOrder->$Script) do={ + :if ($Ticket = $Check) do={ :return true; } + :if ([ :typeof $Ticket ] != "nothing" && $Ticket != $Check) do={ :return false; } + } + :return false; + } + + :local AddTicket do={ + :local Script [ :tostr $1 ]; + :local Add [ :tostr $2 ]; + + :global ScriptLockOrder; + + :while (true) do={ + :local Pos [ :len ($ScriptLockOrder->$Script) ]; + :set ($ScriptLockOrder->$Script->$Pos) $Add; + :delay 10ms; + :if (($ScriptLockOrder->$Script->$Pos) = $Add) do={ :return true; } + } + } + + :local RemoveTicket do={ + :local Script [ :tostr $1 ]; + :local Remove [ :tostr $2 ]; + + :global ScriptLockOrder; + + :foreach Id,Ticket in=($ScriptLockOrder->$Script) do={ + :while (($ScriptLockOrder->$Script->$Id) = $Remove) do={ + :set ($ScriptLockOrder->$Script->$Id); + :delay 10ms; + } + } + } + + :local CleanupTickets do={ + :local Script [ :tostr $1 ]; + + :global ScriptLockOrder; + + :foreach Ticket in=($ScriptLockOrder->$Script) do={ + :if ([ :typeof $Ticket ] != "nothing") do={ + :return false; + } + } + + :set ($ScriptLockOrder->$Script) ({}); + } + + :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={ + $LogPrintExit2 error $0 ("A script named '" . $Script . "' does not exist!") true; + } + + :if ([ $JobCount $Script ] = 0) do={ + $LogPrintExit2 error $0 ("No script '" . $Script . "' is running!") true; + } + + :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={ + $LogPrintExit2 error $0 ("More tickets than running scripts '" . $Script . "', resetting!") false; + :set ($ScriptLockOrder->$Script) ({}); + /system/script/job/remove [ find where script=$Script ]; + } + + :local MyTicket [ $GetRandom20CharAlNum 6 ]; + $AddTicket $Script $MyTicket; + + :local WaitCount 0; + :while ($WaitMax > $WaitCount && ([ $IsFirstTicket $Script $MyTicket ] = false || [ $TicketCount $Script ] < [ $JobCount $Script ])) do={ + :set WaitCount ($WaitCount + 1); + :delay 100ms; + } + + :if ([ $IsFirstTicket $Script $MyTicket ] = true && [ $TicketCount $Script ] = [ $JobCount $Script ]) do={ + $RemoveTicket $Script $MyTicket; + $CleanupTickets $Script; + :return false; + } + + $RemoveTicket $Script $MyTicket; + $LogPrintExit2 info $0 ("Script '" . $Script . "' started more than once" . [ $IfThenElse ($WaitCount > 0) \ + " and timed out waiting for lock" "" ] . "... Aborting.") [ $IfThenElse ($DoReturn = true) false true ]; + :return true; +} + +# send notification via NotificationFunctions - expects at least two string arguments +:set SendNotification do={ + :global SendNotification2; + + $SendNotification2 ({ subject=$1; message=$2; link=$3; silent=$4 }); +} + +# send notification via NotificationFunctions - expects one array argument +:set SendNotification2 do={ + :local Notification $1; + + :global NotificationFunctions; + + :foreach FunctionName,Discard in=$NotificationFunctions do={ + ($NotificationFunctions->$FunctionName) \ + ("\$NotificationFunctions->\"" . $FunctionName . "\"") \ + $Notification; + } +} + +# return UTF-8 symbol for unicode name +:set SymbolByUnicodeName do={ + :local Symbols { + "abacus"="\F0\9F\A7\AE"; + "alarm-clock"="\E2\8F\B0"; + "calendar"="\F0\9F\93\85"; + "card-file-box"="\F0\9F\97\83"; + "chart-decreasing"="\F0\9F\93\89"; + "chart-increasing"="\F0\9F\93\88"; + "cloud"="\E2\98\81"; + "cross-mark"="\E2\9D\8C"; + "earth"="\F0\9F\8C\8D"; + "fire"="\F0\9F\94\A5"; + "floppy-disk"="\F0\9F\92\BE"; + "high-voltage-sign"="\E2\9A\A1"; + "incoming-envelope"="\F0\9F\93\A8"; + "link"="\F0\9F\94\97"; + "lock-with-ink-pen"="\F0\9F\94\8F"; + "memo"="\F0\9F\93\9D"; + "mobile-phone"="\F0\9F\93\B1"; + "pushpin"="\F0\9F\93\8C"; + "scissors"="\E2\9C\82"; + "sparkles"="\E2\9C\A8"; + "speech-balloon"="\F0\9F\92\AC"; + "up-arrow"="\E2\AC\86"; + "warning-sign"="\E2\9A\A0"; + "white-heavy-check-mark"="\E2\9C\85" + } + + :return (($Symbols->$1) . "\EF\B8\8F"); +} + +# return symbol for notification +:set SymbolForNotification do={ + :global NotificationsWithSymbols; + :global SymbolByUnicodeName; + + :if ($NotificationsWithSymbols != true) do={ + :return ""; + } + :local Return ""; + :foreach Symbol in=[ :toarray $1 ] do={ + :set Return ($Return . [ $SymbolByUnicodeName $Symbol ]); + } + :return ($Return . " "); +} + +# convert line endings, UNIX -> DOS +:set Unix2Dos do={ + :local Input [ :tostr $1 ]; + + :global CharacterReplace; + + :return [ $CharacterReplace [ $CharacterReplace $Input \ + ("\n") ("\r\n") ] ("\r\r\n") ("\r\n") ]; +} + +# url encoding +:set UrlEncode do={ + :local Input [ :tostr $1 ]; + + :if ([ :len $Input ] = 0) do={ + :return ""; + } + + :local Return ""; + :local Chars ("\n\r !\"#\$%&'()*+,:;<=>\?@[\\]^`{|}~"); + :local Subs { "%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27"; + "%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E"; "%3F"; + "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D"; "%7E" }; + + :for I from=0 to=([ :len $Input ] - 1) do={ + :local Char [ :pick $Input $I ]; + :local Replace [ :find $Chars $Char ]; + + :if ([ :typeof $Replace ] = "num") do={ + :set Char ($Subs->$Replace); + } + :set Return ($Return . $Char); + } + + :return $Return; +} + +# basic syntax validation +:set ValidateSyntax do={ + :local Code [ :tostr $1 ]; + + :do { + [ :parse (":local Validate do={\n" . $Code . "\n}") ]; + } on-error={ + :return false; + } + :return true; +} + +# convert version string to numeric value +:set VersionToNum do={ + :local Input [ :tostr $1 ]; + :local Multi 0x1000000; + :local Return 0; + + :global CharacterReplace; + + :set Input [ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $Input \ + "." "," ] "beta" ",beta," ] "rc" ",rc," ]; + + :foreach Value in=([ :toarray $Input ], 0) do={ + :local Num [ :tonum $Value ]; + :if ($Multi = 0x100) do={ + :if ([ :typeof $Num ] = "num") do={ + :set Return ($Return + 0xff00); + :set Multi ($Multi / 0x100); + } else={ + :if ($Value = "beta") do={ :set Return ($Return + 0x3f00); } + :if ($Value = "rc") do={ :set Return ($Return + 0x7f00); } + } + } + :if ([ :typeof $Num ] = "num") do={ :set Return ($Return + ($Value * $Multi)); } + :set Multi ($Multi / 0x100); + } + + :return $Return; +} + +# wait for default route to be reachable +:set WaitDefaultRouteReachable do={ + :global IsDefaultRouteReachable; + + :while ([ $IsDefaultRouteReachable ] = false) do={ + :delay 1s; + } +} + +# wait for DNS to resolve +:set WaitDNSResolving do={ + :global IsDNSResolving; + + :while ([ $IsDNSResolving ] = false) do={ + :delay 1s; + } +} + +# wait for file to be available +:set WaitForFile do={ + :local FileName [ :tostr $1 ]; + :local WaitTime [ :totime $2 ]; + + :global CleanFilePath; + :global EitherOr; + + :set FileName [ $CleanFilePath $FileName ]; + :local I 1; + :local Delay ([ :totime [ $EitherOr $WaitTime 2s ] ] / 20); + + :while ([ :len [ /file/find where name=$FileName ] ] = 0) do={ + :if ($I >= 20) do={ + :return false; + } + :delay $Delay; + :set I ($I + 1); + } + :return true; +} + +# wait to be fully connected (default route is reachable, time is sync, DNS resolves) +:set WaitFullyConnected do={ + :global WaitDefaultRouteReachable; + :global WaitDNSResolving; + :global WaitTimeSync; + + $WaitDefaultRouteReachable; + $WaitTimeSync; + $WaitDNSResolving; +} + +# wait for time to become synced +:set WaitTimeSync do={ + :global IsTimeSync; + + :while ([ $IsTimeSync ] = false) do={ + :delay 1s; + } +} + +# load modules +:foreach Script in=[ /system/script/find where name ~ "^mod/." ] do={ + :local ScriptVal [ /system/script/get $Script ]; + :if ([ $ValidateSyntax ($ScriptVal->"source") ] = true) do={ + :do { + /system/script/run $Script; + } on-error={ + $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed to run.") false; + } + } else={ + $LogPrintExit2 error $0 ("Module '" . $ScriptVal->"name" . "' failed syntax validation, skipping.") false; + } +} + +# signal we are ready +:set GlobalFunctionsReady true; diff --git a/global-wait b/global-wait index fe1928b..2da00ca 100644 --- a/global-wait +++ b/global-wait @@ -1,11 +1,3 @@ #!rsc by RouterOS -# RouterOS script: global-wait -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# wait for global-functions to finish -# https://git.eworm.de/cgit/routeros-scripts/about/doc/global-wait.md - -:local 0 "global-wait"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } +# dummy for migration diff --git a/global-wait.rsc b/global-wait.rsc new file mode 100644 index 0000000..fe1928b --- /dev/null +++ b/global-wait.rsc @@ -0,0 +1,11 @@ +#!rsc by RouterOS +# RouterOS script: global-wait +# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# wait for global-functions to finish +# https://git.eworm.de/cgit/routeros-scripts/about/doc/global-wait.md + +:local 0 "global-wait"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } @@ -1,34 +1,3 @@ #!rsc by RouterOS -# RouterOS script: gps-track -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# track gps data by sending json data to http server -# https://git.eworm.de/cgit/routeros-scripts/about/doc/gps-track.md - -:local 0 "gps-track"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global GpsTrackUrl; -:global Identity; - -:global LogPrintExit2; - -:local CoordinateFormat [ /system/gps/get coordinate-format ]; -:local Gps [ /system/gps/monitor once as-value ]; - -:if ($Gps->"valid" = true) do={ - /tool/fetch check-certificate=yes-without-crl $GpsTrackUrl output=none \ - http-method=post http-header-field="Content-Type: application/json" \ - http-data=("{" . \ - "\"lat\":\"" . ($Gps->"latitude") . "\"," . \ - "\"lon\":\"" . ($Gps->"longitude") . "\"," . \ - "\"identity\":\"" . $Identity . "\"" . \ - "}") as-value; - $LogPrintExit2 debug $0 ("Sending GPS data in " . $CoordinateFormat . " format: " . \ - "lat: " . ($Gps->"latitude") . " " . \ - "lon: " . ($Gps->"longitude")) false; -} else={ - $LogPrintExit2 debug $0 ("GPS data not valid.") false; -} +# dummy for migration diff --git a/gps-track.rsc b/gps-track.rsc new file mode 100644 index 0000000..d0d1232 --- /dev/null +++ b/gps-track.rsc @@ -0,0 +1,34 @@ +#!rsc by RouterOS +# RouterOS script: gps-track +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# track gps data by sending json data to http server +# https://git.eworm.de/cgit/routeros-scripts/about/doc/gps-track.md + +:local 0 "gps-track"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global GpsTrackUrl; +:global Identity; + +:global LogPrintExit2; + +:local CoordinateFormat [ /system/gps/get coordinate-format ]; +:local Gps [ /system/gps/monitor once as-value ]; + +:if ($Gps->"valid" = true) do={ + /tool/fetch check-certificate=yes-without-crl $GpsTrackUrl output=none \ + http-method=post http-header-field="Content-Type: application/json" \ + http-data=("{" . \ + "\"lat\":\"" . ($Gps->"latitude") . "\"," . \ + "\"lon\":\"" . ($Gps->"longitude") . "\"," . \ + "\"identity\":\"" . $Identity . "\"" . \ + "}") as-value; + $LogPrintExit2 debug $0 ("Sending GPS data in " . $CoordinateFormat . " format: " . \ + "lat: " . ($Gps->"latitude") . " " . \ + "lon: " . ($Gps->"longitude")) false; +} else={ + $LogPrintExit2 debug $0 ("GPS data not valid.") false; +} diff --git a/hotspot-to-wpa b/hotspot-to-wpa index dbce9ff..2da00ca 100644 --- a/hotspot-to-wpa +++ b/hotspot-to-wpa @@ -1,72 +1,3 @@ #!rsc by RouterOS -# RouterOS script: hotspot-to-wpa -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# add private WPA passphrase after hotspot login -# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md - -:local 0 "hotspot-to-wpa"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; - -:local MacAddress $"mac-address"; -:local UserName $username; -:local Date [ /system/clock/get date ]; -:local UserVal [ /ip/hotspot/user/get [ find where name=$UserName ] ]; -:local UserInfo [ $ParseKeyValueStore ($UserVal->"comment") ]; -:local Hotspot [ /ip/hotspot/host/get [ find where mac-address=$MacAddress authorized ] server ]; - -:if ([ :len [ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={ - /caps-man/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes; - $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- hotspot-to-wpa above ---'.") false; -} -:local PlaceBefore ([ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0); - -:if ([ :len [ /caps-man/access-list/find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ] ] = 0) do={ - /caps-man/access-list/add comment=("hotspot-to-wpa template " . $Hotspot) disabled=yes place-before=$PlaceBefore; - $LogPrintExit2 warning $0 ("Added template in access-list for hotspot '" . $Hotspot . "'.") false; -} -:local Template [ /caps-man/access-list/get ([ find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ]; - -:if ($Template->"action" = "reject") do={ - $LogPrintExit2 info $0 ("Ignoring login for hotspot '" . $Hotspot . "'.") true; -} - -# allow login page to load -:delay 1s; - -$LogPrintExit2 info $0 ("Adding/updating access-list entry for mac address " . $MacAddress . \ - " (user " . $UserName . ").") false; -/caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; -/caps-man/access-list/add comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \ - mac-address=$MacAddress private-passphrase=($UserVal->"password") ssid-regexp="-wpa\$" place-before=$PlaceBefore; - -:local Entry [ /caps-man/access-list/find where mac-address=$MacAddress \ - comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) ]; -:local PrivatePassphrase [ $EitherOr ($UserInfo->"private-passphrase") ($Template->"private-passphrase") ]; -:if ([ :len $PrivatePassphrase ] > 0) do={ - :if ($PrivatePassphrase = "ignore") do={ - /caps-man/access-list/set $Entry !private-passphrase; - } else={ - /caps-man/access-list/set $Entry private-passphrase=$PrivatePassphrase; - } -} -:local SsidRegexp [ $EitherOr ($UserInfo->"ssid-regexp") ($Template->"ssid-regexp") ]; -:if ([ :len $SsidRegexp ] > 0) do={ - /caps-man/access-list/set $Entry ssid-regexp=$SsidRegexp; -} -:local VlanId [ $EitherOr ($UserInfo->"vlan-id") ($Template->"vlan-id") ]; -:if ([ :len $VlanId ] > 0) do={ - /caps-man/access-list/set $Entry vlan-id=$VlanId; -} -:local VlanMode [ $EitherOr ($UserInfo->"vlan-mode") ($Template->"vlan-mode") ]; -:if ([ :len $VlanMode] > 0) do={ - /caps-man/access-list/set $Entry vlan-mode=$VlanMode; -} +# dummy for migration diff --git a/hotspot-to-wpa-cleanup b/hotspot-to-wpa-cleanup index 15f63f9..2da00ca 100644 --- a/hotspot-to-wpa-cleanup +++ b/hotspot-to-wpa-cleanup @@ -1,51 +1,3 @@ #!rsc by RouterOS -# RouterOS script: hotspot-to-wpa-cleanup -# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: lease-script, order=80 -# -# manage and clean up private WPA passphrase after hotspot login -# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md - -:local 0 "hotspot-to-wpa-cleanup"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; -:global ScriptLock; - -$ScriptLock $0 false 10; - -:foreach Client in=[ /caps-man/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ - :local ClientVal [ /caps-man/registration-table/get $Client ]; - :local Lease [ /ip/dhcp-server/lease/find where server~"wpa" dynamic \ - mac-address=($ClientVal->"mac-address") ]; - :if ([ :len $Lease ] > 0) do={ - $LogPrintExit2 info $0 ("Client with mac address " . ($ClientVal->"mac-address") . \ - " connected to WPA, making lease static.") false; - /ip/dhcp-server/lease/make-static $Lease; - /ip/dhcp-server/lease/set comment=($ClientVal->"comment") $Lease; - } -} - -:foreach Client in=[ /caps-man/access-list/find where comment~"^hotspot-to-wpa:" and \ - !(comment~[ /system/clock/get date ]) ] do={ - :local ClientVal [ /caps-man/access-list/get $Client ]; - :if ([ :len [ /ip/dhcp-server/lease/find where server~"wpa" !dynamic \ - mac-address=($ClientVal->"mac-address") ] ] = 0) do={ - $LogPrintExit2 info $0 ("Client with mac address " . ($ClientVal->"mac-address") . \ - " did not connect to WPA, removing from access list.") false; - /caps-man/access-list/remove $Client; - } -} - -:foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status=waiting \ - last-seen>4w comment~"^hotspot-to-wpa:" ] do={ - :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - $LogPrintExit2 info $0 ("Client with mac address " . ($LeaseVal->"mac-address") . \ - " was not seen for long time, removing.") false; - /caps-man/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - mac-address=($LeaseVal->"mac-address") ]; - /ip/dhcp-server/lease/remove $Lease; -} +# dummy for migration diff --git a/hotspot-to-wpa-cleanup.rsc b/hotspot-to-wpa-cleanup.rsc new file mode 100644 index 0000000..15f63f9 --- /dev/null +++ b/hotspot-to-wpa-cleanup.rsc @@ -0,0 +1,51 @@ +#!rsc by RouterOS +# RouterOS script: hotspot-to-wpa-cleanup +# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: lease-script, order=80 +# +# manage and clean up private WPA passphrase after hotspot login +# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md + +:local 0 "hotspot-to-wpa-cleanup"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; +:global ScriptLock; + +$ScriptLock $0 false 10; + +:foreach Client in=[ /caps-man/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ + :local ClientVal [ /caps-man/registration-table/get $Client ]; + :local Lease [ /ip/dhcp-server/lease/find where server~"wpa" dynamic \ + mac-address=($ClientVal->"mac-address") ]; + :if ([ :len $Lease ] > 0) do={ + $LogPrintExit2 info $0 ("Client with mac address " . ($ClientVal->"mac-address") . \ + " connected to WPA, making lease static.") false; + /ip/dhcp-server/lease/make-static $Lease; + /ip/dhcp-server/lease/set comment=($ClientVal->"comment") $Lease; + } +} + +:foreach Client in=[ /caps-man/access-list/find where comment~"^hotspot-to-wpa:" and \ + !(comment~[ /system/clock/get date ]) ] do={ + :local ClientVal [ /caps-man/access-list/get $Client ]; + :if ([ :len [ /ip/dhcp-server/lease/find where server~"wpa" !dynamic \ + mac-address=($ClientVal->"mac-address") ] ] = 0) do={ + $LogPrintExit2 info $0 ("Client with mac address " . ($ClientVal->"mac-address") . \ + " did not connect to WPA, removing from access list.") false; + /caps-man/access-list/remove $Client; + } +} + +:foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status=waiting \ + last-seen>4w comment~"^hotspot-to-wpa:" ] do={ + :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; + $LogPrintExit2 info $0 ("Client with mac address " . ($LeaseVal->"mac-address") . \ + " was not seen for long time, removing.") false; + /caps-man/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ + mac-address=($LeaseVal->"mac-address") ]; + /ip/dhcp-server/lease/remove $Lease; +} diff --git a/hotspot-to-wpa.rsc b/hotspot-to-wpa.rsc new file mode 100644 index 0000000..dbce9ff --- /dev/null +++ b/hotspot-to-wpa.rsc @@ -0,0 +1,72 @@ +#!rsc by RouterOS +# RouterOS script: hotspot-to-wpa +# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# add private WPA passphrase after hotspot login +# https://git.eworm.de/cgit/routeros-scripts/about/doc/hotspot-to-wpa.md + +:local 0 "hotspot-to-wpa"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global EitherOr; +:global LogPrintExit2; +:global ParseKeyValueStore; + +:local MacAddress $"mac-address"; +:local UserName $username; +:local Date [ /system/clock/get date ]; +:local UserVal [ /ip/hotspot/user/get [ find where name=$UserName ] ]; +:local UserInfo [ $ParseKeyValueStore ($UserVal->"comment") ]; +:local Hotspot [ /ip/hotspot/host/get [ find where mac-address=$MacAddress authorized ] server ]; + +:if ([ :len [ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={ + /caps-man/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes; + $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- hotspot-to-wpa above ---'.") false; +} +:local PlaceBefore ([ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0); + +:if ([ :len [ /caps-man/access-list/find where \ + comment=("hotspot-to-wpa template " . $Hotspot) disabled ] ] = 0) do={ + /caps-man/access-list/add comment=("hotspot-to-wpa template " . $Hotspot) disabled=yes place-before=$PlaceBefore; + $LogPrintExit2 warning $0 ("Added template in access-list for hotspot '" . $Hotspot . "'.") false; +} +:local Template [ /caps-man/access-list/get ([ find where \ + comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ]; + +:if ($Template->"action" = "reject") do={ + $LogPrintExit2 info $0 ("Ignoring login for hotspot '" . $Hotspot . "'.") true; +} + +# allow login page to load +:delay 1s; + +$LogPrintExit2 info $0 ("Adding/updating access-list entry for mac address " . $MacAddress . \ + " (user " . $UserName . ").") false; +/caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; +/caps-man/access-list/add comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \ + mac-address=$MacAddress private-passphrase=($UserVal->"password") ssid-regexp="-wpa\$" place-before=$PlaceBefore; + +:local Entry [ /caps-man/access-list/find where mac-address=$MacAddress \ + comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) ]; +:local PrivatePassphrase [ $EitherOr ($UserInfo->"private-passphrase") ($Template->"private-passphrase") ]; +:if ([ :len $PrivatePassphrase ] > 0) do={ + :if ($PrivatePassphrase = "ignore") do={ + /caps-man/access-list/set $Entry !private-passphrase; + } else={ + /caps-man/access-list/set $Entry private-passphrase=$PrivatePassphrase; + } +} +:local SsidRegexp [ $EitherOr ($UserInfo->"ssid-regexp") ($Template->"ssid-regexp") ]; +:if ([ :len $SsidRegexp ] > 0) do={ + /caps-man/access-list/set $Entry ssid-regexp=$SsidRegexp; +} +:local VlanId [ $EitherOr ($UserInfo->"vlan-id") ($Template->"vlan-id") ]; +:if ([ :len $VlanId ] > 0) do={ + /caps-man/access-list/set $Entry vlan-id=$VlanId; +} +:local VlanMode [ $EitherOr ($UserInfo->"vlan-mode") ($Template->"vlan-mode") ]; +:if ([ :len $VlanMode] > 0) do={ + /caps-man/access-list/set $Entry vlan-mode=$VlanMode; +} diff --git a/ip-addr-bridge b/ip-addr-bridge index 99fcba5..2da00ca 100644 --- a/ip-addr-bridge +++ b/ip-addr-bridge @@ -1,18 +1,3 @@ #!rsc by RouterOS -# RouterOS script: ip-addr-bridge -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# enable or disable ip addresses based on bridge port state -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ip-addr-bridge.md - -:foreach Bridge in=[ /interface/bridge/find ] do={ - :local BrName [ /interface/bridge/get $Bridge name ]; - :if ([ :len [ /interface/bridge/port/find where bridge=$BrName ] ] > 0) do={ - :if ([ :len [ /interface/bridge/port/find where bridge=$BrName and inactive=no ] ] = 0) do={ - /ip/address/disable [ find where !dynamic interface=$BrName ]; - } else={ - /ip/address/enable [ find where !dynamic interface=$BrName ]; - } - } -} +# dummy for migration diff --git a/ip-addr-bridge.rsc b/ip-addr-bridge.rsc new file mode 100644 index 0000000..99fcba5 --- /dev/null +++ b/ip-addr-bridge.rsc @@ -0,0 +1,18 @@ +#!rsc by RouterOS +# RouterOS script: ip-addr-bridge +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# enable or disable ip addresses based on bridge port state +# https://git.eworm.de/cgit/routeros-scripts/about/doc/ip-addr-bridge.md + +:foreach Bridge in=[ /interface/bridge/find ] do={ + :local BrName [ /interface/bridge/get $Bridge name ]; + :if ([ :len [ /interface/bridge/port/find where bridge=$BrName ] ] > 0) do={ + :if ([ :len [ /interface/bridge/port/find where bridge=$BrName and inactive=no ] ] = 0) do={ + /ip/address/disable [ find where !dynamic interface=$BrName ]; + } else={ + /ip/address/enable [ find where !dynamic interface=$BrName ]; + } + } +} diff --git a/ipsec-to-dns b/ipsec-to-dns index 530c714..2da00ca 100644 --- a/ipsec-to-dns +++ b/ipsec-to-dns @@ -1,69 +1,3 @@ #!rsc by RouterOS -# RouterOS script: ipsec-to-dns -# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# and add/remove/update DNS entries from IPSec mode-config -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipsec-to-dns.md - -:local 0 "ipsec-to-dns"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Domain; -:global HostNameInZone; -:global Identity; -:global PrefixInZone; - -:global CharacterReplace; -:global EscapeForRegEx; -:global IfThenElse; -:global LogPrintExit2; - -:local Zone \ - ([ $IfThenElse ($PrefixInZone = true) "ipsec." ] . \ - [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain); -:local Ttl 5m; -:local CommentPrefix ("managed by " . $0 . " for "); -:local CommentString ("--- " . $0 . " above ---"); - -:if ([ :len [ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ] ] = 0) do={ - /ip/dns/static/add comment=$CommentString name=- type=NXDOMAIN disabled=yes; - $LogPrintExit2 warning $0 ("Added disabled static dns record with comment '" . $CommentString . "'.") false; -} -:local PlaceBefore ([ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ]->0); - -:foreach DnsRecord in=[ /ip/dns/static/find where comment ~ $CommentPrefix ] do={ - :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; - :local PeerId [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ]; - :if ([ :len [ /ip/ipsec/active-peers/find where id~("^(CN=)?" . [ $EscapeForRegEx $PeerId ] . "\$") \ - dynamic-address=($DnsRecordVal->"address") ] ] > 0) do={ - $LogPrintExit2 debug $0 ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry.") false; - } else={ - :local Found false; - $LogPrintExit2 info $0 ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") has gone, deleting DNS entry.") false; - /ip/dns/static/remove $DnsRecord; - } -} - -:foreach Peer in=[ /ip/ipsec/active-peers/find where !(dynamic-address=[]) ] do={ - :local PeerVal [ /ip/ipsec/active-peers/get $Peer ]; - :local PeerId [ $CharacterReplace ($PeerVal->"id") "CN=" "" ]; - :local Comment ($CommentPrefix . $PeerId); - :local HostName [ :pick $PeerId 0 [ :find ($PeerId . ".") "." ] ]; - - :local Fqdn ($HostName . "." . $Zone); - :local DnsRecord [ /ip/dns/static/find where name=$Fqdn ]; - :if ([ :len $DnsRecord ] > 0) do={ - :local DnsIp [ /ip/dns/static/get $DnsRecord address ]; - :if ($DnsIp = $PeerVal->"dynamic-address") do={ - $LogPrintExit2 debug $0 ("DNS entry for " . $Fqdn . " does not need updating.") false; - } else={ - $LogPrintExit2 info $0 ("Replacing DNS entry for " . $Fqdn . ", new address is " . $PeerVal->"dynamic-address" . ".") false; - /ip/dns/static/set name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment $DnsRecord; - } - } else={ - $LogPrintExit2 info $0 ("Adding new DNS entry for " . $Fqdn . ", address is " . $PeerVal->"dynamic-address" . ".") false; - /ip/dns/static/add name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - } -} +# dummy for migration diff --git a/ipsec-to-dns.rsc b/ipsec-to-dns.rsc new file mode 100644 index 0000000..530c714 --- /dev/null +++ b/ipsec-to-dns.rsc @@ -0,0 +1,69 @@ +#!rsc by RouterOS +# RouterOS script: ipsec-to-dns +# Copyright (c) 2021-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# and add/remove/update DNS entries from IPSec mode-config +# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipsec-to-dns.md + +:local 0 "ipsec-to-dns"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Domain; +:global HostNameInZone; +:global Identity; +:global PrefixInZone; + +:global CharacterReplace; +:global EscapeForRegEx; +:global IfThenElse; +:global LogPrintExit2; + +:local Zone \ + ([ $IfThenElse ($PrefixInZone = true) "ipsec." ] . \ + [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain); +:local Ttl 5m; +:local CommentPrefix ("managed by " . $0 . " for "); +:local CommentString ("--- " . $0 . " above ---"); + +:if ([ :len [ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ] ] = 0) do={ + /ip/dns/static/add comment=$CommentString name=- type=NXDOMAIN disabled=yes; + $LogPrintExit2 warning $0 ("Added disabled static dns record with comment '" . $CommentString . "'.") false; +} +:local PlaceBefore ([ /ip/dns/static/find where comment=$CommentString name=- type=NXDOMAIN disabled ]->0); + +:foreach DnsRecord in=[ /ip/dns/static/find where comment ~ $CommentPrefix ] do={ + :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; + :local PeerId [ $CharacterReplace ($DnsRecordVal->"comment") $CommentPrefix "" ]; + :if ([ :len [ /ip/ipsec/active-peers/find where id~("^(CN=)?" . [ $EscapeForRegEx $PeerId ] . "\$") \ + dynamic-address=($DnsRecordVal->"address") ] ] > 0) do={ + $LogPrintExit2 debug $0 ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry.") false; + } else={ + :local Found false; + $LogPrintExit2 info $0 ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") has gone, deleting DNS entry.") false; + /ip/dns/static/remove $DnsRecord; + } +} + +:foreach Peer in=[ /ip/ipsec/active-peers/find where !(dynamic-address=[]) ] do={ + :local PeerVal [ /ip/ipsec/active-peers/get $Peer ]; + :local PeerId [ $CharacterReplace ($PeerVal->"id") "CN=" "" ]; + :local Comment ($CommentPrefix . $PeerId); + :local HostName [ :pick $PeerId 0 [ :find ($PeerId . ".") "." ] ]; + + :local Fqdn ($HostName . "." . $Zone); + :local DnsRecord [ /ip/dns/static/find where name=$Fqdn ]; + :if ([ :len $DnsRecord ] > 0) do={ + :local DnsIp [ /ip/dns/static/get $DnsRecord address ]; + :if ($DnsIp = $PeerVal->"dynamic-address") do={ + $LogPrintExit2 debug $0 ("DNS entry for " . $Fqdn . " does not need updating.") false; + } else={ + $LogPrintExit2 info $0 ("Replacing DNS entry for " . $Fqdn . ", new address is " . $PeerVal->"dynamic-address" . ".") false; + /ip/dns/static/set name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment $DnsRecord; + } + } else={ + $LogPrintExit2 info $0 ("Adding new DNS entry for " . $Fqdn . ", address is " . $PeerVal->"dynamic-address" . ".") false; + /ip/dns/static/add name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore; + } +} diff --git a/ipv6-update b/ipv6-update index 2838feb..2da00ca 100644 --- a/ipv6-update +++ b/ipv6-update @@ -1,76 +1,3 @@ #!rsc by RouterOS -# RouterOS script: ipv6-update -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# update firewall and dns settings on IPv6 prefix change -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipv6-update.md - -:local 0 "ipv6-update"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local PdPrefix $"pd-prefix"; - -:global LogPrintExit2; -:global ParseKeyValueStore; - -:if ([ :typeof $PdPrefix ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from ipv6 dhcp-client.") true; -} - -:local Pool [ /ipv6/pool/get [ find where prefix=$PdPrefix ] name ]; -:if ([ :len [ /ipv6/firewall/address-list/find where comment=("ipv6-pool-" . $Pool) ] ] = 0) do={ - /ipv6/firewall/address-list/add list=("ipv6-pool-" . $Pool) address=:: comment=("ipv6-pool-" . $Pool); - $LogPrintExit2 warning $0 ("Added ipv6 address list entry for ipv6-pool-" . $Pool) false; -} -:local AddrList [ /ipv6/firewall/address-list/find where comment=("ipv6-pool-" . $Pool) ]; -:local OldPrefix [ /ipv6/firewall/address-list/get ($AddrList->0) address ]; - -:if ($OldPrefix != $PdPrefix) do={ - $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 prefix " . $PdPrefix) false; - /ipv6/firewall/address-list/set address=$PdPrefix $AddrList; - - # give the interfaces a moment to receive their addresses - :delay 2s; - - :foreach ListEntry in=[ /ipv6/firewall/address-list/find where comment~("^ipv6-pool-" . $Pool . ",") ] do={ - :local ListEntryVal [ /ipv6/firewall/address-list/get $ListEntry ]; - :local Comment [ $ParseKeyValueStore ($ListEntryVal->"comment") ]; - - :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ]; - :if ([ :len $Prefix ] = 1) do={ - :set Prefix [ /ipv6/address/get $Prefix address ]; - - :if ([ :typeof [ :find ($ListEntryVal->"address") "/128" ] ] = "num" ) do={ - :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::); - :local Address ($ListEntryVal->"address"); - :local Address ($Prefix | ([ :toip6 [ :pick $Address 0 [ :find $Address "/128" ] ] ] & ::ffff:ffff:ffff:ffff)); - - $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 host address " . $Address . \ - " from interface " . ($Comment->"interface")) false; - /ipv6/firewall/address-list/set address=$Address $ListEntry; - } else={ - $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 prefix " . $Prefix . \ - " from interface " . ($Comment->"interface")) false; - /ipv6/firewall/address-list/set address=$Prefix $ListEntry; - } - } - } - - :foreach Record in=[ /ip/dns/static/find where comment~("^ipv6-pool-" . $Pool . ",") ] do={ - :local RecordVal [ /ip/dns/static/get $Record ]; - :local Comment [ $ParseKeyValueStore ($RecordVal->"comment") ]; - - :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ]; - :if ([ :len $Prefix ] = 1) do={ - :set Prefix [ /ipv6/address/get $Prefix address ]; - :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::); - :local Address ($Prefix | ([ :toip6 ($RecordVal->"address") ] & ::ffff:ffff:ffff:ffff)); - - $LogPrintExit2 info $0 ("Updating DNS record for " . ($RecordVal->"name") . \ - ($RecordVal->"regexp") . " to " . $Address) false; - /ip/dns/static/set address=$Address $Record; - } - } -} +# dummy for migration diff --git a/ipv6-update.rsc b/ipv6-update.rsc new file mode 100644 index 0000000..2838feb --- /dev/null +++ b/ipv6-update.rsc @@ -0,0 +1,76 @@ +#!rsc by RouterOS +# RouterOS script: ipv6-update +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# update firewall and dns settings on IPv6 prefix change +# https://git.eworm.de/cgit/routeros-scripts/about/doc/ipv6-update.md + +:local 0 "ipv6-update"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:local PdPrefix $"pd-prefix"; + +:global LogPrintExit2; +:global ParseKeyValueStore; + +:if ([ :typeof $PdPrefix ] = "nothing") do={ + $LogPrintExit2 error $0 ("This script is supposed to run from ipv6 dhcp-client.") true; +} + +:local Pool [ /ipv6/pool/get [ find where prefix=$PdPrefix ] name ]; +:if ([ :len [ /ipv6/firewall/address-list/find where comment=("ipv6-pool-" . $Pool) ] ] = 0) do={ + /ipv6/firewall/address-list/add list=("ipv6-pool-" . $Pool) address=:: comment=("ipv6-pool-" . $Pool); + $LogPrintExit2 warning $0 ("Added ipv6 address list entry for ipv6-pool-" . $Pool) false; +} +:local AddrList [ /ipv6/firewall/address-list/find where comment=("ipv6-pool-" . $Pool) ]; +:local OldPrefix [ /ipv6/firewall/address-list/get ($AddrList->0) address ]; + +:if ($OldPrefix != $PdPrefix) do={ + $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 prefix " . $PdPrefix) false; + /ipv6/firewall/address-list/set address=$PdPrefix $AddrList; + + # give the interfaces a moment to receive their addresses + :delay 2s; + + :foreach ListEntry in=[ /ipv6/firewall/address-list/find where comment~("^ipv6-pool-" . $Pool . ",") ] do={ + :local ListEntryVal [ /ipv6/firewall/address-list/get $ListEntry ]; + :local Comment [ $ParseKeyValueStore ($ListEntryVal->"comment") ]; + + :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ]; + :if ([ :len $Prefix ] = 1) do={ + :set Prefix [ /ipv6/address/get $Prefix address ]; + + :if ([ :typeof [ :find ($ListEntryVal->"address") "/128" ] ] = "num" ) do={ + :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::); + :local Address ($ListEntryVal->"address"); + :local Address ($Prefix | ([ :toip6 [ :pick $Address 0 [ :find $Address "/128" ] ] ] & ::ffff:ffff:ffff:ffff)); + + $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 host address " . $Address . \ + " from interface " . ($Comment->"interface")) false; + /ipv6/firewall/address-list/set address=$Address $ListEntry; + } else={ + $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 prefix " . $Prefix . \ + " from interface " . ($Comment->"interface")) false; + /ipv6/firewall/address-list/set address=$Prefix $ListEntry; + } + } + } + + :foreach Record in=[ /ip/dns/static/find where comment~("^ipv6-pool-" . $Pool . ",") ] do={ + :local RecordVal [ /ip/dns/static/get $Record ]; + :local Comment [ $ParseKeyValueStore ($RecordVal->"comment") ]; + + :local Prefix [ /ipv6/address/find where from-pool=$Pool interface=($Comment->"interface") global ]; + :if ([ :len $Prefix ] = 1) do={ + :set Prefix [ /ipv6/address/get $Prefix address ]; + :set Prefix ([ :toip6 [ :pick $Prefix 0 [ :find $Prefix "/64" ] ] ] & ffff:ffff:ffff:ffff::); + :local Address ($Prefix | ([ :toip6 ($RecordVal->"address") ] & ::ffff:ffff:ffff:ffff)); + + $LogPrintExit2 info $0 ("Updating DNS record for " . ($RecordVal->"name") . \ + ($RecordVal->"regexp") . " to " . $Address) false; + /ip/dns/static/set address=$Address $Record; + } + } +} diff --git a/lease-script b/lease-script index 346d52b..2da00ca 100644 --- a/lease-script +++ b/lease-script @@ -1,51 +1,3 @@ #!rsc by RouterOS -# RouterOS script: lease-script -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# run scripts on DHCP lease -# https://git.eworm.de/cgit/routeros-scripts/about/doc/lease-script.md - -:local 0 "lease-script"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Grep; -:global IfThenElse; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; - -:if ([ :typeof $leaseActIP ] = "nothing" || \ - [ :typeof $leaseActMAC ] = "nothing" || \ - [ :typeof $leaseServerName ] = "nothing" || \ - [ :typeof $leaseBound ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from ip dhcp-server.") true; -} - -$LogPrintExit2 debug $0 ("DHCP Server " . $leaseServerName . " " . [ $IfThenElse ($leaseBound = 0) \ - "de" "" ] . "assigned lease " . $leaseActIP . " to " . $leaseActMAC) false; - -$ScriptLock $0 false 10; - -:if ([ :len [ /system/script/job/find where script=$0 ] ] > 1) do={ - $LogPrintExit2 debug $0 ("More invocations are waiting, exiting early.") true; -} - -:local RunOrder ({}); - -:foreach Script in=[ /system/script/find where source~("\n# provides: lease-script, ") ] do={ - :local ScriptVal [ /system/script/get $Script ]; - :local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") "# provides: lease-script, " ] ]; - - :set ($RunOrder->($Store->"order")) ($ScriptVal->"name"); -} - -:foreach Order,Script in=$RunOrder do={ - :do { - $LogPrintExit2 debug $0 ("Running script with order " . $Order . ": " . $Script) false; - /system/script/run $Script; - } on-error={ - $LogPrintExit2 warning $0 ("Running script '" . $Script . "' failed!") false; - } -} +# dummy for migration diff --git a/lease-script.rsc b/lease-script.rsc new file mode 100644 index 0000000..346d52b --- /dev/null +++ b/lease-script.rsc @@ -0,0 +1,51 @@ +#!rsc by RouterOS +# RouterOS script: lease-script +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# run scripts on DHCP lease +# https://git.eworm.de/cgit/routeros-scripts/about/doc/lease-script.md + +:local 0 "lease-script"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Grep; +:global IfThenElse; +:global LogPrintExit2; +:global ParseKeyValueStore; +:global ScriptLock; + +:if ([ :typeof $leaseActIP ] = "nothing" || \ + [ :typeof $leaseActMAC ] = "nothing" || \ + [ :typeof $leaseServerName ] = "nothing" || \ + [ :typeof $leaseBound ] = "nothing") do={ + $LogPrintExit2 error $0 ("This script is supposed to run from ip dhcp-server.") true; +} + +$LogPrintExit2 debug $0 ("DHCP Server " . $leaseServerName . " " . [ $IfThenElse ($leaseBound = 0) \ + "de" "" ] . "assigned lease " . $leaseActIP . " to " . $leaseActMAC) false; + +$ScriptLock $0 false 10; + +:if ([ :len [ /system/script/job/find where script=$0 ] ] > 1) do={ + $LogPrintExit2 debug $0 ("More invocations are waiting, exiting early.") true; +} + +:local RunOrder ({}); + +:foreach Script in=[ /system/script/find where source~("\n# provides: lease-script, ") ] do={ + :local ScriptVal [ /system/script/get $Script ]; + :local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") "# provides: lease-script, " ] ]; + + :set ($RunOrder->($Store->"order")) ($ScriptVal->"name"); +} + +:foreach Order,Script in=$RunOrder do={ + :do { + $LogPrintExit2 debug $0 ("Running script with order " . $Order . ": " . $Script) false; + /system/script/run $Script; + } on-error={ + $LogPrintExit2 warning $0 ("Running script '" . $Script . "' failed!") false; + } +} diff --git a/leds-day-mode b/leds-day-mode index ca2e8d8..2da00ca 100644 --- a/leds-day-mode +++ b/leds-day-mode @@ -1,9 +1,3 @@ #!rsc by RouterOS -# RouterOS script: leds-day-mode -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# enable LEDs -# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md - -/system/leds/settings/set all-leds-off=never; +# dummy for migration diff --git a/leds-day-mode.rsc b/leds-day-mode.rsc new file mode 100644 index 0000000..ca2e8d8 --- /dev/null +++ b/leds-day-mode.rsc @@ -0,0 +1,9 @@ +#!rsc by RouterOS +# RouterOS script: leds-day-mode +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# enable LEDs +# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md + +/system/leds/settings/set all-leds-off=never; diff --git a/leds-night-mode b/leds-night-mode index cdd8127..2da00ca 100644 --- a/leds-night-mode +++ b/leds-night-mode @@ -1,9 +1,3 @@ #!rsc by RouterOS -# RouterOS script: leds-night-mode -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# disable LEDs -# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md - -/system/leds/settings/set all-leds-off=immediate; +# dummy for migration diff --git a/leds-night-mode.rsc b/leds-night-mode.rsc new file mode 100644 index 0000000..cdd8127 --- /dev/null +++ b/leds-night-mode.rsc @@ -0,0 +1,9 @@ +#!rsc by RouterOS +# RouterOS script: leds-night-mode +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# disable LEDs +# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md + +/system/leds/settings/set all-leds-off=immediate; diff --git a/leds-toggle-mode b/leds-toggle-mode index da972b7..2da00ca 100644 --- a/leds-toggle-mode +++ b/leds-toggle-mode @@ -1,13 +1,3 @@ #!rsc by RouterOS -# RouterOS script: leds-toggle-mode -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# toggle LEDs mode -# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md - -:if ([ /system/leds/settings/get all-leds-off ] = "never") do={ - /system/leds/settings/set all-leds-off=immediate; -} else={ - /system/leds/settings/set all-leds-off=never; -} +# dummy for migration diff --git a/leds-toggle-mode.rsc b/leds-toggle-mode.rsc new file mode 100644 index 0000000..da972b7 --- /dev/null +++ b/leds-toggle-mode.rsc @@ -0,0 +1,13 @@ +#!rsc by RouterOS +# RouterOS script: leds-toggle-mode +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# toggle LEDs mode +# https://git.eworm.de/cgit/routeros-scripts/about/doc/leds-mode.md + +:if ([ /system/leds/settings/get all-leds-off ] = "never") do={ + /system/leds/settings/set all-leds-off=immediate; +} else={ + /system/leds/settings/set all-leds-off=never; +} diff --git a/log-forward b/log-forward index 96cb257..2da00ca 100644 --- a/log-forward +++ b/log-forward @@ -1,90 +1,3 @@ #!rsc by RouterOS -# RouterOS script: log-forward -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# forward log messages via notification -# https://git.eworm.de/cgit/routeros-scripts/about/doc/log-forward.md - -:local 0 "log-forward"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Identity; -:global LogForwardFilter; -:global LogForwardFilterMessage; -:global LogForwardInclude; -:global LogForwardIncludeMessage; -:global LogForwardLast; -:global LogForwardRateLimit; -:global NotificationsWithSymbols; - -:global EitherOr; -:global HexToNum; -:global IfThenElse; -:global LogForwardFilterLogForwarding; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -$ScriptLock $0; - -:if ([ :typeof $LogForwardRateLimit ] = "nothing") do={ - :set LogForwardRateLimit 0; -} - -:if ($LogForwardRateLimit > 30) do={ - :set LogForwardRateLimit ($LogForwardRateLimit - 1); - $LogPrintExit2 info $0 ("Rate limit in action, not forwarding logs, if any!") true; -} - -:local Count 0; -:local Duplicates false; -:local Last [ $IfThenElse ([ :len $LogForwardLast ] > 0) [ $HexToNum $LogForwardLast ] -1 ]; -:local Messages ""; -:local Warning false; -:local MessageVal; -:local MessageDups ({}); - -:local LogForwardFilterLogForwardingCached [ $EitherOr [ $LogForwardFilterLogForwarding ] ("\$^") ]; -:foreach Message in=[ /log/find where (!(message="") and \ - !(message~$LogForwardFilterLogForwardingCached) and \ - !(topics~$LogForwardFilter) and !(message~$LogForwardFilterMessage)) or \ - topics~$LogForwardInclude or message~$LogForwardIncludeMessage ] do={ - :set MessageVal [ /log/get $Message ]; - - :if ($Last < [ $HexToNum ($MessageVal->".id") ]) do={ - :local DupCount ($MessageDups->($MessageVal->"message")); - :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error|warning)") do={ - :set Warning true; - } - :if ($DupCount < 3) do={ - :set Messages ($Messages . "\n" . [ $IfThenElse ($NotificationsWithSymbols = true) (" \E2\97\8F ") ] . \ - $MessageVal->"time" . " " . [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message"); - } else={ - :set Duplicates true; - } - :set ($MessageDups->($MessageVal->"message")) ($DupCount + 1); - :set Count ($Count + 1); - } -} - -:if ($Count > 0) do={ - :set LogForwardRateLimit ($LogForwardRateLimit + 10); - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification [ $IfThenElse ($Warning = true) "warning-sign" "memo" ] ] . \ - "Log Forwarding"); \ - message=("The log on " . $Identity . " contains " . [ $IfThenElse ($Count = 1) "this message" \ - ("these " . $Count . " messages") ] . " after " . [ /system/resource/get uptime ] . " uptime." . \ - [ $IfThenElse ($Duplicates = true) (" Multi-repeated messages have been skipped.") ] . \ - [ $IfThenElse ($LogForwardRateLimit > 30) ("\nRate limit in action, delaying forwarding.") ] . \ - "\n" . $Messages) }); - - :set LogForwardLast ($MessageVal->".id"); -} else={ - :if ($LogForwardRateLimit > 0) do={ - :set LogForwardRateLimit ($LogForwardRateLimit - 1); - } -} +# dummy for migration diff --git a/log-forward.rsc b/log-forward.rsc new file mode 100644 index 0000000..96cb257 --- /dev/null +++ b/log-forward.rsc @@ -0,0 +1,90 @@ +#!rsc by RouterOS +# RouterOS script: log-forward +# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# forward log messages via notification +# https://git.eworm.de/cgit/routeros-scripts/about/doc/log-forward.md + +:local 0 "log-forward"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Identity; +:global LogForwardFilter; +:global LogForwardFilterMessage; +:global LogForwardInclude; +:global LogForwardIncludeMessage; +:global LogForwardLast; +:global LogForwardRateLimit; +:global NotificationsWithSymbols; + +:global EitherOr; +:global HexToNum; +:global IfThenElse; +:global LogForwardFilterLogForwarding; +:global LogPrintExit2; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; + +$ScriptLock $0; + +:if ([ :typeof $LogForwardRateLimit ] = "nothing") do={ + :set LogForwardRateLimit 0; +} + +:if ($LogForwardRateLimit > 30) do={ + :set LogForwardRateLimit ($LogForwardRateLimit - 1); + $LogPrintExit2 info $0 ("Rate limit in action, not forwarding logs, if any!") true; +} + +:local Count 0; +:local Duplicates false; +:local Last [ $IfThenElse ([ :len $LogForwardLast ] > 0) [ $HexToNum $LogForwardLast ] -1 ]; +:local Messages ""; +:local Warning false; +:local MessageVal; +:local MessageDups ({}); + +:local LogForwardFilterLogForwardingCached [ $EitherOr [ $LogForwardFilterLogForwarding ] ("\$^") ]; +:foreach Message in=[ /log/find where (!(message="") and \ + !(message~$LogForwardFilterLogForwardingCached) and \ + !(topics~$LogForwardFilter) and !(message~$LogForwardFilterMessage)) or \ + topics~$LogForwardInclude or message~$LogForwardIncludeMessage ] do={ + :set MessageVal [ /log/get $Message ]; + + :if ($Last < [ $HexToNum ($MessageVal->".id") ]) do={ + :local DupCount ($MessageDups->($MessageVal->"message")); + :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error|warning)") do={ + :set Warning true; + } + :if ($DupCount < 3) do={ + :set Messages ($Messages . "\n" . [ $IfThenElse ($NotificationsWithSymbols = true) (" \E2\97\8F ") ] . \ + $MessageVal->"time" . " " . [ :tostr ($MessageVal->"topics") ] . " " . $MessageVal->"message"); + } else={ + :set Duplicates true; + } + :set ($MessageDups->($MessageVal->"message")) ($DupCount + 1); + :set Count ($Count + 1); + } +} + +:if ($Count > 0) do={ + :set LogForwardRateLimit ($LogForwardRateLimit + 10); + + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification [ $IfThenElse ($Warning = true) "warning-sign" "memo" ] ] . \ + "Log Forwarding"); \ + message=("The log on " . $Identity . " contains " . [ $IfThenElse ($Count = 1) "this message" \ + ("these " . $Count . " messages") ] . " after " . [ /system/resource/get uptime ] . " uptime." . \ + [ $IfThenElse ($Duplicates = true) (" Multi-repeated messages have been skipped.") ] . \ + [ $IfThenElse ($LogForwardRateLimit > 30) ("\nRate limit in action, delaying forwarding.") ] . \ + "\n" . $Messages) }); + + :set LogForwardLast ($MessageVal->".id"); +} else={ + :if ($LogForwardRateLimit > 0) do={ + :set LogForwardRateLimit ($LogForwardRateLimit - 1); + } +} diff --git a/mod/bridge-port-to b/mod/bridge-port-to index f752d30..2da00ca 100644 --- a/mod/bridge-port-to +++ b/mod/bridge-port-to @@ -1,65 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/bridge-port-to -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# reset bridge ports to default bridge -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-to.md - -:global BridgePortTo; - -:set BridgePortTo do={ - :local BridgePortTo [ :tostr $1 ]; - - :global IfThenElse; - :global LogPrintExit2; - :global ParseKeyValueStore; - - :local InterfaceReEnable ({}); - :foreach BridgePort in=[ /interface/bridge/port/find where !(comment=[]) ] do={ - :local BridgePortVal [ /interface/bridge/port/get $BridgePort ]; - :foreach Config,BridgeDefault in=[ $ParseKeyValueStore ($BridgePortVal->"comment") ] do={ - :if ($Config = $BridgePortTo) do={ - :local DHCPClient [ /ip/dhcp-client/find where interface=$BridgePortVal->"interface" comment="toggle with bridge port" ]; - - :if ($BridgeDefault = "dhcp-client") do={ - :if ([ :len $DHCPClient ] != 1) do={ - $LogPrintExit2 warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \ - " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!") true; - } - :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ]; - - :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={ - $LogPrintExit2 info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.") false; - /interface/bridge/port/disable $BridgePort; - :delay 200ms; - /ip/dhcp-client/enable $DHCPClient; - } - } else={ - :if ($BridgePortVal->"disabled" = true || $BridgeDefault != $BridgePortVal->"bridge") do={ - $LogPrintExit2 info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $BridgePortTo . \ - " bridge " . $BridgeDefault . ", disabling dhcp client.") false; - :if ([ :len $DHCPClient ] = 1) do={ - /ip/dhcp-client/disable $DHCPClient; - :delay 200ms; - } - :local Disable [ /interface/ethernet/find where name=$BridgePortVal->"interface" ]; - :if ([ :len $Disable ] > 0) do={ - /interface/ethernet/disable $Disable; - :set InterfaceReEnable ($InterfaceReEnable, $Disable); - } - /interface/bridge/port/set disabled=no bridge=$BridgeDefault $BridgePort; - } else={ - $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \ - " bridge " . $BridgeDefault . ".") false; - } - } - } - } - } - :if ([ :len $InterfaceReEnable ] > 0) do={ - :delay 2s; - $LogPrintExit2 info $0 ("Re-enabling interfaces...") false; - /interface/ethernet/enable $InterfaceReEnable; - } -} +# dummy for migration diff --git a/mod/bridge-port-to.rsc b/mod/bridge-port-to.rsc new file mode 100644 index 0000000..f752d30 --- /dev/null +++ b/mod/bridge-port-to.rsc @@ -0,0 +1,65 @@ +#!rsc by RouterOS +# RouterOS script: mod/bridge-port-to +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# reset bridge ports to default bridge +# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-to.md + +:global BridgePortTo; + +:set BridgePortTo do={ + :local BridgePortTo [ :tostr $1 ]; + + :global IfThenElse; + :global LogPrintExit2; + :global ParseKeyValueStore; + + :local InterfaceReEnable ({}); + :foreach BridgePort in=[ /interface/bridge/port/find where !(comment=[]) ] do={ + :local BridgePortVal [ /interface/bridge/port/get $BridgePort ]; + :foreach Config,BridgeDefault in=[ $ParseKeyValueStore ($BridgePortVal->"comment") ] do={ + :if ($Config = $BridgePortTo) do={ + :local DHCPClient [ /ip/dhcp-client/find where interface=$BridgePortVal->"interface" comment="toggle with bridge port" ]; + + :if ($BridgeDefault = "dhcp-client") do={ + :if ([ :len $DHCPClient ] != 1) do={ + $LogPrintExit2 warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \ + " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!") true; + } + :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ]; + + :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={ + $LogPrintExit2 info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.") false; + /interface/bridge/port/disable $BridgePort; + :delay 200ms; + /ip/dhcp-client/enable $DHCPClient; + } + } else={ + :if ($BridgePortVal->"disabled" = true || $BridgeDefault != $BridgePortVal->"bridge") do={ + $LogPrintExit2 info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $BridgePortTo . \ + " bridge " . $BridgeDefault . ", disabling dhcp client.") false; + :if ([ :len $DHCPClient ] = 1) do={ + /ip/dhcp-client/disable $DHCPClient; + :delay 200ms; + } + :local Disable [ /interface/ethernet/find where name=$BridgePortVal->"interface" ]; + :if ([ :len $Disable ] > 0) do={ + /interface/ethernet/disable $Disable; + :set InterfaceReEnable ($InterfaceReEnable, $Disable); + } + /interface/bridge/port/set disabled=no bridge=$BridgeDefault $BridgePort; + } else={ + $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \ + " bridge " . $BridgeDefault . ".") false; + } + } + } + } + } + :if ([ :len $InterfaceReEnable ] > 0) do={ + :delay 2s; + $LogPrintExit2 info $0 ("Re-enabling interfaces...") false; + /interface/ethernet/enable $InterfaceReEnable; + } +} diff --git a/mod/bridge-port-vlan b/mod/bridge-port-vlan index 8fb64e1..2da00ca 100644 --- a/mod/bridge-port-vlan +++ b/mod/bridge-port-vlan @@ -1,73 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/bridge-port-vlan -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# manage VLANs on bridge ports -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-vlan.md - -:global BridgePortVlan; - -:global BridgePortVlan do={ - :local ConfigTo [ :tostr $1 ]; - - :global IfThenElse; - :global LogPrintExit2; - :global ParseKeyValueStore; - - :local InterfaceReEnable ({}); - :foreach BridgePort in=[ /interface/bridge/port/find where !(comment=[]) ] do={ - :local BridgePortVal [ /interface/bridge/port/get $BridgePort ]; - :foreach Config,Vlan in=[ $ParseKeyValueStore ($BridgePortVal->"comment") ] do={ - :if ($Config = $ConfigTo) do={ - :local DHCPClient [ /ip/dhcp-client/find where interface=$BridgePortVal->"interface" comment="toggle with bridge port" ]; - - :if ($Vlan = "dhcp-client") do={ - :if ([ :len $DHCPClient ] != 1) do={ - $LogPrintExit2 warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \ - " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!") true; - } - :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ]; - - :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={ - $LogPrintExit2 info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.") false; - /interface/bridge/port/disable $BridgePort; - :delay 200ms; - /ip/dhcp-client/enable $DHCPClient; - } - } else={ - :local VlanName $Vlan; - :if ($Vlan != [ :tostr [ :tonum $Vlan ] ]) do={ - :do { - :set $Vlan ([ /interface/bridge/vlan/get [ find where comment=$Vlan ] vlan-ids ]->0); - } on-error={ - $LogPrintExit2 warning $0 ("Could not find VLAN '" . $Vlan . "' for interface " . $BridgePortVal->"interface" . "!") true; - } - } - :if ($BridgePortVal->"disabled" = true || $Vlan != $BridgePortVal->"pvid") do={ - $LogPrintExit2 info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $ConfigTo . \ - " vlan " . $Vlan . [ $IfThenElse ($Vlan != $VlanName) (" (" . $VlanName . ")") ] . ", disabling dhcp client.") false; - :if ([ :len $DHCPClient ] = 1) do={ - /ip/dhcp-client/disable $DHCPClient; - :delay 200ms; - } - :local Disable [ /interface/ethernet/find where name=$BridgePortVal->"interface" ]; - :if ([ :len $Disable ] > 0) do={ - /interface/ethernet/disable $Disable; - :set InterfaceReEnable ($InterfaceReEnable, $Disable); - } - /interface/bridge/port/set disabled=no pvid=$Vlan $BridgePort; - } else={ - $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $ConfigTo . \ - " vlan " . $Vlan . ".") false; - } - } - } - } - } - :if ([ :len $InterfaceReEnable ] > 0) do={ - :delay 2s; - $LogPrintExit2 info $0 ("Re-enabling interfaces...") false; - /interface/ethernet/enable $InterfaceReEnable; - } -} +# dummy for migration diff --git a/mod/bridge-port-vlan.rsc b/mod/bridge-port-vlan.rsc new file mode 100644 index 0000000..8fb64e1 --- /dev/null +++ b/mod/bridge-port-vlan.rsc @@ -0,0 +1,73 @@ +#!rsc by RouterOS +# RouterOS script: mod/bridge-port-vlan +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# manage VLANs on bridge ports +# https://git.eworm.de/cgit/routeros-scripts/about/doc/mod/bridge-port-vlan.md + +:global BridgePortVlan; + +:global BridgePortVlan do={ + :local ConfigTo [ :tostr $1 ]; + + :global IfThenElse; + :global LogPrintExit2; + :global ParseKeyValueStore; + + :local InterfaceReEnable ({}); + :foreach BridgePort in=[ /interface/bridge/port/find where !(comment=[]) ] do={ + :local BridgePortVal [ /interface/bridge/port/get $BridgePort ]; + :foreach Config,Vlan in=[ $ParseKeyValueStore ($BridgePortVal->"comment") ] do={ + :if ($Config = $ConfigTo) do={ + :local DHCPClient [ /ip/dhcp-client/find where interface=$BridgePortVal->"interface" comment="toggle with bridge port" ]; + + :if ($Vlan = "dhcp-client") do={ + :if ([ :len $DHCPClient ] != 1) do={ + $LogPrintExit2 warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \ + " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!") true; + } + :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ]; + + :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={ + $LogPrintExit2 info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client.") false; + /interface/bridge/port/disable $BridgePort; + :delay 200ms; + /ip/dhcp-client/enable $DHCPClient; + } + } else={ + :local VlanName $Vlan; + :if ($Vlan != [ :tostr [ :tonum $Vlan ] ]) do={ + :do { + :set $Vlan ([ /interface/bridge/vlan/get [ find where comment=$Vlan ] vlan-ids ]->0); + } on-error={ + $LogPrintExit2 warning $0 ("Could not find VLAN '" . $Vlan . "' for interface " . $BridgePortVal->"interface" . "!") true; + } + } + :if ($BridgePortVal->"disabled" = true || $Vlan != $BridgePortVal->"pvid") do={ + $LogPrintExit2 info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $ConfigTo . \ + " vlan " . $Vlan . [ $IfThenElse ($Vlan != $VlanName) (" (" . $VlanName . ")") ] . ", disabling dhcp client.") false; + :if ([ :len $DHCPClient ] = 1) do={ + /ip/dhcp-client/disable $DHCPClient; + :delay 200ms; + } + :local Disable [ /interface/ethernet/find where name=$BridgePortVal->"interface" ]; + :if ([ :len $Disable ] > 0) do={ + /interface/ethernet/disable $Disable; + :set InterfaceReEnable ($InterfaceReEnable, $Disable); + } + /interface/bridge/port/set disabled=no pvid=$Vlan $BridgePort; + } else={ + $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $ConfigTo . \ + " vlan " . $Vlan . ".") false; + } + } + } + } + } + :if ([ :len $InterfaceReEnable ] > 0) do={ + :delay 2s; + $LogPrintExit2 info $0 ("Re-enabling interfaces...") false; + /interface/ethernet/enable $InterfaceReEnable; + } +} diff --git a/mod/inspectvar b/mod/inspectvar index 8bb5c5f..2da00ca 100644 --- a/mod/inspectvar +++ b/mod/inspectvar @@ -1,54 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/inspectvar -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md - -:global InspectVar; -:global InspectVarReturn; - -# inspect variable and print on terminal -:set InspectVar do={ - :global InspectVarReturn; - :global PrettyPrint; - - $PrettyPrint [ $InspectVarReturn $1 ]; -} - -# inspect variable and return formatted string -:set InspectVarReturn do={ - :local Input $1; - :local Level (0 + [ :tonum $2 ]); - - :global IfThenElse; - :global InspectVarReturn; - - :local IndentReturn do={ - :local Prefix [ :tostr $1 ]; - :local Value [ :tostr $2 ]; - :local Level [ :tonum $3 ]; - - :local Indent ""; - :for I from=1 to=$Level step=1 do={ - :set Indent ($Indent . " "); - } - :return ($Indent . "-" . $Prefix . "-> " . $Value); - } - - :local TypeOf [ :typeof $Input ]; - :local Return [ $IndentReturn "type" $TypeOf $Level ]; - - :if ($TypeOf = "array") do={ - :foreach Key,Value in=$Input do={ - :set $Return ($Return . "\n" . \ - [ $IndentReturn "key" $Key ($Level + 1) ] . "\n" . \ - [ $InspectVarReturn $Value ($Level + 2) ]); - } - } else={ - :if ($TypeOf != "nothing") do={ - :set $Return ($Return . "\n" . \ - [ $IndentReturn "value" [ $IfThenElse ([ :len $Input ] > 80) \ - ([ :pick $Input 0 77 ] . "...") $Input ] $Level ]); - } - } - :return $Return; -} +# +# dummy for migration diff --git a/mod/inspectvar.rsc b/mod/inspectvar.rsc new file mode 100644 index 0000000..8bb5c5f --- /dev/null +++ b/mod/inspectvar.rsc @@ -0,0 +1,54 @@ +#!rsc by RouterOS +# RouterOS script: mod/inspectvar +# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +:global InspectVar; +:global InspectVarReturn; + +# inspect variable and print on terminal +:set InspectVar do={ + :global InspectVarReturn; + :global PrettyPrint; + + $PrettyPrint [ $InspectVarReturn $1 ]; +} + +# inspect variable and return formatted string +:set InspectVarReturn do={ + :local Input $1; + :local Level (0 + [ :tonum $2 ]); + + :global IfThenElse; + :global InspectVarReturn; + + :local IndentReturn do={ + :local Prefix [ :tostr $1 ]; + :local Value [ :tostr $2 ]; + :local Level [ :tonum $3 ]; + + :local Indent ""; + :for I from=1 to=$Level step=1 do={ + :set Indent ($Indent . " "); + } + :return ($Indent . "-" . $Prefix . "-> " . $Value); + } + + :local TypeOf [ :typeof $Input ]; + :local Return [ $IndentReturn "type" $TypeOf $Level ]; + + :if ($TypeOf = "array") do={ + :foreach Key,Value in=$Input do={ + :set $Return ($Return . "\n" . \ + [ $IndentReturn "key" $Key ($Level + 1) ] . "\n" . \ + [ $InspectVarReturn $Value ($Level + 2) ]); + } + } else={ + :if ($TypeOf != "nothing") do={ + :set $Return ($Return . "\n" . \ + [ $IndentReturn "value" [ $IfThenElse ([ :len $Input ] > 80) \ + ([ :pick $Input 0 77 ] . "...") $Input ] $Level ]); + } + } + :return $Return; +} @@ -1,46 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/ipcalc -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md - -:global IPCalc; -:global IPCalcReturn; - -# print netmask, network, min host, max host and broadcast -:set IPCalc do={ - :local Input [ :tostr $1 ]; - - :global IPCalcReturn; - :global PrettyPrint; - - :local Values [ $IPCalcReturn $1 ]; - - $PrettyPrint ( \ - "Address: " . $Values->"address" . "\n" . \ - "Netmask: " . $Values->"netmask" . "\n" . \ - "Network: " . $Values->"network" . "\n" . \ - "HostMin: " . $Values->"hostmin" . "\n" . \ - "HostMax: " . $Values->"hostmax" . "\n" . \ - "Broadcast: " . $Values->"broadcast"); -} - -# calculate and return netmask, network, min host, max host and broadcast -:set IPCalcReturn do={ - :local Input [ :tostr $1 ]; - :local Address [ :toip [ :pick $Input 0 [ :find $Input "/" ] ] ]; - :local Bits [ :tonum [ :pick $Input ([ :find $Input "/" ] + 1) [ :len $Input ] ] ]; - :local Mask ((255.255.255.255 << (32 - $Bits)) & 255.255.255.255); - - :local Return { - "address"=$Address; - "netmask"=$Mask; - "networkaddress"=($Address & $Mask); - "networkbits"=$Bits; - "network"=(($Address & $Mask) . "/" . $Bits); - "hostmin"=(($Address & $Mask) | 0.0.0.1); - "hostmax"=(($Address | ~$Mask) ^ 0.0.0.1); - "broadcast"=($Address | ~$Mask); - } - - :return $Return; -} +# +# dummy for migration diff --git a/mod/ipcalc.rsc b/mod/ipcalc.rsc new file mode 100644 index 0000000..92e246f --- /dev/null +++ b/mod/ipcalc.rsc @@ -0,0 +1,46 @@ +#!rsc by RouterOS +# RouterOS script: mod/ipcalc +# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +:global IPCalc; +:global IPCalcReturn; + +# print netmask, network, min host, max host and broadcast +:set IPCalc do={ + :local Input [ :tostr $1 ]; + + :global IPCalcReturn; + :global PrettyPrint; + + :local Values [ $IPCalcReturn $1 ]; + + $PrettyPrint ( \ + "Address: " . $Values->"address" . "\n" . \ + "Netmask: " . $Values->"netmask" . "\n" . \ + "Network: " . $Values->"network" . "\n" . \ + "HostMin: " . $Values->"hostmin" . "\n" . \ + "HostMax: " . $Values->"hostmax" . "\n" . \ + "Broadcast: " . $Values->"broadcast"); +} + +# calculate and return netmask, network, min host, max host and broadcast +:set IPCalcReturn do={ + :local Input [ :tostr $1 ]; + :local Address [ :toip [ :pick $Input 0 [ :find $Input "/" ] ] ]; + :local Bits [ :tonum [ :pick $Input ([ :find $Input "/" ] + 1) [ :len $Input ] ] ]; + :local Mask ((255.255.255.255 << (32 - $Bits)) & 255.255.255.255); + + :local Return { + "address"=$Address; + "netmask"=$Mask; + "networkaddress"=($Address & $Mask); + "networkbits"=$Bits; + "network"=(($Address & $Mask) . "/" . $Bits); + "hostmin"=(($Address & $Mask) | 0.0.0.1); + "hostmax"=(($Address | ~$Mask) ^ 0.0.0.1); + "broadcast"=($Address | ~$Mask); + } + + :return $Return; +} diff --git a/mod/notification-email b/mod/notification-email index b03e176..2da00ca 100644 --- a/mod/notification-email +++ b/mod/notification-email @@ -1,206 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/notification-email -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md - -:global FlushEmailQueue; -:global LogForwardFilterLogForwarding; -:global NotificationEMailSubject; -:global NotificationFunctions; -:global QuotedPrintable; -:global SendEMail; -:global SendEMail2; - -# flush e-mail queue -:set FlushEmailQueue do={ - :global EmailQueue; - - :global EitherOr; - :global IsDNSResolving; - :global IsTimeSync; - :global LogPrintExit2; - - :local AllDone true; - :local QueueLen [ :len $EmailQueue ]; - :local Scheduler [ /system/scheduler/find where name=$0 ]; - - :if ([ :len $Scheduler ] > 0 && [ /system/scheduler/get $Scheduler interval ] < 1m) do={ - /system/scheduler/set interval=1m comment="Doing initial checks..." $Scheduler; - } - - :if ([ /tool/e-mail/get last-status ] = "in-progress") do={ - $LogPrintExit2 debug $0 ("Sending mail is currently in progress, not flushing.") false; - :return false; - } - - :if ([ $IsTimeSync ] = false) do={ - $LogPrintExit2 debug $0 ("Time is not synced, not flushing.") false; - :return false; - } - - :if ([ :typeof [ :toip [ /tool/e-mail/get address ] ] ] != "ip" && [ $IsDNSResolving ] = false) do={ - $LogPrintExit2 debug $0 ("Server address is a DNS name and resolving fails, not flushing.") false; - :return false; - } - - :if ([ :len $Scheduler ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.") false; - } - - /system/scheduler/set interval=([ $EitherOr $QueueLen 1 ] . "m") comment="Sending..." $Scheduler; - - :foreach Id,Message in=$EmailQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - :local Attach ({}); - :while ([ /tool/e-mail/get last-status ] = "in-progress") do={ :delay 1s; } - :foreach File in=[ :toarray [ $EitherOr ($Message->"attach") "" ] ] do={ - :if ([ :len [ /file/find where name=$File ] ] = 1) do={ - :set Attach ($Attach, $File); - } else={ - $LogPrintExit2 warning $0 ("File '" . $File . "' does not exist, can not attach.") false; - } - } - /tool/e-mail/send to=($Message->"to") cc=($Message->"cc") subject=($Message->"subject") \ - body=($Message->"body") file=$Attach; - :local Wait true; - :do { - :delay 1s; - :local Status [ /tool/e-mail/get last-status ]; - :if ($Status = "succeeded") do={ - :set ($EmailQueue->$Id); - :set Wait false; - :if (($Message->"remove-attach") = true) do={ - :foreach File in=$Attach do={ - /file/remove $File; - } - } - } - :if ($Status = "failed") do={ - :set AllDone false; - :set Wait false; - } - } while=($Wait = true); - } - } - - :if ($AllDone = true && $QueueLen = [ :len $EmailQueue ]) do={ - /system/scheduler/remove $Scheduler; - :set EmailQueue; - } else={ - /system/scheduler/set interval=1m comment="Waiting for retry..." $Scheduler; - } -} - -# generate filter for log-forward -:set LogForwardFilterLogForwarding do={ - :global EscapeForRegEx; - :global NotificationEMailSubject; - :global SymbolForNotification; - - :return ("^Error sending e-mail <(" . \ - [ $EscapeForRegEx [ $NotificationEMailSubject ([ $SymbolForNotification \ - "memo" ] . "Log Forwarding") ] ] . "|" . \ - [ $EscapeForRegEx [ $NotificationEMailSubject ([ $SymbolForNotification \ - "warning-sign" ] . "Log Forwarding") ] ] . ")>:"); -} - -# generate the e-mail subject -:set NotificationEMailSubject do={ - :global Identity; - :global IdentityExtra; - - :global QuotedPrintable; - - :return [ $QuotedPrintable ("[" . $IdentityExtra . $Identity . "] " . $1) ]; -} - -# send notification via e-mail - expects one array argument -:set ($NotificationFunctions->"email") do={ - :local Notification $1; - - :global EmailGeneralTo; - :global EmailGeneralToOverride; - :global EmailGeneralCc; - :global EmailGeneralCcOverride; - :global EmailQueue; - - :global EitherOr; - :global IfThenElse; - :global NotificationEMailSubject; - - :local To [ $EitherOr ($EmailGeneralToOverride->($Notification->"origin")) $EmailGeneralTo ]; - :local Cc [ $EitherOr ($EmailGeneralCcOverride->($Notification->"origin")) $EmailGeneralCc ]; - - :local EMailSettings [ /tool/e-mail/get ]; - :if ([ :len $To ] = 0 || ($EMailSettings->"address") = "0.0.0.0" || ($EMailSettings->"from") = "<>") do={ - :return false; - } - - :if ([ :typeof $EmailQueue ] = "nothing") do={ - :set EmailQueue ({}); - } - :local Signature [ /system/note/get note ]; - :set ($EmailQueue->[ :len $EmailQueue ]) { - to=$To; cc=$Cc; - subject=[ $NotificationEMailSubject ($Notification->"subject") ]; - body=(($Notification->"message") . \ - [ $IfThenElse ([ :len ($Notification->"link") ] > 0) ("\n\n" . ($Notification->"link")) "" ] . \ - [ $IfThenElse ([ :len $Signature ] > 0) ("\n-- \n" . $Signature) "" ]); \ - attach=($Notification->"attach"); remove-attach=($Notification->"remove-attach") }; - :if ([ :len [ /system/scheduler/find where name="\$FlushEmailQueue" ] ] = 0) do={ - /system/scheduler/add name="\$FlushEmailQueue" interval=1s start-time=startup \ - comment="Queuing new mail..." on-event=(":global FlushEmailQueue; \$FlushEmailQueue;"); - } -} - -# convert string to quoted-printable -:global QuotedPrintable do={ - :local Input [ :tostr $1 ]; - - :if ([ :len $Input ] = 0) do={ - :return $Input; - } - - :local Return ""; - :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" . \ - "\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" . \ - "\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" . \ - "\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" . \ - "\EC\ED\EE\EF\F0\F1\F2\F3\F4\F5\F6\F7\F8\F9\FA\FB\FC\FD\FE\FF"); - :local Hex { "0"; "1"; "2"; "3"; "4"; "5"; "6"; "7"; "8"; "9"; "A"; "B"; "C"; "D"; "E"; "F" }; - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :local Replace [ :find $Chars $Char ]; - - :if ($Char = "=") do={ - :set Char "=3D"; - } - :if ([ :typeof $Replace ] = "num") do={ - :set Char ("=" . ($Hex->($Replace / 16 + 8)) . ($Hex->($Replace % 16))); - } - :set Return ($Return . $Char); - } - - :if ($Input = $Return) do={ - :return $Input; - } - - :return ("=\?utf-8\?Q\?" . $Return . "\?="); -} - -# send notification via e-mail - expects at least two string arguments -:set SendEMail do={ - :global SendEMail2; - - $SendEMail2 ({ subject=$1; message=$2; link=$3 }); -} - -# send notification via e-mail - expects one array argument -:set SendEMail2 do={ - :local Notification $1; - - :global NotificationFunctions; - - ($NotificationFunctions->"email") ("\$NotificationFunctions->\"email\"") $Notification; -} +# +# dummy for migration diff --git a/mod/notification-email.rsc b/mod/notification-email.rsc new file mode 100644 index 0000000..b03e176 --- /dev/null +++ b/mod/notification-email.rsc @@ -0,0 +1,206 @@ +#!rsc by RouterOS +# RouterOS script: mod/notification-email +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +:global FlushEmailQueue; +:global LogForwardFilterLogForwarding; +:global NotificationEMailSubject; +:global NotificationFunctions; +:global QuotedPrintable; +:global SendEMail; +:global SendEMail2; + +# flush e-mail queue +:set FlushEmailQueue do={ + :global EmailQueue; + + :global EitherOr; + :global IsDNSResolving; + :global IsTimeSync; + :global LogPrintExit2; + + :local AllDone true; + :local QueueLen [ :len $EmailQueue ]; + :local Scheduler [ /system/scheduler/find where name=$0 ]; + + :if ([ :len $Scheduler ] > 0 && [ /system/scheduler/get $Scheduler interval ] < 1m) do={ + /system/scheduler/set interval=1m comment="Doing initial checks..." $Scheduler; + } + + :if ([ /tool/e-mail/get last-status ] = "in-progress") do={ + $LogPrintExit2 debug $0 ("Sending mail is currently in progress, not flushing.") false; + :return false; + } + + :if ([ $IsTimeSync ] = false) do={ + $LogPrintExit2 debug $0 ("Time is not synced, not flushing.") false; + :return false; + } + + :if ([ :typeof [ :toip [ /tool/e-mail/get address ] ] ] != "ip" && [ $IsDNSResolving ] = false) do={ + $LogPrintExit2 debug $0 ("Server address is a DNS name and resolving fails, not flushing.") false; + :return false; + } + + :if ([ :len $Scheduler ] > 0 && $QueueLen = 0) do={ + $LogPrintExit2 warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.") false; + } + + /system/scheduler/set interval=([ $EitherOr $QueueLen 1 ] . "m") comment="Sending..." $Scheduler; + + :foreach Id,Message in=$EmailQueue do={ + :if ([ :typeof $Message ] = "array" ) do={ + :local Attach ({}); + :while ([ /tool/e-mail/get last-status ] = "in-progress") do={ :delay 1s; } + :foreach File in=[ :toarray [ $EitherOr ($Message->"attach") "" ] ] do={ + :if ([ :len [ /file/find where name=$File ] ] = 1) do={ + :set Attach ($Attach, $File); + } else={ + $LogPrintExit2 warning $0 ("File '" . $File . "' does not exist, can not attach.") false; + } + } + /tool/e-mail/send to=($Message->"to") cc=($Message->"cc") subject=($Message->"subject") \ + body=($Message->"body") file=$Attach; + :local Wait true; + :do { + :delay 1s; + :local Status [ /tool/e-mail/get last-status ]; + :if ($Status = "succeeded") do={ + :set ($EmailQueue->$Id); + :set Wait false; + :if (($Message->"remove-attach") = true) do={ + :foreach File in=$Attach do={ + /file/remove $File; + } + } + } + :if ($Status = "failed") do={ + :set AllDone false; + :set Wait false; + } + } while=($Wait = true); + } + } + + :if ($AllDone = true && $QueueLen = [ :len $EmailQueue ]) do={ + /system/scheduler/remove $Scheduler; + :set EmailQueue; + } else={ + /system/scheduler/set interval=1m comment="Waiting for retry..." $Scheduler; + } +} + +# generate filter for log-forward +:set LogForwardFilterLogForwarding do={ + :global EscapeForRegEx; + :global NotificationEMailSubject; + :global SymbolForNotification; + + :return ("^Error sending e-mail <(" . \ + [ $EscapeForRegEx [ $NotificationEMailSubject ([ $SymbolForNotification \ + "memo" ] . "Log Forwarding") ] ] . "|" . \ + [ $EscapeForRegEx [ $NotificationEMailSubject ([ $SymbolForNotification \ + "warning-sign" ] . "Log Forwarding") ] ] . ")>:"); +} + +# generate the e-mail subject +:set NotificationEMailSubject do={ + :global Identity; + :global IdentityExtra; + + :global QuotedPrintable; + + :return [ $QuotedPrintable ("[" . $IdentityExtra . $Identity . "] " . $1) ]; +} + +# send notification via e-mail - expects one array argument +:set ($NotificationFunctions->"email") do={ + :local Notification $1; + + :global EmailGeneralTo; + :global EmailGeneralToOverride; + :global EmailGeneralCc; + :global EmailGeneralCcOverride; + :global EmailQueue; + + :global EitherOr; + :global IfThenElse; + :global NotificationEMailSubject; + + :local To [ $EitherOr ($EmailGeneralToOverride->($Notification->"origin")) $EmailGeneralTo ]; + :local Cc [ $EitherOr ($EmailGeneralCcOverride->($Notification->"origin")) $EmailGeneralCc ]; + + :local EMailSettings [ /tool/e-mail/get ]; + :if ([ :len $To ] = 0 || ($EMailSettings->"address") = "0.0.0.0" || ($EMailSettings->"from") = "<>") do={ + :return false; + } + + :if ([ :typeof $EmailQueue ] = "nothing") do={ + :set EmailQueue ({}); + } + :local Signature [ /system/note/get note ]; + :set ($EmailQueue->[ :len $EmailQueue ]) { + to=$To; cc=$Cc; + subject=[ $NotificationEMailSubject ($Notification->"subject") ]; + body=(($Notification->"message") . \ + [ $IfThenElse ([ :len ($Notification->"link") ] > 0) ("\n\n" . ($Notification->"link")) "" ] . \ + [ $IfThenElse ([ :len $Signature ] > 0) ("\n-- \n" . $Signature) "" ]); \ + attach=($Notification->"attach"); remove-attach=($Notification->"remove-attach") }; + :if ([ :len [ /system/scheduler/find where name="\$FlushEmailQueue" ] ] = 0) do={ + /system/scheduler/add name="\$FlushEmailQueue" interval=1s start-time=startup \ + comment="Queuing new mail..." on-event=(":global FlushEmailQueue; \$FlushEmailQueue;"); + } +} + +# convert string to quoted-printable +:global QuotedPrintable do={ + :local Input [ :tostr $1 ]; + + :if ([ :len $Input ] = 0) do={ + :return $Input; + } + + :local Return ""; + :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" . \ + "\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" . \ + "\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" . \ + "\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" . \ + "\EC\ED\EE\EF\F0\F1\F2\F3\F4\F5\F6\F7\F8\F9\FA\FB\FC\FD\FE\FF"); + :local Hex { "0"; "1"; "2"; "3"; "4"; "5"; "6"; "7"; "8"; "9"; "A"; "B"; "C"; "D"; "E"; "F" }; + + :for I from=0 to=([ :len $Input ] - 1) do={ + :local Char [ :pick $Input $I ]; + :local Replace [ :find $Chars $Char ]; + + :if ($Char = "=") do={ + :set Char "=3D"; + } + :if ([ :typeof $Replace ] = "num") do={ + :set Char ("=" . ($Hex->($Replace / 16 + 8)) . ($Hex->($Replace % 16))); + } + :set Return ($Return . $Char); + } + + :if ($Input = $Return) do={ + :return $Input; + } + + :return ("=\?utf-8\?Q\?" . $Return . "\?="); +} + +# send notification via e-mail - expects at least two string arguments +:set SendEMail do={ + :global SendEMail2; + + $SendEMail2 ({ subject=$1; message=$2; link=$3 }); +} + +# send notification via e-mail - expects one array argument +:set SendEMail2 do={ + :local Notification $1; + + :global NotificationFunctions; + + ($NotificationFunctions->"email") ("\$NotificationFunctions->\"email\"") $Notification; +} diff --git a/mod/notification-matrix b/mod/notification-matrix index 6266b75..2da00ca 100644 --- a/mod/notification-matrix +++ b/mod/notification-matrix @@ -1,165 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/notification-matrix -# Copyright (c) 2013-2023 Michael Gisbers <michael@gisbers.de> -# Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md - -:global FlushMatrixQueue; -:global NotificationFunctions; -:global SendMatrix; -:global SendMatrix2; - -# flush Matrix queue -:set FlushMatrixQueue do={ - :global MatrixQueue; - - :global IsFullyConnected; - :global LogPrintExit2; - - :if ([ $IsFullyConnected ] = false) do={ - $LogPrintExit2 debug $0 ("System is not fully connected, not flushing.") false; - :return false; - } - - :local AllDone true; - :local QueueLen [ :len $MatrixQueue ]; - - :if ([ :len [ /system/scheduler/find where name=$0 ] ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing Matrix messages from scheduler, but queue is empty.") false; - } - - :foreach Id,Message in=$MatrixQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - :do { - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - ("https://" . $Message->"homeserver" . "/_matrix/client/r0/rooms/" . $Message->"room" . \ - "/send/m.room.message?access_token=" . $Message->"accesstoken") \ - http-data=("{ \"msgtype\": \"m.text\", \"body\": \"" . $Message->"plain" . "\"," . \ - "\"format\": \"org.matrix.custom.html\", \"formatted_body\": \"" . \ - $Message->"formatted" . "\" }") as-value; - :set ($MatrixQueue->$Id); - } on-error={ - $LogPrintExit2 debug $0 ("Sending queued Matrix message failed.") false; - :set AllDone false; - } - } - } - - :if ($AllDone = true && $QueueLen = [ :len $MatrixQueue ]) do={ - /system/scheduler/remove [ find where name=$0 ]; - :set MatrixQueue; - } -} - -# send notification via Matrix - expects one array argument -:set ($NotificationFunctions->"matrix") do={ - :local Notification $1; - - :global Identity; - :global IdentityExtra; - :global MatrixAccessToken; - :global MatrixAccessTokenOverride; - :global MatrixHomeServer; - :global MatrixHomeServerOverride; - :global MatrixQueue; - :global MatrixRoom; - :global MatrixRoomOverride; - - :global EitherOr; - :global LogPrintExit2; - :global SymbolForNotification; - - :local PrepareText do={ - :local Input [ :tostr $1 ]; - - :if ([ :len $Input ] = 0) do={ - :return ""; - } - - :local Return ""; - :local Chars { - "plain"={ "\\"; "\""; "\n" }; - "format"={ "\\"; "\""; "\n"; "&"; "<"; ">" }; - } - :local Subs { - "plain"={ "\\\\"; "\\\""; "\\n" }; - "format"={ "\\\\"; """; "<br/>"; "&"; "<"; ">" }; - } - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :local Replace [ :find ($Chars->$2) $Char ]; - - :if ([ :typeof $Replace ] = "num") do={ - :set Char ($Subs->$2->$Replace); - } - :set Return ($Return . $Char); - } - - :return $Return; - } - - :local AccessToken [ $EitherOr ($MatrixAccessTokenOverride->($Notification->"origin")) $MatrixAccessToken ]; - :local HomeServer [ $EitherOr ($MatrixHomeServerOverride->($Notification->"origin")) $MatrixHomeServer ]; - :local Room [ $EitherOr ($MatrixRoomOverride->($Notification->"origin")) $MatrixRoom ]; - - :if ([ :len $AccessToken ] = 0 || [ :len $HomeServer ] = 0 || [ :len $Room ] = 0) do={ - :return false; - } - - :local Plain [ $PrepareText ("## [" . $IdentityExtra . $Identity . "] " . \ - ($Notification->"subject") . "\n```\n" . ($Notification->"message") . "\n```") "plain" ]; - :local Formatted ("<h2>" . [ $PrepareText ("[" . $IdentityExtra . $Identity . "] " . \ - ($Notification->"subject")) "format" ] . "</h2>" . "<pre><code>" . \ - [ $PrepareText ($Notification->"message") "format" ] . "</code></pre>"); - :if ([ :len ($Notification->"link") ] > 0) do={ - :set Plain ($Plain . "\\n" . [ $SymbolForNotification "link" ] . \ - [ $PrepareText ("[" . $Notification->"link" . "](" . $Notification->"link" . ")") "plain" ]); - :set Formatted ($Formatted . "<br/>" . [ $SymbolForNotification "link" ] . \ - "<a href=\\\"" . [ $PrepareText ($Notification->"link") "format" ] . "\\\">" . \ - [ $PrepareText ($Notification->"link") "format" ] . "</a>"); - } - - :do { - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - ("https://" . $HomeServer . "/_matrix/client/r0/rooms/" . $Room . \ - "/send/m.room.message?access_token=" . $AccessToken) \ - http-data=("{ \"msgtype\": \"m.text\", \"body\": \"" . $Plain . "\"," . \ - "\"format\": \"org.matrix.custom.html\", \"formatted_body\": \"" . \ - $Formatted . "\" }") as-value; - } on-error={ - $LogPrintExit2 info $0 ("Failed sending Matrix notification! Queuing...") false; - - :if ([ :typeof $MatrixQueue ] = "nothing") do={ - :set MatrixQueue ({}); - } - :local Text ([ $SymbolForNotification "alarm-clock" ] . \ - "This message was queued since " . [ /system/clock/get date ] . \ - " " . [ /system/clock/get time ] . " and may be obsolete."); - :set Plain ($Plain . "\\n" . $Text); - :set Formatted ($Formatted . "<br/>" . $Text); - :set ($MatrixQueue->[ :len $MatrixQueue ]) { room=$Room; \ - accesstoken=$AccessToken; homeserver=$HomeServer; \ - plain=$Plain; formatted=$Formatted }; - :if ([ :len [ /system/scheduler/find where name="\$FlushMatrixQueue" ] ] = 0) do={ - /system/scheduler/add name="\$FlushMatrixQueue" interval=1m start-time=startup \ - on-event=(":global FlushMatrixQueue; \$FlushMatrixQueue;"); - } - } -} - -# send notification via Matrix - expects at least two string arguments -:set SendMatrix do={ - :global SendMatrix2; - - $SendMatrix2 ({ subject=$1; message=$2; link=$3 }); -} - -# send notification via Matrix - expects one array argument -:set SendMatrix2 do={ - :local Notification $1; - - :global NotificationFunctions; - - ($NotificationFunctions->"matrix") ("\$NotificationFunctions->\"matrix\"") $Notification; -} +# +# dummy for migration diff --git a/mod/notification-matrix.rsc b/mod/notification-matrix.rsc new file mode 100644 index 0000000..6266b75 --- /dev/null +++ b/mod/notification-matrix.rsc @@ -0,0 +1,165 @@ +#!rsc by RouterOS +# RouterOS script: mod/notification-matrix +# Copyright (c) 2013-2023 Michael Gisbers <michael@gisbers.de> +# Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +:global FlushMatrixQueue; +:global NotificationFunctions; +:global SendMatrix; +:global SendMatrix2; + +# flush Matrix queue +:set FlushMatrixQueue do={ + :global MatrixQueue; + + :global IsFullyConnected; + :global LogPrintExit2; + + :if ([ $IsFullyConnected ] = false) do={ + $LogPrintExit2 debug $0 ("System is not fully connected, not flushing.") false; + :return false; + } + + :local AllDone true; + :local QueueLen [ :len $MatrixQueue ]; + + :if ([ :len [ /system/scheduler/find where name=$0 ] ] > 0 && $QueueLen = 0) do={ + $LogPrintExit2 warning $0 ("Flushing Matrix messages from scheduler, but queue is empty.") false; + } + + :foreach Id,Message in=$MatrixQueue do={ + :if ([ :typeof $Message ] = "array" ) do={ + :do { + /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ + ("https://" . $Message->"homeserver" . "/_matrix/client/r0/rooms/" . $Message->"room" . \ + "/send/m.room.message?access_token=" . $Message->"accesstoken") \ + http-data=("{ \"msgtype\": \"m.text\", \"body\": \"" . $Message->"plain" . "\"," . \ + "\"format\": \"org.matrix.custom.html\", \"formatted_body\": \"" . \ + $Message->"formatted" . "\" }") as-value; + :set ($MatrixQueue->$Id); + } on-error={ + $LogPrintExit2 debug $0 ("Sending queued Matrix message failed.") false; + :set AllDone false; + } + } + } + + :if ($AllDone = true && $QueueLen = [ :len $MatrixQueue ]) do={ + /system/scheduler/remove [ find where name=$0 ]; + :set MatrixQueue; + } +} + +# send notification via Matrix - expects one array argument +:set ($NotificationFunctions->"matrix") do={ + :local Notification $1; + + :global Identity; + :global IdentityExtra; + :global MatrixAccessToken; + :global MatrixAccessTokenOverride; + :global MatrixHomeServer; + :global MatrixHomeServerOverride; + :global MatrixQueue; + :global MatrixRoom; + :global MatrixRoomOverride; + + :global EitherOr; + :global LogPrintExit2; + :global SymbolForNotification; + + :local PrepareText do={ + :local Input [ :tostr $1 ]; + + :if ([ :len $Input ] = 0) do={ + :return ""; + } + + :local Return ""; + :local Chars { + "plain"={ "\\"; "\""; "\n" }; + "format"={ "\\"; "\""; "\n"; "&"; "<"; ">" }; + } + :local Subs { + "plain"={ "\\\\"; "\\\""; "\\n" }; + "format"={ "\\\\"; """; "<br/>"; "&"; "<"; ">" }; + } + + :for I from=0 to=([ :len $Input ] - 1) do={ + :local Char [ :pick $Input $I ]; + :local Replace [ :find ($Chars->$2) $Char ]; + + :if ([ :typeof $Replace ] = "num") do={ + :set Char ($Subs->$2->$Replace); + } + :set Return ($Return . $Char); + } + + :return $Return; + } + + :local AccessToken [ $EitherOr ($MatrixAccessTokenOverride->($Notification->"origin")) $MatrixAccessToken ]; + :local HomeServer [ $EitherOr ($MatrixHomeServerOverride->($Notification->"origin")) $MatrixHomeServer ]; + :local Room [ $EitherOr ($MatrixRoomOverride->($Notification->"origin")) $MatrixRoom ]; + + :if ([ :len $AccessToken ] = 0 || [ :len $HomeServer ] = 0 || [ :len $Room ] = 0) do={ + :return false; + } + + :local Plain [ $PrepareText ("## [" . $IdentityExtra . $Identity . "] " . \ + ($Notification->"subject") . "\n```\n" . ($Notification->"message") . "\n```") "plain" ]; + :local Formatted ("<h2>" . [ $PrepareText ("[" . $IdentityExtra . $Identity . "] " . \ + ($Notification->"subject")) "format" ] . "</h2>" . "<pre><code>" . \ + [ $PrepareText ($Notification->"message") "format" ] . "</code></pre>"); + :if ([ :len ($Notification->"link") ] > 0) do={ + :set Plain ($Plain . "\\n" . [ $SymbolForNotification "link" ] . \ + [ $PrepareText ("[" . $Notification->"link" . "](" . $Notification->"link" . ")") "plain" ]); + :set Formatted ($Formatted . "<br/>" . [ $SymbolForNotification "link" ] . \ + "<a href=\\\"" . [ $PrepareText ($Notification->"link") "format" ] . "\\\">" . \ + [ $PrepareText ($Notification->"link") "format" ] . "</a>"); + } + + :do { + /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ + ("https://" . $HomeServer . "/_matrix/client/r0/rooms/" . $Room . \ + "/send/m.room.message?access_token=" . $AccessToken) \ + http-data=("{ \"msgtype\": \"m.text\", \"body\": \"" . $Plain . "\"," . \ + "\"format\": \"org.matrix.custom.html\", \"formatted_body\": \"" . \ + $Formatted . "\" }") as-value; + } on-error={ + $LogPrintExit2 info $0 ("Failed sending Matrix notification! Queuing...") false; + + :if ([ :typeof $MatrixQueue ] = "nothing") do={ + :set MatrixQueue ({}); + } + :local Text ([ $SymbolForNotification "alarm-clock" ] . \ + "This message was queued since " . [ /system/clock/get date ] . \ + " " . [ /system/clock/get time ] . " and may be obsolete."); + :set Plain ($Plain . "\\n" . $Text); + :set Formatted ($Formatted . "<br/>" . $Text); + :set ($MatrixQueue->[ :len $MatrixQueue ]) { room=$Room; \ + accesstoken=$AccessToken; homeserver=$HomeServer; \ + plain=$Plain; formatted=$Formatted }; + :if ([ :len [ /system/scheduler/find where name="\$FlushMatrixQueue" ] ] = 0) do={ + /system/scheduler/add name="\$FlushMatrixQueue" interval=1m start-time=startup \ + on-event=(":global FlushMatrixQueue; \$FlushMatrixQueue;"); + } + } +} + +# send notification via Matrix - expects at least two string arguments +:set SendMatrix do={ + :global SendMatrix2; + + $SendMatrix2 ({ subject=$1; message=$2; link=$3 }); +} + +# send notification via Matrix - expects one array argument +:set SendMatrix2 do={ + :local Notification $1; + + :global NotificationFunctions; + + ($NotificationFunctions->"matrix") ("\$NotificationFunctions->\"matrix\"") $Notification; +} diff --git a/mod/notification-telegram b/mod/notification-telegram index c90e3f0..2da00ca 100644 --- a/mod/notification-telegram +++ b/mod/notification-telegram @@ -1,176 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/notification-telegram -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md - -:global FlushTelegramQueue; -:global NotificationFunctions; -:global SendTelegram; -:global SendTelegram2; - -# flush telegram queue -:set FlushTelegramQueue do={ - :global TelegramQueue; - - :global IsFullyConnected; - :global LogPrintExit2; - - :if ([ $IsFullyConnected ] = false) do={ - $LogPrintExit2 debug $0 ("System is not fully connected, not flushing.") false; - :return false; - } - - :local AllDone true; - :local QueueLen [ :len $TelegramQueue ]; - - :if ([ :len [ /system/scheduler/find where name=$0 ] ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing Telegram messages from scheduler, but queue is empty.") false; - } - - :foreach Id,Message in=$TelegramQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - :do { - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - ("https://api.telegram.org/bot" . ($Message->"tokenid") . "/sendMessage") \ - http-data=("chat_id=" . ($Message->"chatid") . \ - "&disable_notification=" . ($Message->"silent") . \ - "&reply_to_message_id=" . ($Notification->"replyto") . \ - "&disable_web_page_preview=true&parse_mode=" . ($Message->"parsemode") . \ - "&text=" . ($Message->"text")) as-value; - :set ($TelegramQueue->$Id); - } on-error={ - $LogPrintExit2 debug $0 ("Sending queued Telegram message failed.") false; - :set AllDone false; - } - } - } - - :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={ - /system/scheduler/remove [ find where name=$0 ]; - :set TelegramQueue; - } -} - -# send notification via telegram - expects one array argument -:set ($NotificationFunctions->"telegram") do={ - :local Notification $1; - - :global Identity; - :global IdentityExtra; - :global TelegramChatId; - :global TelegramChatIdOverride; - :global TelegramFixedWidthFont; - :global TelegramQueue; - :global TelegramTokenId; - :global TelegramTokenIdOverride; - - :global CertificateAvailable; - :global CharacterReplace; - :global EitherOr; - :global IfThenElse; - :global LogPrintExit2; - :global SymbolForNotification; - :global UrlEncode; - - :local EscapeMD do={ - :global TelegramFixedWidthFont; - - :global CharacterReplace; - :global IfThenElse; - - :if ($TelegramFixedWidthFont != true) do={ - :return ($1 . [ $IfThenElse ($2 = "body") ("\n") "" ]); - } - - :local Return $1; - :local Chars { - "body"={ "\\"; "`" }; - "plain"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">"; - "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" }; - } - :foreach Char in=($Chars->$2) do={ - :set Return [ $CharacterReplace $Return $Char ("\\" . $Char) ]; - } - - :if ($2 = "body") do={ - :return ("```\n" . $Return . "\n```"); - } - - :return $Return; - } - - :local ChatId [ $EitherOr ($Notification->"chatid") \ - [ $EitherOr ($TelegramChatIdOverride->($Notification->"origin")) $TelegramChatId ] ]; - :local TokenId [ $EitherOr ($TelegramTokenIdOverride->($Notification->"origin")) $TelegramTokenId ]; - - :if ([ :len $TokenId ] = 0 || [ :len $ChatId ] = 0) do={ - :return false; - } - - :local Truncated false; - :local Text ("*__" . [ $EscapeMD ("[" . $IdentityExtra . $Identity . "] " . \ - ($Notification->"subject")) "plain" ] . "__*\n\n"); - :local LenSubject [ :len $Text ]; - :local LenMessage [ :len ($Notification->"message") ]; - :local LenLink [ :len ($Notification->"link") ]; - :local LenSum ($LenSubject + $LenMessage + $LenLink); - :if ($LenSum > 3968) do={ - :set Text ($Text . [ $EscapeMD ([ :pick ($Notification->"message") 0 (3840 - $LenSubject - $LenLink) ] . "...") "body" ]); - :set Truncated true; - } else={ - :set Text ($Text . [ $EscapeMD ($Notification->"message") "body" ]); - } - :if ($LenLink > 0) do={ - :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . [ $EscapeMD ($Notification->"link") "plain" ]); - } - :if ($Truncated = true) do={ - :set Text ($Text . "\n" . [ $SymbolForNotification "scissors" ] . \ - [ $EscapeMD ("The message was too long and has been truncated, cut off " . \ - (($LenSum - [ :len $Text ]) * 100 / $LenSum) . "%!") "plain" ]); - } - :set Text [ $UrlEncode $Text ]; - :local ParseMode [ $IfThenElse ($TelegramFixedWidthFont = true) "MarkdownV2" "" ]; - - :do { - :if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; - } - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - ("https://api.telegram.org/bot" . $TokenId . "/sendMessage") \ - http-data=("chat_id=" . $ChatId . "&disable_notification=" . ($Notification->"silent") . \ - "&reply_to_message_id=" . ($Notification->"replyto") . \ - "&disable_web_page_preview=true&parse_mode=" . $ParseMode . "&text=" . $Text) as-value; - } on-error={ - $LogPrintExit2 info $0 ("Failed sending telegram notification! Queuing...") false; - - :if ([ :typeof $TelegramQueue ] = "nothing") do={ - :set TelegramQueue ({}); - } - :set Text ($Text . [ $UrlEncode ("\n" . [ $SymbolForNotification "alarm-clock" ] . \ - [ $EscapeMD ("This message was queued since " . [ /system/clock/get date ] . \ - " " . [ /system/clock/get time ] . " and may be obsolete.") "plain" ]) ]); - :set ($TelegramQueue->[ :len $TelegramQueue ]) { chatid=$ChatId; tokenid=$TokenId; - parsemode=$ParseMode; text=$Text; silent=($Notification->"silent"); - replyto=($Notification->"replyto") }; - :if ([ :len [ /system/scheduler/find where name="\$FlushTelegramQueue" ] ] = 0) do={ - /system/scheduler/add name="\$FlushTelegramQueue" interval=1m start-time=startup \ - on-event=(":global FlushTelegramQueue; \$FlushTelegramQueue;"); - } - } -} - -# send notification via telegram - expects at least two string arguments -:set SendTelegram do={ - :global SendTelegram2; - - $SendTelegram2 ({ subject=$1; message=$2; link=$3; silent=$4 }); -} - -# send notification via telegram - expects one array argument -:set SendTelegram2 do={ - :local Notification $1; - - :global NotificationFunctions; - - ($NotificationFunctions->"telegram") ("\$NotificationFunctions->\"telegram\"") $Notification; -} +# +# dummy for migration diff --git a/mod/notification-telegram.rsc b/mod/notification-telegram.rsc new file mode 100644 index 0000000..c90e3f0 --- /dev/null +++ b/mod/notification-telegram.rsc @@ -0,0 +1,176 @@ +#!rsc by RouterOS +# RouterOS script: mod/notification-telegram +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +:global FlushTelegramQueue; +:global NotificationFunctions; +:global SendTelegram; +:global SendTelegram2; + +# flush telegram queue +:set FlushTelegramQueue do={ + :global TelegramQueue; + + :global IsFullyConnected; + :global LogPrintExit2; + + :if ([ $IsFullyConnected ] = false) do={ + $LogPrintExit2 debug $0 ("System is not fully connected, not flushing.") false; + :return false; + } + + :local AllDone true; + :local QueueLen [ :len $TelegramQueue ]; + + :if ([ :len [ /system/scheduler/find where name=$0 ] ] > 0 && $QueueLen = 0) do={ + $LogPrintExit2 warning $0 ("Flushing Telegram messages from scheduler, but queue is empty.") false; + } + + :foreach Id,Message in=$TelegramQueue do={ + :if ([ :typeof $Message ] = "array" ) do={ + :do { + /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ + ("https://api.telegram.org/bot" . ($Message->"tokenid") . "/sendMessage") \ + http-data=("chat_id=" . ($Message->"chatid") . \ + "&disable_notification=" . ($Message->"silent") . \ + "&reply_to_message_id=" . ($Notification->"replyto") . \ + "&disable_web_page_preview=true&parse_mode=" . ($Message->"parsemode") . \ + "&text=" . ($Message->"text")) as-value; + :set ($TelegramQueue->$Id); + } on-error={ + $LogPrintExit2 debug $0 ("Sending queued Telegram message failed.") false; + :set AllDone false; + } + } + } + + :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={ + /system/scheduler/remove [ find where name=$0 ]; + :set TelegramQueue; + } +} + +# send notification via telegram - expects one array argument +:set ($NotificationFunctions->"telegram") do={ + :local Notification $1; + + :global Identity; + :global IdentityExtra; + :global TelegramChatId; + :global TelegramChatIdOverride; + :global TelegramFixedWidthFont; + :global TelegramQueue; + :global TelegramTokenId; + :global TelegramTokenIdOverride; + + :global CertificateAvailable; + :global CharacterReplace; + :global EitherOr; + :global IfThenElse; + :global LogPrintExit2; + :global SymbolForNotification; + :global UrlEncode; + + :local EscapeMD do={ + :global TelegramFixedWidthFont; + + :global CharacterReplace; + :global IfThenElse; + + :if ($TelegramFixedWidthFont != true) do={ + :return ($1 . [ $IfThenElse ($2 = "body") ("\n") "" ]); + } + + :local Return $1; + :local Chars { + "body"={ "\\"; "`" }; + "plain"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">"; + "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" }; + } + :foreach Char in=($Chars->$2) do={ + :set Return [ $CharacterReplace $Return $Char ("\\" . $Char) ]; + } + + :if ($2 = "body") do={ + :return ("```\n" . $Return . "\n```"); + } + + :return $Return; + } + + :local ChatId [ $EitherOr ($Notification->"chatid") \ + [ $EitherOr ($TelegramChatIdOverride->($Notification->"origin")) $TelegramChatId ] ]; + :local TokenId [ $EitherOr ($TelegramTokenIdOverride->($Notification->"origin")) $TelegramTokenId ]; + + :if ([ :len $TokenId ] = 0 || [ :len $ChatId ] = 0) do={ + :return false; + } + + :local Truncated false; + :local Text ("*__" . [ $EscapeMD ("[" . $IdentityExtra . $Identity . "] " . \ + ($Notification->"subject")) "plain" ] . "__*\n\n"); + :local LenSubject [ :len $Text ]; + :local LenMessage [ :len ($Notification->"message") ]; + :local LenLink [ :len ($Notification->"link") ]; + :local LenSum ($LenSubject + $LenMessage + $LenLink); + :if ($LenSum > 3968) do={ + :set Text ($Text . [ $EscapeMD ([ :pick ($Notification->"message") 0 (3840 - $LenSubject - $LenLink) ] . "...") "body" ]); + :set Truncated true; + } else={ + :set Text ($Text . [ $EscapeMD ($Notification->"message") "body" ]); + } + :if ($LenLink > 0) do={ + :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . [ $EscapeMD ($Notification->"link") "plain" ]); + } + :if ($Truncated = true) do={ + :set Text ($Text . "\n" . [ $SymbolForNotification "scissors" ] . \ + [ $EscapeMD ("The message was too long and has been truncated, cut off " . \ + (($LenSum - [ :len $Text ]) * 100 / $LenSum) . "%!") "plain" ]); + } + :set Text [ $UrlEncode $Text ]; + :local ParseMode [ $IfThenElse ($TelegramFixedWidthFont = true) "MarkdownV2" "" ]; + + :do { + :if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={ + $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; + } + /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ + ("https://api.telegram.org/bot" . $TokenId . "/sendMessage") \ + http-data=("chat_id=" . $ChatId . "&disable_notification=" . ($Notification->"silent") . \ + "&reply_to_message_id=" . ($Notification->"replyto") . \ + "&disable_web_page_preview=true&parse_mode=" . $ParseMode . "&text=" . $Text) as-value; + } on-error={ + $LogPrintExit2 info $0 ("Failed sending telegram notification! Queuing...") false; + + :if ([ :typeof $TelegramQueue ] = "nothing") do={ + :set TelegramQueue ({}); + } + :set Text ($Text . [ $UrlEncode ("\n" . [ $SymbolForNotification "alarm-clock" ] . \ + [ $EscapeMD ("This message was queued since " . [ /system/clock/get date ] . \ + " " . [ /system/clock/get time ] . " and may be obsolete.") "plain" ]) ]); + :set ($TelegramQueue->[ :len $TelegramQueue ]) { chatid=$ChatId; tokenid=$TokenId; + parsemode=$ParseMode; text=$Text; silent=($Notification->"silent"); + replyto=($Notification->"replyto") }; + :if ([ :len [ /system/scheduler/find where name="\$FlushTelegramQueue" ] ] = 0) do={ + /system/scheduler/add name="\$FlushTelegramQueue" interval=1m start-time=startup \ + on-event=(":global FlushTelegramQueue; \$FlushTelegramQueue;"); + } + } +} + +# send notification via telegram - expects at least two string arguments +:set SendTelegram do={ + :global SendTelegram2; + + $SendTelegram2 ({ subject=$1; message=$2; link=$3; silent=$4 }); +} + +# send notification via telegram - expects one array argument +:set SendTelegram2 do={ + :local Notification $1; + + :global NotificationFunctions; + + ($NotificationFunctions->"telegram") ("\$NotificationFunctions->\"telegram\"") $Notification; +} diff --git a/mod/scriptrunonce b/mod/scriptrunonce index 7b87b4c..2da00ca 100644 --- a/mod/scriptrunonce +++ b/mod/scriptrunonce @@ -1,46 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mod/scriptrunonece -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md - -:global ScriptRunOnce; - -# fetch and run script(s) once -:set ScriptRunOnce do={ - :local Scripts [ :toarray $1 ]; - - :global ScriptRunOnceBaseUrl; - :global ScriptRunOnceUrlSuffix; - - :global LogPrintExit2; - :global ValidateSyntax; - - :foreach Script in=$Scripts do={ - :if (!($Script ~ "^(ftp|https\?|sftp)://")) do={ - :if ([ :len $ScriptRunOnceBaseUrl ] = 0) do={ - $LogPrintExit2 warning $0 ("Script '" . $Script . "' is not an url and base url is not available.") true; - } - :set Script ($ScriptRunOnceBaseUrl . $Script . $ScriptRunOnceUrlSuffix); - } - - :local Source; - :do { - :set Source ([ /tool/fetch check-certificate=yes-without-crl $Script output=user as-value ]->"data"); - } on-error={ - $LogPrintExit2 warning $0 ("Failed fetching script '" . $Script . "'!") false; - } - - :if ([ :len $Source ] > 0) do={ - :if ([ $ValidateSyntax $Source ] = true) do={ - :do { - $LogPrintExit2 info $0 ("Running script '" . $Script . "' now.") false; - [ :parse $Source ]; - } on-error={ - $LogPrintExit2 warning $0 ("The script '" . $Script . "' failed to run!") false; - } - } else={ - $LogPrintExit2 warning $0 ("The script '" . $Script . "' failed syntax validation!") false; - } - } - } -} +# +# dummy for migration diff --git a/mod/scriptrunonce.rsc b/mod/scriptrunonce.rsc new file mode 100644 index 0000000..96c49c3 --- /dev/null +++ b/mod/scriptrunonce.rsc @@ -0,0 +1,46 @@ +#!rsc by RouterOS +# RouterOS script: mod/scriptrunonece +# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +:global ScriptRunOnce; + +# fetch and run script(s) once +:set ScriptRunOnce do={ + :local Scripts [ :toarray $1 ]; + + :global ScriptRunOnceBaseUrl; + :global ScriptRunOnceUrlSuffix; + + :global LogPrintExit2; + :global ValidateSyntax; + + :foreach Script in=$Scripts do={ + :if (!($Script ~ "^(ftp|https\?|sftp)://")) do={ + :if ([ :len $ScriptRunOnceBaseUrl ] = 0) do={ + $LogPrintExit2 warning $0 ("Script '" . $Script . "' is not an url and base url is not available.") true; + } + :set Script ($ScriptRunOnceBaseUrl . $Script . ".rsc" . $ScriptRunOnceUrlSuffix); + } + + :local Source; + :do { + :set Source ([ /tool/fetch check-certificate=yes-without-crl $Script output=user as-value ]->"data"); + } on-error={ + $LogPrintExit2 warning $0 ("Failed fetching script '" . $Script . "'!") false; + } + + :if ([ :len $Source ] > 0) do={ + :if ([ $ValidateSyntax $Source ] = true) do={ + :do { + $LogPrintExit2 info $0 ("Running script '" . $Script . "' now.") false; + [ :parse $Source ]; + } on-error={ + $LogPrintExit2 warning $0 ("The script '" . $Script . "' failed to run!") false; + } + } else={ + $LogPrintExit2 warning $0 ("The script '" . $Script . "' failed syntax validation!") false; + } + } + } +} diff --git a/mode-button b/mode-button index 52ab00e..2da00ca 100644 --- a/mode-button +++ b/mode-button @@ -1,76 +1,3 @@ #!rsc by RouterOS -# RouterOS script: mode-button -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# act on multiple mode and reset button presses -# https://git.eworm.de/cgit/routeros-scripts/about/doc/mode-button.md - -:local 0 "mode-button"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global ModeButton; - -:global LogPrintExit2; - -:set ($ModeButton->"count") ($ModeButton->"count" + 1); - -:local Scheduler [ /system/scheduler/find where name="ModeButtonScheduler" ]; - -:if ([ :len $Scheduler ] = 0) do={ - $LogPrintExit2 info $0 ("Creating scheduler ModeButtonScheduler, counting presses...") false; - :global ModeButtonScheduler do={ - :global ModeButton; - - :global LogPrintExit2; - :global ModeButtonScheduler; - :global ValidateSyntax; - - :local LEDInvert do={ - :global ModeButtonLED; - - :global IfThenElse; - - :local LED [ /system/leds/find where leds=$ModeButtonLED type~"^(on|off)\$" interface=[] ]; - :if ([ :len $LED ] = 0) do={ - :return false; - } - /system/leds/set type=[ $IfThenElse ([ get $LED type ] = "on") "off" "on" ] $LED; - } - - :local Count ($ModeButton->"count"); - :local Code ($ModeButton->[ :tostr $Count ]); - - :set ($ModeButton->"count") 0; - :set ModeButtonScheduler; - /system/scheduler/remove ModeButtonScheduler; - - :if ([ :len $Code ] > 0) do={ - :if ([ $ValidateSyntax $Code ] = true) do={ - $LogPrintExit2 info $0 ("Acting on " . $Count . " mode-button presses: " . $Code) false; - - :for I from=1 to=$Count do={ - $LEDInvert; - :if ([ /system/routerboard/settings/get silent-boot ] = false) do={ - :beep length=200ms; - } - :delay 200ms; - $LEDInvert; - :delay 200ms; - } - - [ :parse $Code ]; - } else={ - $LogPrintExit2 warning $0 ("The code for " . $Count . " mode-button presses failed syntax validation!") false; - } - } else={ - $LogPrintExit2 info $0 ("No action defined for " . $Count . " mode-button presses.") false; - } - } - /system/scheduler/add name="ModeButtonScheduler" \ - on-event=":global ModeButtonScheduler; \$ModeButtonScheduler;" interval=3s; -} else={ - $LogPrintExit2 debug $0 ("Updating scheduler ModeButtonScheduler...") false; - /system/scheduler/set $Scheduler start-time=[ /system/clock/get time ]; -} +# dummy for migration diff --git a/mode-button.rsc b/mode-button.rsc new file mode 100644 index 0000000..52ab00e --- /dev/null +++ b/mode-button.rsc @@ -0,0 +1,76 @@ +#!rsc by RouterOS +# RouterOS script: mode-button +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# act on multiple mode and reset button presses +# https://git.eworm.de/cgit/routeros-scripts/about/doc/mode-button.md + +:local 0 "mode-button"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global ModeButton; + +:global LogPrintExit2; + +:set ($ModeButton->"count") ($ModeButton->"count" + 1); + +:local Scheduler [ /system/scheduler/find where name="ModeButtonScheduler" ]; + +:if ([ :len $Scheduler ] = 0) do={ + $LogPrintExit2 info $0 ("Creating scheduler ModeButtonScheduler, counting presses...") false; + :global ModeButtonScheduler do={ + :global ModeButton; + + :global LogPrintExit2; + :global ModeButtonScheduler; + :global ValidateSyntax; + + :local LEDInvert do={ + :global ModeButtonLED; + + :global IfThenElse; + + :local LED [ /system/leds/find where leds=$ModeButtonLED type~"^(on|off)\$" interface=[] ]; + :if ([ :len $LED ] = 0) do={ + :return false; + } + /system/leds/set type=[ $IfThenElse ([ get $LED type ] = "on") "off" "on" ] $LED; + } + + :local Count ($ModeButton->"count"); + :local Code ($ModeButton->[ :tostr $Count ]); + + :set ($ModeButton->"count") 0; + :set ModeButtonScheduler; + /system/scheduler/remove ModeButtonScheduler; + + :if ([ :len $Code ] > 0) do={ + :if ([ $ValidateSyntax $Code ] = true) do={ + $LogPrintExit2 info $0 ("Acting on " . $Count . " mode-button presses: " . $Code) false; + + :for I from=1 to=$Count do={ + $LEDInvert; + :if ([ /system/routerboard/settings/get silent-boot ] = false) do={ + :beep length=200ms; + } + :delay 200ms; + $LEDInvert; + :delay 200ms; + } + + [ :parse $Code ]; + } else={ + $LogPrintExit2 warning $0 ("The code for " . $Count . " mode-button presses failed syntax validation!") false; + } + } else={ + $LogPrintExit2 info $0 ("No action defined for " . $Count . " mode-button presses.") false; + } + } + /system/scheduler/add name="ModeButtonScheduler" \ + on-event=":global ModeButtonScheduler; \$ModeButtonScheduler;" interval=3s; +} else={ + $LogPrintExit2 debug $0 ("Updating scheduler ModeButtonScheduler...") false; + /system/scheduler/set $Scheduler start-time=[ /system/clock/get time ]; +} diff --git a/netwatch-dns b/netwatch-dns index 4eb3285..2da00ca 100644 --- a/netwatch-dns +++ b/netwatch-dns @@ -1,94 +1,3 @@ #!rsc by RouterOS -# RouterOS script: netwatch-dns -# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# monitor and manage dns/doh with netwatch -# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-dns.md - -:local 0 "netwatch-dns"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global CertificateAvailable; -:global EitherOr; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptLock; - -$ScriptLock $0; - -:if ([ /system/resource/get uptime ] < 5m) do={ - $LogPrintExit2 info $0 ("System just booted, giving netwatch some time to settle.") true; -} - -:local DnsServers ({}); -:local DnsFallback ({}); -:local DnsCurrent [ /ip/dns/get servers ]; - -:foreach Host in=[ /tool/netwatch/find where comment~"dns" !disabled ] do={ - :local HostVal [ /tool/netwatch/get $Host ]; - :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; - - :if ($HostVal->"status" = "up" && $HostInfo->"disabled" != true) do={ - :if ($HostInfo->"dns" = true) do={ - :set DnsServers ($DnsServers, $HostVal->"host"); - } - :if ($HostInfo->"dns-fallback" = true) do={ - :set DnsFallback ($DnsFallback, $HostVal->"host"); - } - } -} - -:if ([ :len $DnsServers ] > 0) do={ - :if ($DnsServers != $DnsCurrent) do={ - $LogPrintExit2 info $0 ("Updating DNS servers: " . [ :tostr $DnsServers ]) false; - /ip/dns/set servers=$DnsServers; - /ip/dns/cache/flush; - } -} else={ - :if ([ :len $DnsFallback ] > 0) do={ - :if ($DnsFallback != $DnsCurrent) do={ - $LogPrintExit2 info $0 ("Updating DNS servers to fallback: " . \ - [ :tostr $DnsFallback ]) false; - /ip/dns/set servers=$DnsFallback; - /ip/dns/cache/flush; - } - } -} - -:local DohServer ""; -:local DohCurrent [ /ip/dns/get use-doh-server ]; -:local DohCert ""; - -:foreach Host in=[ /tool/netwatch/find where comment~"doh" !disabled ] do={ - :local HostVal [ /tool/netwatch/get $Host ]; - :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; - - :if ($HostVal->"status" = "up" && $HostInfo->"doh" = true && \ - $HostInfo->"disabled" != true && $DohServer = "") do={ - :set DohServer [ $EitherOr ($HostInfo->"doh-url") \ - ("https://" . $HostVal->"host" . "/dns-query") ]; - :set DohCert ($HostInfo->"doh-cert"); - } -} - -:if ($DohServer != "") do={ - :if ($DohServer != $DohCurrent) do={ - $LogPrintExit2 info $0 ("Updating DoH server: " . $DohServer) false; - :if ([ :len $DohCert ] > 0) do={ - /ip/dns/set use-doh-server=""; - :if ([ $CertificateAvailable $DohCert ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; - } - } - /ip/dns/set use-doh-server=$DohServer; - /ip/dns/cache/flush; - } -} else={ - :if ($DohCurrent != "") do={ - $LogPrintExit2 info $0 ("DoH server (" . $DohCurrent . ") is down, disabling.") false; - /ip/dns/set use-doh-server=""; - /ip/dns/cache/flush; - } -} +# dummy for migration diff --git a/netwatch-dns.rsc b/netwatch-dns.rsc new file mode 100644 index 0000000..4eb3285 --- /dev/null +++ b/netwatch-dns.rsc @@ -0,0 +1,94 @@ +#!rsc by RouterOS +# RouterOS script: netwatch-dns +# Copyright (c) 2022-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# monitor and manage dns/doh with netwatch +# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-dns.md + +:local 0 "netwatch-dns"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global CertificateAvailable; +:global EitherOr; +:global LogPrintExit2; +:global ParseKeyValueStore; +:global ScriptLock; + +$ScriptLock $0; + +:if ([ /system/resource/get uptime ] < 5m) do={ + $LogPrintExit2 info $0 ("System just booted, giving netwatch some time to settle.") true; +} + +:local DnsServers ({}); +:local DnsFallback ({}); +:local DnsCurrent [ /ip/dns/get servers ]; + +:foreach Host in=[ /tool/netwatch/find where comment~"dns" !disabled ] do={ + :local HostVal [ /tool/netwatch/get $Host ]; + :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; + + :if ($HostVal->"status" = "up" && $HostInfo->"disabled" != true) do={ + :if ($HostInfo->"dns" = true) do={ + :set DnsServers ($DnsServers, $HostVal->"host"); + } + :if ($HostInfo->"dns-fallback" = true) do={ + :set DnsFallback ($DnsFallback, $HostVal->"host"); + } + } +} + +:if ([ :len $DnsServers ] > 0) do={ + :if ($DnsServers != $DnsCurrent) do={ + $LogPrintExit2 info $0 ("Updating DNS servers: " . [ :tostr $DnsServers ]) false; + /ip/dns/set servers=$DnsServers; + /ip/dns/cache/flush; + } +} else={ + :if ([ :len $DnsFallback ] > 0) do={ + :if ($DnsFallback != $DnsCurrent) do={ + $LogPrintExit2 info $0 ("Updating DNS servers to fallback: " . \ + [ :tostr $DnsFallback ]) false; + /ip/dns/set servers=$DnsFallback; + /ip/dns/cache/flush; + } + } +} + +:local DohServer ""; +:local DohCurrent [ /ip/dns/get use-doh-server ]; +:local DohCert ""; + +:foreach Host in=[ /tool/netwatch/find where comment~"doh" !disabled ] do={ + :local HostVal [ /tool/netwatch/get $Host ]; + :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; + + :if ($HostVal->"status" = "up" && $HostInfo->"doh" = true && \ + $HostInfo->"disabled" != true && $DohServer = "") do={ + :set DohServer [ $EitherOr ($HostInfo->"doh-url") \ + ("https://" . $HostVal->"host" . "/dns-query") ]; + :set DohCert ($HostInfo->"doh-cert"); + } +} + +:if ($DohServer != "") do={ + :if ($DohServer != $DohCurrent) do={ + $LogPrintExit2 info $0 ("Updating DoH server: " . $DohServer) false; + :if ([ :len $DohCert ] > 0) do={ + /ip/dns/set use-doh-server=""; + :if ([ $CertificateAvailable $DohCert ] = false) do={ + $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; + } + } + /ip/dns/set use-doh-server=$DohServer; + /ip/dns/cache/flush; + } +} else={ + :if ($DohCurrent != "") do={ + $LogPrintExit2 info $0 ("DoH server (" . $DohCurrent . ") is down, disabling.") false; + /ip/dns/set use-doh-server=""; + /ip/dns/cache/flush; + } +} diff --git a/netwatch-notify b/netwatch-notify index d04d23f..2da00ca 100644 --- a/netwatch-notify +++ b/netwatch-notify @@ -1,186 +1,3 @@ #!rsc by RouterOS -# RouterOS script: netwatch-notify -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# monitor netwatch and send notifications -# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-notify.md - -:local 0 "netwatch-notify"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global NetwatchNotify; - -:global EitherOr; -:global IfThenElse; -:global IsDNSResolving; -:global LogPrintExit2; -:global ParseKeyValueStore; -:global ScriptFromTerminal; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; - -:local NetwatchNotifyHook do={ - :local Name [ :tostr $1 ]; - :local Type [ :tostr $2 ]; - :local State [ :tostr $3 ]; - :local Hook [ :tostr $4 ]; - - :global LogPrintExit2; - :global ValidateSyntax; - - :if ([ $ValidateSyntax $Hook ] = true) do={ - :do { - [ :parse $Hook ]; - } on-error={ - $LogPrintExit2 warning $0 ("The " . $State . "-hook for " . $Type . " '" . $Name . \ - "' failed to run.") false; - :return ("The hook failed to run."); - } - } else={ - $LogPrintExit2 warning $0 ("The " . $State . "-hook for " . $Type . " '" . $Name . \ - "' failed syntax validation.") false; - :return ("The hook failed syntax validation."); - } - - $LogPrintExit2 info $0 ("Ran hook on " . $Type . " '" . $Name . "' " . $State . ": " . \ - $Hook) false; - :return ("Ran hook:\n" . $Hook); -} - -$ScriptLock $0; - -:local ScriptFromTerminalCached [ $ScriptFromTerminal $0 ]; - -:if ([ /system/resource/get uptime ] < 5m) do={ - $LogPrintExit2 info $0 ("System just booted, giving netwatch some time to settle.") true; -} - -:if ([ :typeof $NetwatchNotify ] = "nothing") do={ - :set NetwatchNotify ({}); -} - -:foreach Host in=[ /tool/netwatch/find where comment~"notify" !disabled ] do={ - :local HostVal [ /tool/netwatch/get $Host ]; - :local Type [ $IfThenElse ($HostVal->"type" ~ "^(https?-get|tcp-conn)\$") "service" "host" ]; - :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; - :local HostDetails ($HostVal->"host" . \ - [ $IfThenElse ([ :len ($HostInfo->"resolve") ] > 0) (", " . $HostInfo->"resolve") ]); - - :if ($HostInfo->"notify" = true && $HostInfo->"disabled" != true) do={ - :local Name [ $EitherOr ($HostInfo->"name") ($HostVal->"name") ]; - - :local Metric { "count-down"=0; "count-up"=0; "notified"=false; "resolve-failcnt"=0 }; - :if ([ :typeof ($NetwatchNotify->$Name) ] = "array") do={ - :set $Metric ($NetwatchNotify->$Name); - } - - :if ([ :typeof ($HostInfo->"resolve") ] = "str") do={ - :if ([ $IsDNSResolving ] = true) do={ - :do { - :local Resolve [ :resolve ($HostInfo->"resolve") ]; - :if ($Resolve != $HostVal->"host") do={ - $LogPrintExit2 info $0 ("Name '" . $HostInfo->"resolve" . [ $IfThenElse \ - ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ - $HostInfo->"name") "" ] . "' resolves to different address " . $Resolve . \ - ", updating.") false; - /tool/netwatch/set host=$Resolve $Host; - :set ($Metric->"resolve-failcnt") 0; - } - } on-error={ - :set ($Metric->"resolve-failcnt") ($Metric->"resolve-failcnt" + 1); - :if ($Metric->"resolve-failcnt" = 3) do={ - $LogPrintExit2 warning $0 ("Resolving name '" . $HostInfo->"resolve" . [ $IfThenElse \ - ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ - $HostInfo->"name") "" ] . "' failed.") false; - } - } - } - } - - :if ($HostVal->"status" = "up") do={ - :local CountDown ($Metric->"count-down"); - :if ($CountDown > 0) do={ - $LogPrintExit2 info $0 \ - ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is up.") false; - :set ($Metric->"count-down") 0; - } - :set ($Metric->"count-up") ($Metric->"count-up" + 1); - :if ($Metric->"notified" = true) do={ - :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \ - ") is up since " . $HostVal->"since" . ".\n" . \ - "It was down for " . $CountDown . " checks since " . ($Metric->"since") . "."); - :if ([ :typeof ($HostInfo->"up-hook") ] = "str") do={ - :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $Name $Type "up" \ - ($HostInfo->"up-hook") ]); - } - $SendNotification2 ({ origin=$0; silent=($HostInfo->"silent"); \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Netwatch Notify: " . \ - $Name . " up"); \ - message=$Message }); - } - :set ($Metric->"notified") false; - :set ($Metric->"parent") ($HostInfo->"parent"); - :set ($Metric->"since"); - } else={ - :set ($Metric->"count-down") ($Metric->"count-down" + 1); - :set ($Metric->"count-up") 0; - :set ($Metric->"parent") ($HostInfo->"parent"); - :set ($Metric->"since") ($HostVal->"since"); - :local CountDown [ $IfThenElse ([ :tonum ($HostInfo->"count") ] > 0) ($HostInfo->"count") 5 ]; - :local Parent ($HostInfo->"parent"); - :local ParentUp false; - :while ([ :len $Parent ] > 0) do={ - :set CountDown ($CountDown + 1); - :set Parent ($NetwatchNotify->$Parent->"parent"); - } - :set Parent ($HostInfo->"parent"); - :local ParentNotified false; - :while ($ParentNotified = false && [ :len $Parent ] > 0) do={ - :set ParentNotified [ $IfThenElse (($NetwatchNotify->$Parent->"notified") = true) \ - true false ]; - :set ParentUp ($NetwatchNotify->$Parent->"count-up"); - :if ($ParentNotified = false) do={ - :set Parent ($NetwatchNotify->$Parent->"parent"); - } - } - :if ($Metric->"notified" = false || $Metric->"count-down" % 120 = 0 || \ - $ScriptFromTerminalCached = true) do={ - $LogPrintExit2 [ $IfThenElse ($HostInfo->"no-down-notification" != true) info debug ] $0 \ - ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is down for " . \ - $Metric->"count-down" . " checks, " . [ $IfThenElse ($ParentNotified = false) [ $IfThenElse \ - ($Metric->"notified" = true) ("already notified.") ($CountDown - $Metric->"count-down" . \ - " to go.") ] ("parent " . $Type . " " . $Parent . " is down.") ]) false; - } - :if ((($CountDown * 2) - ($Metric->"count-down" * 3)) / 2 = 0 && \ - [ :typeof ($HostInfo->"pre-down-hook") ] = "str") do={ - $NetwatchNotifyHook $Name $Type "pre-down" ($HostInfo->"pre-down-hook"); - } - :if ($ParentNotified = false && $Metric->"count-down" >= $CountDown && \ - ($ParentUp = false || $ParentUp > 2) && $Metric->"notified" != true) do={ - :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \ - ") is down since " . $HostVal->"since" . "."); - :if ([ :typeof ($HostInfo->"down-hook") ] = "str") do={ - :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $Name $Type "down" \ - ($HostInfo->"down-hook") ]); - } - :if ($HostInfo->"no-down-notification" != true) do={ - $SendNotification2 ({ origin=$0; silent=($HostInfo->"silent"); \ - subject=([ $SymbolForNotification "cross-mark" ] . "Netwatch Notify: " . \ - $Name . " down"); \ - message=$Message }); - } - :set ($Metric->"notified") true; - } - } - :set ($NetwatchNotify->$Name) { - "count-down"=($Metric->"count-down"); - "count-up"=($Metric->"count-up"); - "notified"=($Metric->"notified"); - "parent"=($Metric->"parent"); - "resolve-failcnt"=($Metric->"resolve-failcnt"); - "since"=($Metric->"since") }; - } -} +# dummy for migration diff --git a/netwatch-notify.rsc b/netwatch-notify.rsc new file mode 100644 index 0000000..d04d23f --- /dev/null +++ b/netwatch-notify.rsc @@ -0,0 +1,186 @@ +#!rsc by RouterOS +# RouterOS script: netwatch-notify +# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# monitor netwatch and send notifications +# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-notify.md + +:local 0 "netwatch-notify"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global NetwatchNotify; + +:global EitherOr; +:global IfThenElse; +:global IsDNSResolving; +:global LogPrintExit2; +:global ParseKeyValueStore; +:global ScriptFromTerminal; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; + +:local NetwatchNotifyHook do={ + :local Name [ :tostr $1 ]; + :local Type [ :tostr $2 ]; + :local State [ :tostr $3 ]; + :local Hook [ :tostr $4 ]; + + :global LogPrintExit2; + :global ValidateSyntax; + + :if ([ $ValidateSyntax $Hook ] = true) do={ + :do { + [ :parse $Hook ]; + } on-error={ + $LogPrintExit2 warning $0 ("The " . $State . "-hook for " . $Type . " '" . $Name . \ + "' failed to run.") false; + :return ("The hook failed to run."); + } + } else={ + $LogPrintExit2 warning $0 ("The " . $State . "-hook for " . $Type . " '" . $Name . \ + "' failed syntax validation.") false; + :return ("The hook failed syntax validation."); + } + + $LogPrintExit2 info $0 ("Ran hook on " . $Type . " '" . $Name . "' " . $State . ": " . \ + $Hook) false; + :return ("Ran hook:\n" . $Hook); +} + +$ScriptLock $0; + +:local ScriptFromTerminalCached [ $ScriptFromTerminal $0 ]; + +:if ([ /system/resource/get uptime ] < 5m) do={ + $LogPrintExit2 info $0 ("System just booted, giving netwatch some time to settle.") true; +} + +:if ([ :typeof $NetwatchNotify ] = "nothing") do={ + :set NetwatchNotify ({}); +} + +:foreach Host in=[ /tool/netwatch/find where comment~"notify" !disabled ] do={ + :local HostVal [ /tool/netwatch/get $Host ]; + :local Type [ $IfThenElse ($HostVal->"type" ~ "^(https?-get|tcp-conn)\$") "service" "host" ]; + :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; + :local HostDetails ($HostVal->"host" . \ + [ $IfThenElse ([ :len ($HostInfo->"resolve") ] > 0) (", " . $HostInfo->"resolve") ]); + + :if ($HostInfo->"notify" = true && $HostInfo->"disabled" != true) do={ + :local Name [ $EitherOr ($HostInfo->"name") ($HostVal->"name") ]; + + :local Metric { "count-down"=0; "count-up"=0; "notified"=false; "resolve-failcnt"=0 }; + :if ([ :typeof ($NetwatchNotify->$Name) ] = "array") do={ + :set $Metric ($NetwatchNotify->$Name); + } + + :if ([ :typeof ($HostInfo->"resolve") ] = "str") do={ + :if ([ $IsDNSResolving ] = true) do={ + :do { + :local Resolve [ :resolve ($HostInfo->"resolve") ]; + :if ($Resolve != $HostVal->"host") do={ + $LogPrintExit2 info $0 ("Name '" . $HostInfo->"resolve" . [ $IfThenElse \ + ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ + $HostInfo->"name") "" ] . "' resolves to different address " . $Resolve . \ + ", updating.") false; + /tool/netwatch/set host=$Resolve $Host; + :set ($Metric->"resolve-failcnt") 0; + } + } on-error={ + :set ($Metric->"resolve-failcnt") ($Metric->"resolve-failcnt" + 1); + :if ($Metric->"resolve-failcnt" = 3) do={ + $LogPrintExit2 warning $0 ("Resolving name '" . $HostInfo->"resolve" . [ $IfThenElse \ + ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ + $HostInfo->"name") "" ] . "' failed.") false; + } + } + } + } + + :if ($HostVal->"status" = "up") do={ + :local CountDown ($Metric->"count-down"); + :if ($CountDown > 0) do={ + $LogPrintExit2 info $0 \ + ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is up.") false; + :set ($Metric->"count-down") 0; + } + :set ($Metric->"count-up") ($Metric->"count-up" + 1); + :if ($Metric->"notified" = true) do={ + :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \ + ") is up since " . $HostVal->"since" . ".\n" . \ + "It was down for " . $CountDown . " checks since " . ($Metric->"since") . "."); + :if ([ :typeof ($HostInfo->"up-hook") ] = "str") do={ + :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $Name $Type "up" \ + ($HostInfo->"up-hook") ]); + } + $SendNotification2 ({ origin=$0; silent=($HostInfo->"silent"); \ + subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Netwatch Notify: " . \ + $Name . " up"); \ + message=$Message }); + } + :set ($Metric->"notified") false; + :set ($Metric->"parent") ($HostInfo->"parent"); + :set ($Metric->"since"); + } else={ + :set ($Metric->"count-down") ($Metric->"count-down" + 1); + :set ($Metric->"count-up") 0; + :set ($Metric->"parent") ($HostInfo->"parent"); + :set ($Metric->"since") ($HostVal->"since"); + :local CountDown [ $IfThenElse ([ :tonum ($HostInfo->"count") ] > 0) ($HostInfo->"count") 5 ]; + :local Parent ($HostInfo->"parent"); + :local ParentUp false; + :while ([ :len $Parent ] > 0) do={ + :set CountDown ($CountDown + 1); + :set Parent ($NetwatchNotify->$Parent->"parent"); + } + :set Parent ($HostInfo->"parent"); + :local ParentNotified false; + :while ($ParentNotified = false && [ :len $Parent ] > 0) do={ + :set ParentNotified [ $IfThenElse (($NetwatchNotify->$Parent->"notified") = true) \ + true false ]; + :set ParentUp ($NetwatchNotify->$Parent->"count-up"); + :if ($ParentNotified = false) do={ + :set Parent ($NetwatchNotify->$Parent->"parent"); + } + } + :if ($Metric->"notified" = false || $Metric->"count-down" % 120 = 0 || \ + $ScriptFromTerminalCached = true) do={ + $LogPrintExit2 [ $IfThenElse ($HostInfo->"no-down-notification" != true) info debug ] $0 \ + ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is down for " . \ + $Metric->"count-down" . " checks, " . [ $IfThenElse ($ParentNotified = false) [ $IfThenElse \ + ($Metric->"notified" = true) ("already notified.") ($CountDown - $Metric->"count-down" . \ + " to go.") ] ("parent " . $Type . " " . $Parent . " is down.") ]) false; + } + :if ((($CountDown * 2) - ($Metric->"count-down" * 3)) / 2 = 0 && \ + [ :typeof ($HostInfo->"pre-down-hook") ] = "str") do={ + $NetwatchNotifyHook $Name $Type "pre-down" ($HostInfo->"pre-down-hook"); + } + :if ($ParentNotified = false && $Metric->"count-down" >= $CountDown && \ + ($ParentUp = false || $ParentUp > 2) && $Metric->"notified" != true) do={ + :local Message ("The " . $Type . " '" . $Name . "' (" . $HostDetails . \ + ") is down since " . $HostVal->"since" . "."); + :if ([ :typeof ($HostInfo->"down-hook") ] = "str") do={ + :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $Name $Type "down" \ + ($HostInfo->"down-hook") ]); + } + :if ($HostInfo->"no-down-notification" != true) do={ + $SendNotification2 ({ origin=$0; silent=($HostInfo->"silent"); \ + subject=([ $SymbolForNotification "cross-mark" ] . "Netwatch Notify: " . \ + $Name . " down"); \ + message=$Message }); + } + :set ($Metric->"notified") true; + } + } + :set ($NetwatchNotify->$Name) { + "count-down"=($Metric->"count-down"); + "count-up"=($Metric->"count-up"); + "notified"=($Metric->"notified"); + "parent"=($Metric->"parent"); + "resolve-failcnt"=($Metric->"resolve-failcnt"); + "since"=($Metric->"since") }; + } +} diff --git a/news-and-changes.rsc b/news-and-changes.rsc new file mode 100644 index 0000000..e532496 --- /dev/null +++ b/news-and-changes.rsc @@ -0,0 +1,16 @@ +# News, changes and migration by RouterOS Scripts +# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +:global IfThenElse; +:global RequiredRouterOS; + +# News, changes and migration up to change 95 are in global-config.changes! + +# Changes for global-config to be added to notification on script updates +:global GlobalConfigChanges { +}; + +# Migration steps to be applied on script updates +:global GlobalConfigMigration { +}; diff --git a/ospf-to-leds b/ospf-to-leds index 12ec820..2da00ca 100644 --- a/ospf-to-leds +++ b/ospf-to-leds @@ -1,35 +1,3 @@ #!rsc by RouterOS -# RouterOS script: ospf-to-leds -# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# visualize ospf instance state via leds -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ospf-to-leds.md - -:local 0 "ospf-to-leds"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; -:global ParseKeyValueStore; - -:foreach Instance in=[ /routing/ospf/instance/find where comment~"^ospf-to-leds," ] do={ - :local InstanceVal [ /routing/ospf/instance/get $Instance ]; - :local LED ([ $ParseKeyValueStore ($InstanceVal->"comment") ]->"leds"); - :local LEDType [ /system/leds/get [ find where leds=$LED ] type ]; - - :local NeighborCount 0; - :foreach Area in=[ /routing/ospf/area/find where instance=($InstanceVal->"name") ] do={ - :local AreaName [ /routing/ospf/area/get $Area name ]; - :set NeighborCount ($NeighborCount + [ :len [ /routing/ospf/neighbor/find where area=$AreaName ] ]); - } - - :if ($NeighborCount > 0 && $LEDType = "off") do={ - $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " has " . $NeighborCount . " neighbors, led on!") false; - /system/leds/set type=on [ find where leds=$LED ]; - } - :if ($NeighborCount = 0 && $LEDType = "on") do={ - $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " has no neighbors, led off!") false; - /system/leds/set type=off [ find where leds=$LED ]; - } -} +# dummy for migration diff --git a/ospf-to-leds.rsc b/ospf-to-leds.rsc new file mode 100644 index 0000000..12ec820 --- /dev/null +++ b/ospf-to-leds.rsc @@ -0,0 +1,35 @@ +#!rsc by RouterOS +# RouterOS script: ospf-to-leds +# Copyright (c) 2020-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# visualize ospf instance state via leds +# https://git.eworm.de/cgit/routeros-scripts/about/doc/ospf-to-leds.md + +:local 0 "ospf-to-leds"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; +:global ParseKeyValueStore; + +:foreach Instance in=[ /routing/ospf/instance/find where comment~"^ospf-to-leds," ] do={ + :local InstanceVal [ /routing/ospf/instance/get $Instance ]; + :local LED ([ $ParseKeyValueStore ($InstanceVal->"comment") ]->"leds"); + :local LEDType [ /system/leds/get [ find where leds=$LED ] type ]; + + :local NeighborCount 0; + :foreach Area in=[ /routing/ospf/area/find where instance=($InstanceVal->"name") ] do={ + :local AreaName [ /routing/ospf/area/get $Area name ]; + :set NeighborCount ($NeighborCount + [ :len [ /routing/ospf/neighbor/find where area=$AreaName ] ]); + } + + :if ($NeighborCount > 0 && $LEDType = "off") do={ + $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " has " . $NeighborCount . " neighbors, led on!") false; + /system/leds/set type=on [ find where leds=$LED ]; + } + :if ($NeighborCount = 0 && $LEDType = "on") do={ + $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " has no neighbors, led off!") false; + /system/leds/set type=off [ find where leds=$LED ]; + } +} diff --git a/packages-update b/packages-update index 5162103..2da00ca 100644 --- a/packages-update +++ b/packages-update @@ -1,98 +1,3 @@ #!rsc by RouterOS -# RouterOS script: packages-update -# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# download packages and reboot for installation -# https://git.eworm.de/cgit/routeros-scripts/about/doc/packages-update.md - -:local 0 "packages-update"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global DownloadPackage; -:global LogPrintExit2; -:global ScriptFromTerminal; -:global ScriptLock; -:global VersionToNum; - -$ScriptLock $0; - -:local Update [ /system/package/update/get ]; - -:if ([ :typeof ($Update->"latest-version") ] = "nothing") do={ - $LogPrintExit2 warning $0 ("Latest version is not known.") true; -} - -:if ($Update->"installed-version" = $Update->"latest-version") do={ - $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is already installed.") true; -} - -:local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; -:local NumLatest [ $VersionToNum ($Update->"latest-version") ]; - -:local DoDowngrade false; -:if ($NumInstalled > $NumLatest) do={ - :if ([ $ScriptFromTerminal $0 ] = true) do={ - :put "Latest version is older than installed one. Want to downgrade? [y/N]"; - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - :set DoDowngrade true; - } else={ - :put "Canceled..."; - } - } else={ - $LogPrintExit2 warning $0 ("Not installing downgrade automatically.") true; - } -} - -:foreach Package in=[ /system/package/find where !bundle ] do={ - :local PkgName [ /system/package/get $Package name ]; - :if ([ $DownloadPackage $PkgName ($Update->"latest-version") ] = false) do={ - $LogPrintExit2 error $0 ("Download for package " . $PkgName . " failed, update aborted.") true; - } -} - -:foreach Script in=[ /system/script/find where source~"\n# provides: backup-script\n" ] do={ - :local ScriptName [ /system/script/get $Script name ]; - :do { - $LogPrintExit2 info $0 ("Running backup script " . $ScriptName . " before update.") false; - /system/script/run $Script; - } on-error={ - $LogPrintExit2 warning $0 ("Running backup script " . $ScriptName . " before update failed!") false; - :if ([ $ScriptFromTerminal $0 ] = true) do={ - :put "Do you want to continue anyway? [y/N]"; - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - $LogPrintExit2 info $0 ("User requested to continue anyway.") false; - } else={ - $LogPrintExit2 info $0 ("Canceled update...") true; - } - } else={ - $LogPrintExit2 info $0 ("Canceled non-interactive update.") true; - } - } -} - -:if ($DoDowngrade = true) do={ - $LogPrintExit2 info $0 ("Rebooting for downgrade.") false; - :delay 1s; - /system/package/downgrade; -} - -:if ([ $ScriptFromTerminal $0 ] = true) do={ - :put "Do you want to (s)chedule reboot or (r)eboot now? [s/R]"; - :if (([ /terminal/inkey timeout=60 ] % 32) = 19) do={ - :global RebootForUpdate do={ - :global RandomDelay; - $RandomDelay 3600; - /system/reboot; - } - /system/scheduler/add name="reboot-for-update" start-time=03:00:00 interval=1d \ - on-event=("/system/scheduler/remove reboot-for-update; " . \ - ":global RebootForUpdate; \$RebootForUpdate;"); - $LogPrintExit2 info $0 ("Scheduled reboot for update between 03:00 and 04:00.") true; - } -} - -$LogPrintExit2 info $0 ("Rebooting for update.") false; -:delay 1s; -/system/reboot; +# dummy for migration diff --git a/packages-update.rsc b/packages-update.rsc new file mode 100644 index 0000000..5162103 --- /dev/null +++ b/packages-update.rsc @@ -0,0 +1,98 @@ +#!rsc by RouterOS +# RouterOS script: packages-update +# Copyright (c) 2019-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# download packages and reboot for installation +# https://git.eworm.de/cgit/routeros-scripts/about/doc/packages-update.md + +:local 0 "packages-update"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global DownloadPackage; +:global LogPrintExit2; +:global ScriptFromTerminal; +:global ScriptLock; +:global VersionToNum; + +$ScriptLock $0; + +:local Update [ /system/package/update/get ]; + +:if ([ :typeof ($Update->"latest-version") ] = "nothing") do={ + $LogPrintExit2 warning $0 ("Latest version is not known.") true; +} + +:if ($Update->"installed-version" = $Update->"latest-version") do={ + $LogPrintExit2 info $0 ("Version " . $Update->"latest-version" . " is already installed.") true; +} + +:local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; +:local NumLatest [ $VersionToNum ($Update->"latest-version") ]; + +:local DoDowngrade false; +:if ($NumInstalled > $NumLatest) do={ + :if ([ $ScriptFromTerminal $0 ] = true) do={ + :put "Latest version is older than installed one. Want to downgrade? [y/N]"; + :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ + :set DoDowngrade true; + } else={ + :put "Canceled..."; + } + } else={ + $LogPrintExit2 warning $0 ("Not installing downgrade automatically.") true; + } +} + +:foreach Package in=[ /system/package/find where !bundle ] do={ + :local PkgName [ /system/package/get $Package name ]; + :if ([ $DownloadPackage $PkgName ($Update->"latest-version") ] = false) do={ + $LogPrintExit2 error $0 ("Download for package " . $PkgName . " failed, update aborted.") true; + } +} + +:foreach Script in=[ /system/script/find where source~"\n# provides: backup-script\n" ] do={ + :local ScriptName [ /system/script/get $Script name ]; + :do { + $LogPrintExit2 info $0 ("Running backup script " . $ScriptName . " before update.") false; + /system/script/run $Script; + } on-error={ + $LogPrintExit2 warning $0 ("Running backup script " . $ScriptName . " before update failed!") false; + :if ([ $ScriptFromTerminal $0 ] = true) do={ + :put "Do you want to continue anyway? [y/N]"; + :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ + $LogPrintExit2 info $0 ("User requested to continue anyway.") false; + } else={ + $LogPrintExit2 info $0 ("Canceled update...") true; + } + } else={ + $LogPrintExit2 info $0 ("Canceled non-interactive update.") true; + } + } +} + +:if ($DoDowngrade = true) do={ + $LogPrintExit2 info $0 ("Rebooting for downgrade.") false; + :delay 1s; + /system/package/downgrade; +} + +:if ([ $ScriptFromTerminal $0 ] = true) do={ + :put "Do you want to (s)chedule reboot or (r)eboot now? [s/R]"; + :if (([ /terminal/inkey timeout=60 ] % 32) = 19) do={ + :global RebootForUpdate do={ + :global RandomDelay; + $RandomDelay 3600; + /system/reboot; + } + /system/scheduler/add name="reboot-for-update" start-time=03:00:00 interval=1d \ + on-event=("/system/scheduler/remove reboot-for-update; " . \ + ":global RebootForUpdate; \$RebootForUpdate;"); + $LogPrintExit2 info $0 ("Scheduled reboot for update between 03:00 and 04:00.") true; + } +} + +$LogPrintExit2 info $0 ("Rebooting for update.") false; +:delay 1s; +/system/reboot; @@ -1,34 +1,3 @@ #!rsc by RouterOS -# RouterOS script: ppp-on-up -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# run scripts on ppp up -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-up.md - -:local 0 "ppp-on-up"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global LogPrintExit2; - -:local Interface $interface; - -:if ([ :typeof $Interface ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from ppp on-up script hook.") true; -} - -:local IntName [ /interface/get $Interface name ]; -$LogPrintExit2 info $0 ("PPP interface " . $IntName . " is up.") false; - -/ipv6/dhcp-client/release [ find where interface=$IntName !disabled ]; - -:foreach Script in=[ /system/script/find where source~("\n# provides: ppp-on-up\n") ] do={ - :local ScriptName [ /system/script/get $Script name ]; - :do { - $LogPrintExit2 debug $0 ("Running script: " . $ScriptName) false; - /system/script/run $Script; - } on-error={ - $LogPrintExit2 warning $0 ("Running script '" . $ScriptName . "' failed!") false; - } -} +# dummy for migration diff --git a/ppp-on-up.rsc b/ppp-on-up.rsc new file mode 100644 index 0000000..ac01c97 --- /dev/null +++ b/ppp-on-up.rsc @@ -0,0 +1,34 @@ +#!rsc by RouterOS +# RouterOS script: ppp-on-up +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# run scripts on ppp up +# https://git.eworm.de/cgit/routeros-scripts/about/doc/ppp-on-up.md + +:local 0 "ppp-on-up"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global LogPrintExit2; + +:local Interface $interface; + +:if ([ :typeof $Interface ] = "nothing") do={ + $LogPrintExit2 error $0 ("This script is supposed to run from ppp on-up script hook.") true; +} + +:local IntName [ /interface/get $Interface name ]; +$LogPrintExit2 info $0 ("PPP interface " . $IntName . " is up.") false; + +/ipv6/dhcp-client/release [ find where interface=$IntName !disabled ]; + +:foreach Script in=[ /system/script/find where source~("\n# provides: ppp-on-up\n") ] do={ + :local ScriptName [ /system/script/get $Script name ]; + :do { + $LogPrintExit2 debug $0 ("Running script: " . $ScriptName) false; + /system/script/run $Script; + } on-error={ + $LogPrintExit2 warning $0 ("Running script '" . $ScriptName . "' failed!") false; + } +} @@ -1,31 +1,3 @@ #!rsc by RouterOS -# RouterOS script: sms-action -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# run action on received SMS -# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-action.md - -:local 0 "sms-action"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global SmsAction; - -:global LogPrintExit2; -:global ValidateSyntax; - -:local Action $action; - -:if ([ :typeof $Action ] = "nothing") do={ - $LogPrintExit2 error $0 ("This script is supposed to run from SMS hook with action=...") true; -} - -:local Code ($SmsAction->$Action); -:if ([ $ValidateSyntax $Code ] = true) do={ - :log info ("Acting on SMS action '" . $Action . "': " . $Code); - :delay 1s; - [ :parse $Code ]; -} else={ - $LogPrintExit2 warning $0 ("The code for action '" . $Action . "' failed syntax validation!") false; -} +# dummy for migration diff --git a/sms-action.rsc b/sms-action.rsc new file mode 100644 index 0000000..f5de11f --- /dev/null +++ b/sms-action.rsc @@ -0,0 +1,31 @@ +#!rsc by RouterOS +# RouterOS script: sms-action +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# run action on received SMS +# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-action.md + +:local 0 "sms-action"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global SmsAction; + +:global LogPrintExit2; +:global ValidateSyntax; + +:local Action $action; + +:if ([ :typeof $Action ] = "nothing") do={ + $LogPrintExit2 error $0 ("This script is supposed to run from SMS hook with action=...") true; +} + +:local Code ($SmsAction->$Action); +:if ([ $ValidateSyntax $Code ] = true) do={ + :log info ("Acting on SMS action '" . $Action . "': " . $Code); + :delay 1s; + [ :parse $Code ]; +} else={ + $LogPrintExit2 warning $0 ("The code for action '" . $Action . "' failed syntax validation!") false; +} diff --git a/sms-forward b/sms-forward index 802da48..2da00ca 100644 --- a/sms-forward +++ b/sms-forward @@ -1,84 +1,3 @@ #!rsc by RouterOS -# RouterOS script: sms-forward -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# Anatoly Bubenkov <bubenkoff@gmail.com> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# forward SMS to e-mail -# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-forward.md - -:local 0 "sms-forward"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Identity; -:global SmsForwardHooks; - -:global IfThenElse; -:global LogPrintExit2; -:global ScriptLock; -:global SendNotification2; -:global SymbolForNotification; -:global ValidateSyntax; -:global WaitFullyConnected; - -$ScriptLock $0; - -:if ([ /tool/sms/get receive-enabled ] = false) do={ - $LogPrintExit2 warning $0 ("Receiving of SMS is not enabled.") true; -} - -$WaitFullyConnected; - -:local Settings [ /tool/sms/get ]; - -# forward SMS in a loop -:while ([ :len [ /tool/sms/inbox/find ] ] > 0) do={ - :local Phone [ /tool/sms/inbox/get ([ find ]->0) phone ]; - :local Messages ""; - :local Delete ({}); - - :foreach Sms in=[ /tool/sms/inbox/find where phone=$Phone ] do={ - :local SmsVal [ /tool/sms/inbox/get $Sms ]; - - :if ($Phone = $Settings->"allowed-number" && \ - ($SmsVal->"message")~("^:cmd " . $Settings->"secret" . " script ")) do={ - $LogPrintExit2 debug $0 ("Removing SMS, which started a script.") false; - /tool/sms/inbox/remove $Sms; - } else={ - :set Messages ($Messages . "\n\nOn " . $SmsVal->"timestamp" . \ - " type " . $SmsVal->"type" . ":\n" . $SmsVal->"message"); - :foreach Hook in=$SmsForwardHooks do={ - :if ($Phone~($Hook->"allowed-number") && ($SmsVal->"message")~($Hook->"match")) do={ - :if ([ $ValidateSyntax ($Hook->"command") ] = true) do={ - $LogPrintExit2 info $0 ("Running hook '" . $Hook->"match" . "': " . \ - $Hook->"command") false; - :do { - [ :parse ($Hook->"command") ]; - :set Messages ($Messages . "\n\nRan hook '" . $Hook->"match" . "':\n" . \ - $Hook->"command"); - } on-error={ - $LogPrintExit2 warning $0 ("The code for hook '" . $Hook->"match" . \ - "' failed to run!") false; - } - } else={ - $LogPrintExit2 warning $0 ("The code for hook '" . $Hook->"match" . \ - "' failed syntax validation!") false; - } - } - } - :set Delete ($Delete, $Sms); - } - } - - :if ([ :len $Messages ] > 0) do={ - :local Count [ :len $Delete ]; - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "incoming-envelope" ] . "SMS Forwarding from " . $Phone); \ - message=("Received " . [ $IfThenElse ($Count = 1) "this message" ("these " . $Count . " messages") ] . \ - " by " . $Identity . " from " . $Phone . ":" . $Messages) }); - :foreach Sms in=$Delete do={ - /tool/sms/inbox/remove $Sms; - } - } -} +# dummy for migration diff --git a/sms-forward.rsc b/sms-forward.rsc new file mode 100644 index 0000000..802da48 --- /dev/null +++ b/sms-forward.rsc @@ -0,0 +1,84 @@ +#!rsc by RouterOS +# RouterOS script: sms-forward +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Anatoly Bubenkov <bubenkoff@gmail.com> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# forward SMS to e-mail +# https://git.eworm.de/cgit/routeros-scripts/about/doc/sms-forward.md + +:local 0 "sms-forward"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Identity; +:global SmsForwardHooks; + +:global IfThenElse; +:global LogPrintExit2; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; +:global ValidateSyntax; +:global WaitFullyConnected; + +$ScriptLock $0; + +:if ([ /tool/sms/get receive-enabled ] = false) do={ + $LogPrintExit2 warning $0 ("Receiving of SMS is not enabled.") true; +} + +$WaitFullyConnected; + +:local Settings [ /tool/sms/get ]; + +# forward SMS in a loop +:while ([ :len [ /tool/sms/inbox/find ] ] > 0) do={ + :local Phone [ /tool/sms/inbox/get ([ find ]->0) phone ]; + :local Messages ""; + :local Delete ({}); + + :foreach Sms in=[ /tool/sms/inbox/find where phone=$Phone ] do={ + :local SmsVal [ /tool/sms/inbox/get $Sms ]; + + :if ($Phone = $Settings->"allowed-number" && \ + ($SmsVal->"message")~("^:cmd " . $Settings->"secret" . " script ")) do={ + $LogPrintExit2 debug $0 ("Removing SMS, which started a script.") false; + /tool/sms/inbox/remove $Sms; + } else={ + :set Messages ($Messages . "\n\nOn " . $SmsVal->"timestamp" . \ + " type " . $SmsVal->"type" . ":\n" . $SmsVal->"message"); + :foreach Hook in=$SmsForwardHooks do={ + :if ($Phone~($Hook->"allowed-number") && ($SmsVal->"message")~($Hook->"match")) do={ + :if ([ $ValidateSyntax ($Hook->"command") ] = true) do={ + $LogPrintExit2 info $0 ("Running hook '" . $Hook->"match" . "': " . \ + $Hook->"command") false; + :do { + [ :parse ($Hook->"command") ]; + :set Messages ($Messages . "\n\nRan hook '" . $Hook->"match" . "':\n" . \ + $Hook->"command"); + } on-error={ + $LogPrintExit2 warning $0 ("The code for hook '" . $Hook->"match" . \ + "' failed to run!") false; + } + } else={ + $LogPrintExit2 warning $0 ("The code for hook '" . $Hook->"match" . \ + "' failed syntax validation!") false; + } + } + } + :set Delete ($Delete, $Sms); + } + } + + :if ([ :len $Messages ] > 0) do={ + :local Count [ :len $Delete ]; + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "incoming-envelope" ] . "SMS Forwarding from " . $Phone); \ + message=("Received " . [ $IfThenElse ($Count = 1) "this message" ("these " . $Count . " messages") ] . \ + " by " . $Identity . " from " . $Phone . ":" . $Messages) }); + :foreach Sms in=$Delete do={ + /tool/sms/inbox/remove $Sms; + } + } +} diff --git a/ssh-keys-import b/ssh-keys-import index b40a997..2da00ca 100644 --- a/ssh-keys-import +++ b/ssh-keys-import @@ -1,11 +1,3 @@ #!rsc by RouterOS -# RouterOS script: ssh-keys-import -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# import ssh keys from file -# https://git.eworm.de/cgit/routeros-scripts/about/doc/ssh-keys-import.md - -:foreach Key in=[ /file/find where type="ssh key" ] do={ - /user/ssh-key/import user=admin public-key-file=[ /file/get $Key name ]; -} +# dummy for migration diff --git a/ssh-keys-import.rsc b/ssh-keys-import.rsc new file mode 100644 index 0000000..b40a997 --- /dev/null +++ b/ssh-keys-import.rsc @@ -0,0 +1,11 @@ +#!rsc by RouterOS +# RouterOS script: ssh-keys-import +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# import ssh keys from file +# https://git.eworm.de/cgit/routeros-scripts/about/doc/ssh-keys-import.md + +:foreach Key in=[ /file/find where type="ssh key" ] do={ + /user/ssh-key/import user=admin public-key-file=[ /file/get $Key name ]; +} diff --git a/super-mario-theme b/super-mario-theme index 7787a12..2da00ca 100644 --- a/super-mario-theme +++ b/super-mario-theme @@ -1,69 +1,3 @@ #!rsc by RouterOS -# RouterOS script: super-mario-theme -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# play Super Mario theme -# https://git.eworm.de/cgit/routeros-scripts/about/doc/super-mario-theme.md - -:local Beeps { - { 660; 100 }; 150; { 660; 100 }; 300; { 660; 100 }; 300; - { 510; 100 }; 100; { 660; 100 }; 300; { 770; 100 }; 550; - { 380; 100 }; 575; { 510; 100 }; 450; { 380; 100 }; 400; - { 320; 100 }; 500; { 440; 100 }; 300; { 480; 80 }; 330; - { 450; 100 }; 150; { 430; 100 }; 300; { 380; 100 }; 200; - { 660; 80 }; 200; { 760; 50 }; 150; { 860; 100 }; 300; - { 700; 80 }; 150; { 760; 50 }; 350; { 660; 80 }; 300; - { 520; 80 }; 150; { 580; 80 }; 150; { 480; 80 }; 500; - { 510; 100 }; 450; { 380; 100 }; 400; { 320; 100 }; 500; - { 440; 100 }; 300; { 480; 80 }; 330; { 450; 100 }; 150; - { 430; 100 }; 300; { 380; 100 }; 200; { 660; 80 }; 200; - { 760; 50 }; 150; { 860; 100 }; 300; { 700; 80 }; 150; - { 760; 50 }; 350; { 660; 80 }; 300; { 520; 80 }; 150; - { 580; 80 }; 150; { 480; 80 }; 500; { 500; 100 }; 300; - { 760; 100 }; 100; { 720; 100 }; 150; { 680; 100 }; 150; - { 620; 150 }; 300; { 650; 150 }; 300; { 380; 100 }; 150; - { 430; 100 }; 150; { 500; 100 }; 300; { 430; 100 }; 150; - { 500; 100 }; 100; { 570; 100 }; 220; { 500; 100 }; 300; - { 760; 100 }; 100; { 720; 100 }; 150; { 680; 100 }; 150; - { 620; 150 }; 300; { 650; 200 }; 300; { 1020; 80 }; 300; - { 1020; 80 }; 150; { 1020; 80 }; 300; { 380; 100 }; 300; - { 500; 100 }; 300; { 760; 100 }; 100; { 720; 100 }; 150; - { 680; 100 }; 150; { 620; 150 }; 300; { 650; 150 }; 300; - { 380; 100 }; 150; { 430; 100 }; 150; { 500; 100 }; 300; - { 430; 100 }; 150; { 500; 100 }; 100; { 570; 100 }; 420; - { 585; 100 }; 450; { 550; 100 }; 420; { 500; 100 }; 360; - { 380; 100 }; 300; { 500; 100 }; 300; { 500; 100 }; 150; - { 500; 100 }; 300; { 500; 100 }; 300; { 760; 100 }; 100; - { 720; 100 }; 150; { 680; 100 }; 150; { 620; 150 }; 300; - { 650; 150 }; 300; { 380; 100 }; 150; { 430; 100 }; 150; - { 500; 100 }; 300; { 430; 100 }; 150; { 500; 100 }; 100; - { 570; 100 }; 220; { 500; 100 }; 300; { 760; 100 }; 100; - { 720; 100 }; 150; { 680; 100 }; 150; { 620; 150 }; 300; - { 650; 200 }; 300; { 1020; 80 }; 300; { 1020; 80 }; 150; - { 1020; 80 }; 300; { 380; 100 }; 300; { 500; 100 }; 300; - { 760; 100 }; 100; { 720; 100 }; 150; { 680; 100 }; 150; - { 620; 150 }; 300; { 650; 150 }; 300; { 380; 100 }; 150; - { 430; 100 }; 150; { 500; 100 }; 300; { 430; 100 }; 150; - { 500; 100 }; 100; { 570; 100 }; 420; { 585; 100 }; 450; - { 550; 100 }; 420; { 500; 100 }; 360; { 380; 100 }; 300; - { 500; 100 }; 300; { 500; 100 }; 150; { 500; 100 }; 300; - { 500; 60 }; 150; { 500; 80 }; 300; { 500; 60 }; 350; - { 500; 80 }; 150; { 580; 80 }; 350; { 660; 80 }; 150; - { 500; 80 }; 300; { 430; 80 }; 150; { 380; 80 }; 600; - { 500; 60 }; 150; { 500; 80 }; 300; { 500; 60 }; 350; - { 500; 80 }; 150; { 580; 80 }; 150; { 660; 80 }; 550; - { 870; 80 }; 325; { 760; 80 }; 600; { 500; 60 }; 150; - { 500; 80 }; 300; { 500; 60 }; 350; { 500; 80 }; 150; - { 580; 80 }; 350; { 660; 80 }; 150; { 500; 80 }; 300; - { 430; 80 }; 150; { 380; 80 }; 600; { 660; 100 }; 150; - { 660; 100 }; 300; { 660; 100 }; 300; { 510; 100 }; 100; - { 660; 100 }; 300; { 770; 100 }; 550; { 380; 100 }; 575 }; - -:foreach Beep in=$Beeps do={ - :if ([ :len $Beep ] = 2) do={ - :beep frequency=($Beep->0) length=(($Beep->1) . "ms"); - } else={ - :delay ($Beep . "ms"); - } -} +# dummy for migration diff --git a/super-mario-theme.rsc b/super-mario-theme.rsc new file mode 100644 index 0000000..7787a12 --- /dev/null +++ b/super-mario-theme.rsc @@ -0,0 +1,69 @@ +#!rsc by RouterOS +# RouterOS script: super-mario-theme +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# play Super Mario theme +# https://git.eworm.de/cgit/routeros-scripts/about/doc/super-mario-theme.md + +:local Beeps { + { 660; 100 }; 150; { 660; 100 }; 300; { 660; 100 }; 300; + { 510; 100 }; 100; { 660; 100 }; 300; { 770; 100 }; 550; + { 380; 100 }; 575; { 510; 100 }; 450; { 380; 100 }; 400; + { 320; 100 }; 500; { 440; 100 }; 300; { 480; 80 }; 330; + { 450; 100 }; 150; { 430; 100 }; 300; { 380; 100 }; 200; + { 660; 80 }; 200; { 760; 50 }; 150; { 860; 100 }; 300; + { 700; 80 }; 150; { 760; 50 }; 350; { 660; 80 }; 300; + { 520; 80 }; 150; { 580; 80 }; 150; { 480; 80 }; 500; + { 510; 100 }; 450; { 380; 100 }; 400; { 320; 100 }; 500; + { 440; 100 }; 300; { 480; 80 }; 330; { 450; 100 }; 150; + { 430; 100 }; 300; { 380; 100 }; 200; { 660; 80 }; 200; + { 760; 50 }; 150; { 860; 100 }; 300; { 700; 80 }; 150; + { 760; 50 }; 350; { 660; 80 }; 300; { 520; 80 }; 150; + { 580; 80 }; 150; { 480; 80 }; 500; { 500; 100 }; 300; + { 760; 100 }; 100; { 720; 100 }; 150; { 680; 100 }; 150; + { 620; 150 }; 300; { 650; 150 }; 300; { 380; 100 }; 150; + { 430; 100 }; 150; { 500; 100 }; 300; { 430; 100 }; 150; + { 500; 100 }; 100; { 570; 100 }; 220; { 500; 100 }; 300; + { 760; 100 }; 100; { 720; 100 }; 150; { 680; 100 }; 150; + { 620; 150 }; 300; { 650; 200 }; 300; { 1020; 80 }; 300; + { 1020; 80 }; 150; { 1020; 80 }; 300; { 380; 100 }; 300; + { 500; 100 }; 300; { 760; 100 }; 100; { 720; 100 }; 150; + { 680; 100 }; 150; { 620; 150 }; 300; { 650; 150 }; 300; + { 380; 100 }; 150; { 430; 100 }; 150; { 500; 100 }; 300; + { 430; 100 }; 150; { 500; 100 }; 100; { 570; 100 }; 420; + { 585; 100 }; 450; { 550; 100 }; 420; { 500; 100 }; 360; + { 380; 100 }; 300; { 500; 100 }; 300; { 500; 100 }; 150; + { 500; 100 }; 300; { 500; 100 }; 300; { 760; 100 }; 100; + { 720; 100 }; 150; { 680; 100 }; 150; { 620; 150 }; 300; + { 650; 150 }; 300; { 380; 100 }; 150; { 430; 100 }; 150; + { 500; 100 }; 300; { 430; 100 }; 150; { 500; 100 }; 100; + { 570; 100 }; 220; { 500; 100 }; 300; { 760; 100 }; 100; + { 720; 100 }; 150; { 680; 100 }; 150; { 620; 150 }; 300; + { 650; 200 }; 300; { 1020; 80 }; 300; { 1020; 80 }; 150; + { 1020; 80 }; 300; { 380; 100 }; 300; { 500; 100 }; 300; + { 760; 100 }; 100; { 720; 100 }; 150; { 680; 100 }; 150; + { 620; 150 }; 300; { 650; 150 }; 300; { 380; 100 }; 150; + { 430; 100 }; 150; { 500; 100 }; 300; { 430; 100 }; 150; + { 500; 100 }; 100; { 570; 100 }; 420; { 585; 100 }; 450; + { 550; 100 }; 420; { 500; 100 }; 360; { 380; 100 }; 300; + { 500; 100 }; 300; { 500; 100 }; 150; { 500; 100 }; 300; + { 500; 60 }; 150; { 500; 80 }; 300; { 500; 60 }; 350; + { 500; 80 }; 150; { 580; 80 }; 350; { 660; 80 }; 150; + { 500; 80 }; 300; { 430; 80 }; 150; { 380; 80 }; 600; + { 500; 60 }; 150; { 500; 80 }; 300; { 500; 60 }; 350; + { 500; 80 }; 150; { 580; 80 }; 150; { 660; 80 }; 550; + { 870; 80 }; 325; { 760; 80 }; 600; { 500; 60 }; 150; + { 500; 80 }; 300; { 500; 60 }; 350; { 500; 80 }; 150; + { 580; 80 }; 350; { 660; 80 }; 150; { 500; 80 }; 300; + { 430; 80 }; 150; { 380; 80 }; 600; { 660; 100 }; 150; + { 660; 100 }; 300; { 660; 100 }; 300; { 510; 100 }; 100; + { 660; 100 }; 300; { 770; 100 }; 550; { 380; 100 }; 575 }; + +:foreach Beep in=$Beeps do={ + :if ([ :len $Beep ] = 2) do={ + :beep frequency=($Beep->0) length=(($Beep->1) . "ms"); + } else={ + :delay ($Beep . "ms"); + } +} diff --git a/telegram-chat b/telegram-chat index ba2a3ff..2da00ca 100644 --- a/telegram-chat +++ b/telegram-chat @@ -1,150 +1,3 @@ #!rsc by RouterOS -# RouterOS script: telegram-chat -# Copyright (c) 2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# use Telegram to chat with your Router and send commands -# https://git.eworm.de/cgit/routeros-scripts/about/doc/telegram-chat.md - -:local 0 "telegram-chat"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global Identity; -:global TelegramChatActive; -:global TelegramChatGroups; -:global TelegramChatId; -:global TelegramChatIdsTrusted; -:global TelegramChatOffset; -:global TelegramChatRunTime; -:global TelegramTokenId; - -:global CertificateAvailable; -:global EitherOr; -:global EscapeForRegEx; -:global GetRandom20CharAlNum; -:global IfThenElse; -:global LogPrintExit2; -:global MkDir; -:global ScriptLock; -:global SendTelegram2; -:global SymbolForNotification; -:global ValidateSyntax; -:global WaitForFile; -:global WaitFullyConnected; - -$ScriptLock $0; - -$WaitFullyConnected; - -:if ([ :typeof $TelegramChatOffset ] != "array") do={ - :set TelegramChatOffset { 0; 0; 0 }; -} - -:if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; -} - -:local JsonGetKey do={ - :local Array [ :toarray $1 ]; - :local Key [ :tostr $2 ]; - - :for I from=0 to=([ :len $Array ] - 1) do={ - :if (($Array->$I) = $Key) do={ - :if ($Array->($I + 1) = ":") do={ - :return ($Array->($I + 2)); - } - :return [ :pick ($Array->($I + 1)) 1 [ :len ($Array->($I + 1)) ] ]; - } - } - - :return false; -} - -:local Data; -:do { - :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ - ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \ - $TelegramChatOffset->0 . "&allowed_updates=%5B%22message%22%5D") as-value ]->"data"); - :set Data [ :pick $Data ([ :find $Data "[" ] + 1) ([ :len $Data ] - 2) ]; -} on-error={ - $LogPrintExit2 debug $0 ("Failed getting updates from Telegram.") true; -} - -:local UpdateID 0; -:local Uptime [ /system/resource/get uptime ]; -:foreach Update in=[ :toarray $Data ] do={ - :set UpdateID [ $JsonGetKey $Update "update_id" ]; - :if (($TelegramChatOffset->0 > 0 || $Uptime > 5m) && $UpdateID >= $TelegramChatOffset->2) do={ - :local Trusted false; - :local Message [ $JsonGetKey $Update "message" ]; - :local MessageId [ $JsonGetKey $Message "message_id" ]; - :local From [ $JsonGetKey $Message "from" ]; - :local FromID [ $JsonGetKey $From "id" ]; - :local FromUserName [ $JsonGetKey $From "username" ]; - :local ChatID [ $JsonGetKey [ $JsonGetKey $Message "chat" ] "id" ]; - :local Text [ $JsonGetKey $Message "text" ]; - :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={ - :if ($FromID = $IdsTrusted || $FromUserName = $IdsTrusted) do={ - :set Trusted true; - } - } - - :if ($Trusted = true) do={ - :if ([ :pick $Text 0 1 ] = "!") do={ - :if ($Text ~ ("^! *(" . [ $EscapeForRegEx $Identity ] . "|@" . $TelegramChatGroups . ")\$")) do={ - :set TelegramChatActive true; - } else={ - :set TelegramChatActive false; - } - $LogPrintExit2 info $0 ("Now " . [ $IfThenElse $TelegramChatActive "active" "passive" ] . \ - " from update " . $UpdateID . "!") false; - } else={ - :if ($TelegramChatActive = true && $Text != false && [ :len $Text ] > 0) do={ - :if ([ $ValidateSyntax $Text ] = true) do={ - :local State ""; - :local File ("tmpfs/telegram-chat/" . [ $GetRandom20CharAlNum 6 ]); - $MkDir "tmpfs/telegram-chat"; - $LogPrintExit2 info $0 ("Running command from update " . $UpdateID . ": " . $Text) false; - :exec script=(":do {\n" . $Text . "\n} on-error={ :execute script=\"/\" file=" . $File . ".failed };" . \ - ":execute script=\"/\" file=" . $File . ".done") file=$File; - :if ([ $WaitForFile ($File . ".done.txt") [ $EitherOr $TelegramChatRunTime 20s ] ] = false) do={ - :set State "The command did not finish, still running in background.\n\n"; - } - :if ([ :len [ /file/find where name=($File . ".failed.txt") ] ] > 0) do={ - :set State "The command failed with an error!\n\n"; - } - :local Content [ /file/get ($File . ".txt") contents ]; - $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=("Command:\n" . $Text . "\n\n" . $State . [ $IfThenElse ([ :len $Content ] > 0) \ - ("Output:\n" . $Content) [ $IfThenElse ([ /file/get ($File . ".txt") size ] > 0) \ - ("Output exceeds file read size.") ("No output.") ] ]) }); - /file/remove "tmpfs/telegram-chat"; - } else={ - $LogPrintExit2 info $0 ("The command from update " . $UpdateID . " failed syntax validation!") false; - $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=("Command:\n" . $Text . "\n\nThe command failed syntax validation!") }); - } - } - } - } else={ - :local Message ("Received a message from untrusted contact " . \ - [ $IfThenElse ($FromUserName = false) "without username" ("'" . $FromUserName . "'") ] . \ - " (ID " . $FromID . ") in update " . $UpdateID . "!"); - :if ($Text ~ ("^! *" . [ $EscapeForRegEx $Identity ] . "\$")) do={ - $LogPrintExit2 warning $0 $Message false; - $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=("You are not trusted.") }); - } else={ - $LogPrintExit2 info $0 $Message false; - } - } - } else={ - $LogPrintExit2 debug $0 ("Already handled update " . $UpdateID . ".") false; - } -} -:set TelegramChatOffset ([ :pick $TelegramChatOffset 1 3 ], \ - [ $IfThenElse ($UpdateID >= $TelegramChatOffset->2) ($UpdateID + 1) ($TelegramChatOffset->2) ]); +# dummy for migration diff --git a/telegram-chat.rsc b/telegram-chat.rsc new file mode 100644 index 0000000..ba2a3ff --- /dev/null +++ b/telegram-chat.rsc @@ -0,0 +1,150 @@ +#!rsc by RouterOS +# RouterOS script: telegram-chat +# Copyright (c) 2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# use Telegram to chat with your Router and send commands +# https://git.eworm.de/cgit/routeros-scripts/about/doc/telegram-chat.md + +:local 0 "telegram-chat"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Identity; +:global TelegramChatActive; +:global TelegramChatGroups; +:global TelegramChatId; +:global TelegramChatIdsTrusted; +:global TelegramChatOffset; +:global TelegramChatRunTime; +:global TelegramTokenId; + +:global CertificateAvailable; +:global EitherOr; +:global EscapeForRegEx; +:global GetRandom20CharAlNum; +:global IfThenElse; +:global LogPrintExit2; +:global MkDir; +:global ScriptLock; +:global SendTelegram2; +:global SymbolForNotification; +:global ValidateSyntax; +:global WaitForFile; +:global WaitFullyConnected; + +$ScriptLock $0; + +$WaitFullyConnected; + +:if ([ :typeof $TelegramChatOffset ] != "array") do={ + :set TelegramChatOffset { 0; 0; 0 }; +} + +:if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={ + $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; +} + +:local JsonGetKey do={ + :local Array [ :toarray $1 ]; + :local Key [ :tostr $2 ]; + + :for I from=0 to=([ :len $Array ] - 1) do={ + :if (($Array->$I) = $Key) do={ + :if ($Array->($I + 1) = ":") do={ + :return ($Array->($I + 2)); + } + :return [ :pick ($Array->($I + 1)) 1 [ :len ($Array->($I + 1)) ] ]; + } + } + + :return false; +} + +:local Data; +:do { + :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ + ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \ + $TelegramChatOffset->0 . "&allowed_updates=%5B%22message%22%5D") as-value ]->"data"); + :set Data [ :pick $Data ([ :find $Data "[" ] + 1) ([ :len $Data ] - 2) ]; +} on-error={ + $LogPrintExit2 debug $0 ("Failed getting updates from Telegram.") true; +} + +:local UpdateID 0; +:local Uptime [ /system/resource/get uptime ]; +:foreach Update in=[ :toarray $Data ] do={ + :set UpdateID [ $JsonGetKey $Update "update_id" ]; + :if (($TelegramChatOffset->0 > 0 || $Uptime > 5m) && $UpdateID >= $TelegramChatOffset->2) do={ + :local Trusted false; + :local Message [ $JsonGetKey $Update "message" ]; + :local MessageId [ $JsonGetKey $Message "message_id" ]; + :local From [ $JsonGetKey $Message "from" ]; + :local FromID [ $JsonGetKey $From "id" ]; + :local FromUserName [ $JsonGetKey $From "username" ]; + :local ChatID [ $JsonGetKey [ $JsonGetKey $Message "chat" ] "id" ]; + :local Text [ $JsonGetKey $Message "text" ]; + :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={ + :if ($FromID = $IdsTrusted || $FromUserName = $IdsTrusted) do={ + :set Trusted true; + } + } + + :if ($Trusted = true) do={ + :if ([ :pick $Text 0 1 ] = "!") do={ + :if ($Text ~ ("^! *(" . [ $EscapeForRegEx $Identity ] . "|@" . $TelegramChatGroups . ")\$")) do={ + :set TelegramChatActive true; + } else={ + :set TelegramChatActive false; + } + $LogPrintExit2 info $0 ("Now " . [ $IfThenElse $TelegramChatActive "active" "passive" ] . \ + " from update " . $UpdateID . "!") false; + } else={ + :if ($TelegramChatActive = true && $Text != false && [ :len $Text ] > 0) do={ + :if ([ $ValidateSyntax $Text ] = true) do={ + :local State ""; + :local File ("tmpfs/telegram-chat/" . [ $GetRandom20CharAlNum 6 ]); + $MkDir "tmpfs/telegram-chat"; + $LogPrintExit2 info $0 ("Running command from update " . $UpdateID . ": " . $Text) false; + :exec script=(":do {\n" . $Text . "\n} on-error={ :execute script=\"/\" file=" . $File . ".failed };" . \ + ":execute script=\"/\" file=" . $File . ".done") file=$File; + :if ([ $WaitForFile ($File . ".done.txt") [ $EitherOr $TelegramChatRunTime 20s ] ] = false) do={ + :set State "The command did not finish, still running in background.\n\n"; + } + :if ([ :len [ /file/find where name=($File . ".failed.txt") ] ] > 0) do={ + :set State "The command failed with an error!\n\n"; + } + :local Content [ /file/get ($File . ".txt") contents ]; + $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ + subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ + message=("Command:\n" . $Text . "\n\n" . $State . [ $IfThenElse ([ :len $Content ] > 0) \ + ("Output:\n" . $Content) [ $IfThenElse ([ /file/get ($File . ".txt") size ] > 0) \ + ("Output exceeds file read size.") ("No output.") ] ]) }); + /file/remove "tmpfs/telegram-chat"; + } else={ + $LogPrintExit2 info $0 ("The command from update " . $UpdateID . " failed syntax validation!") false; + $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ + subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ + message=("Command:\n" . $Text . "\n\nThe command failed syntax validation!") }); + } + } + } + } else={ + :local Message ("Received a message from untrusted contact " . \ + [ $IfThenElse ($FromUserName = false) "without username" ("'" . $FromUserName . "'") ] . \ + " (ID " . $FromID . ") in update " . $UpdateID . "!"); + :if ($Text ~ ("^! *" . [ $EscapeForRegEx $Identity ] . "\$")) do={ + $LogPrintExit2 warning $0 $Message false; + $SendTelegram2 ({ origin=$0; chatid=$ChatID; silent=false; replyto=$MessageId; \ + subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ + message=("You are not trusted.") }); + } else={ + $LogPrintExit2 info $0 $Message false; + } + } + } else={ + $LogPrintExit2 debug $0 ("Already handled update " . $UpdateID . ".") false; + } +} +:set TelegramChatOffset ([ :pick $TelegramChatOffset 1 3 ], \ + [ $IfThenElse ($UpdateID >= $TelegramChatOffset->2) ($UpdateID + 1) ($TelegramChatOffset->2) ]); diff --git a/unattended-lte-firmware-upgrade b/unattended-lte-firmware-upgrade index eac65c3..2da00ca 100644 --- a/unattended-lte-firmware-upgrade +++ b/unattended-lte-firmware-upgrade @@ -1,45 +1,3 @@ #!rsc by RouterOS -# RouterOS script: unattended-lte-firmware-upgrade -# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# schedule unattended lte firmware upgrade -# https://git.eworm.de/cgit/routeros-scripts/about/doc/unattended-lte-firmware-upgrade.md - -:foreach Interface in=[ /interface/lte/find where running ] do={ - :local Firmware; - :local IntName [ /interface/lte/get $Interface name ]; - :do { - :set Firmware [ /interface/lte/firmware-upgrade $Interface once as-value ]; - } on-error={ - :log debug ("Could not get latest LTE firmware version for interface " . $IntName . "."); - } - - :if ([ :typeof $Firmware ] = "array") do={ - :if (($Firmware->"installed") != ($Firmware->"latest")) do={ - :log info ("Scheduling LTE firmware upgrade for interface " . $IntName . "."); - - :global LTEFirmwareUpgrade do={ - :global LTEFirmwareUpgrade; - :set LTEFirmwareUpgrade; - - /system/scheduler/remove ($1 . "-firmware-upgrade"); - /interface/lte/firmware-upgrade $1 upgrade=yes; - :log info ("LTE firmware upgrade on '" . $1 . "' finished, waiting for reset."); - :delay 240s; - :local Firmware [ /interface/lte/firmware-upgrade $1 once as-value ]; - :if (($Firmware->"installed") != ($Firmware->"latest")) do={ - :log warning ("LTE firmware versions still differ. Resetting again..."); - /interface/lte/at-chat $1 input="AT+RESET"; - } - } - - /system/scheduler/add name=($IntName . "-firmware-upgrade") start-time=startup interval=2s \ - on-event=(":global LTEFirmwareUpgrade; \$LTEFirmwareUpgrade \"" . $IntName . "\";"); - } else={ - :log info ("The LTE firmware is up to date on interface " . $IntName . "."); - } - } else={ - :log info ("No LTE firmware information available for interface " . $IntName . "."); - } -} +# dummy for migration diff --git a/unattended-lte-firmware-upgrade.rsc b/unattended-lte-firmware-upgrade.rsc new file mode 100644 index 0000000..eac65c3 --- /dev/null +++ b/unattended-lte-firmware-upgrade.rsc @@ -0,0 +1,45 @@ +#!rsc by RouterOS +# RouterOS script: unattended-lte-firmware-upgrade +# Copyright (c) 2018-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# schedule unattended lte firmware upgrade +# https://git.eworm.de/cgit/routeros-scripts/about/doc/unattended-lte-firmware-upgrade.md + +:foreach Interface in=[ /interface/lte/find where running ] do={ + :local Firmware; + :local IntName [ /interface/lte/get $Interface name ]; + :do { + :set Firmware [ /interface/lte/firmware-upgrade $Interface once as-value ]; + } on-error={ + :log debug ("Could not get latest LTE firmware version for interface " . $IntName . "."); + } + + :if ([ :typeof $Firmware ] = "array") do={ + :if (($Firmware->"installed") != ($Firmware->"latest")) do={ + :log info ("Scheduling LTE firmware upgrade for interface " . $IntName . "."); + + :global LTEFirmwareUpgrade do={ + :global LTEFirmwareUpgrade; + :set LTEFirmwareUpgrade; + + /system/scheduler/remove ($1 . "-firmware-upgrade"); + /interface/lte/firmware-upgrade $1 upgrade=yes; + :log info ("LTE firmware upgrade on '" . $1 . "' finished, waiting for reset."); + :delay 240s; + :local Firmware [ /interface/lte/firmware-upgrade $1 once as-value ]; + :if (($Firmware->"installed") != ($Firmware->"latest")) do={ + :log warning ("LTE firmware versions still differ. Resetting again..."); + /interface/lte/at-chat $1 input="AT+RESET"; + } + } + + /system/scheduler/add name=($IntName . "-firmware-upgrade") start-time=startup interval=2s \ + on-event=(":global LTEFirmwareUpgrade; \$LTEFirmwareUpgrade \"" . $IntName . "\";"); + } else={ + :log info ("The LTE firmware is up to date on interface " . $IntName . "."); + } + } else={ + :log info ("No LTE firmware information available for interface " . $IntName . "."); + } +} diff --git a/update-gre-address b/update-gre-address index 2958055..2da00ca 100644 --- a/update-gre-address +++ b/update-gre-address @@ -1,32 +1,3 @@ #!rsc by RouterOS -# RouterOS script: update-gre-address -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# update gre interface remote address with dynamic address from -# ipsec remote peer -# https://git.eworm.de/cgit/routeros-scripts/about/doc/update-gre-address.md - -:local 0 "update-gre-address"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global CharacterReplace; -:global LogPrintExit2; - -/interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where !running !disabled ]; - -:foreach Peer in=[ /ip/ipsec/active-peers/find ] do={ - :local PeerVal [ /ip/ipsec/active-peers/get $Peer ]; - :local GreInt [ /interface/gre/find where comment=($PeerVal->"id") or comment=[ $CharacterReplace ($PeerVal->"id") "CN=" "" ] ]; - :if ([ :len $GreInt ] > 0) do={ - :local GreIntVal [ /interface/gre/get $GreInt ]; - :if ([ :typeof ($PeerVal->"dynamic-address") ] = "str" && \ - ($PeerVal->"dynamic-address" != $GreIntVal->"remote-address" || \ - $GreIntVal->"disabled" = true)) do={ - $LogPrintExit2 info $0 ("Updating remote address for interface " . $GreIntVal->"name" . " to " . $PeerVal->"dynamic-address") false; - /interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where remote-address=$PeerVal->"dynamic-address" name!=$GreIntVal->"name" ]; - /interface/gre/set $GreInt remote-address=($PeerVal->"dynamic-address") disabled=no; - } - } -} +# dummy for migration diff --git a/update-gre-address.rsc b/update-gre-address.rsc new file mode 100644 index 0000000..2958055 --- /dev/null +++ b/update-gre-address.rsc @@ -0,0 +1,32 @@ +#!rsc by RouterOS +# RouterOS script: update-gre-address +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# update gre interface remote address with dynamic address from +# ipsec remote peer +# https://git.eworm.de/cgit/routeros-scripts/about/doc/update-gre-address.md + +:local 0 "update-gre-address"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global CharacterReplace; +:global LogPrintExit2; + +/interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where !running !disabled ]; + +:foreach Peer in=[ /ip/ipsec/active-peers/find ] do={ + :local PeerVal [ /ip/ipsec/active-peers/get $Peer ]; + :local GreInt [ /interface/gre/find where comment=($PeerVal->"id") or comment=[ $CharacterReplace ($PeerVal->"id") "CN=" "" ] ]; + :if ([ :len $GreInt ] > 0) do={ + :local GreIntVal [ /interface/gre/get $GreInt ]; + :if ([ :typeof ($PeerVal->"dynamic-address") ] = "str" && \ + ($PeerVal->"dynamic-address" != $GreIntVal->"remote-address" || \ + $GreIntVal->"disabled" = true)) do={ + $LogPrintExit2 info $0 ("Updating remote address for interface " . $GreIntVal->"name" . " to " . $PeerVal->"dynamic-address") false; + /interface/gre/set remote-address=0.0.0.0 disabled=yes [ find where remote-address=$PeerVal->"dynamic-address" name!=$GreIntVal->"name" ]; + /interface/gre/set $GreInt remote-address=($PeerVal->"dynamic-address") disabled=no; + } + } +} diff --git a/update-tunnelbroker b/update-tunnelbroker index 84d7430..2da00ca 100644 --- a/update-tunnelbroker +++ b/update-tunnelbroker @@ -1,55 +1,3 @@ #!rsc by RouterOS -# RouterOS script: update-tunnelbroker -# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # -# provides: ppp-on-up -# -# update local address of tunnelbroker interface -# https://git.eworm.de/cgit/routeros-scripts/about/doc/update-tunnelbroker.md - -:local 0 "update-tunnelbroker"; -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:global CertificateAvailable; -:global LogPrintExit2; -:global ParseKeyValueStore; - -:if ([ $CertificateAvailable "Starfield Secure Certificate Authority - G2" ] = false) do={ - $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; -} - -:foreach Interface in=[ /interface/6to4/find where comment~"^tunnelbroker" !disabled ] do={ - :local I 0; - :local Response ""; - :local InterfaceVal [ /interface/6to4/get $Interface ]; - :local Comment [ $ParseKeyValueStore ($InterfaceVal->"comment") ]; - - :while ($I < 3 && $Response = "") do={ - :do { - :set Response ([ /tool/fetch check-certificate=yes-without-crl \ - ("https://ipv4.tunnelbroker.net/nic/update\?hostname=" . $Comment->"id") \ - user=($Comment->"user") password=($Comment->"pass") output=user as-value ]->"data"); - } on-error={ - :delay 10s; - :set I ($I + 1); - } - } - - :if (!($Response~"^(good|nochg) ")) do={ - $LogPrintExit2 error $0 ("Failed sending the local address to tunnelbroker or unexpected response!") true; - } - - :local PublicAddress [ :pick $Response ([ :find $Response " " ] + 1) [ :find $Response "\n" ] ]; - - :if ($PublicAddress != $InterfaceVal->"local-address") do={ - :if ([ :len [ /ip/address find where address~("^" . $PublicAddress . "/") ] ] < 1) do={ - $LogPrintExit2 warning $0 ("The address " . $PublicAddress . " is not configured on your device. NAT by ISP?") false; - } - - $LogPrintExit2 info $0 ("Local address changed, updating tunnel configuration with address: " . $PublicAddress) false; - /interface/6to4/set $Interface local-address=$PublicAddress; - } -} +# dummy for migration diff --git a/update-tunnelbroker.rsc b/update-tunnelbroker.rsc new file mode 100644 index 0000000..84d7430 --- /dev/null +++ b/update-tunnelbroker.rsc @@ -0,0 +1,55 @@ +#!rsc by RouterOS +# RouterOS script: update-tunnelbroker +# Copyright (c) 2013-2023 Christian Hesse <mail@eworm.de> +# Michael Gisbers <michael@gisbers.de> +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# provides: ppp-on-up +# +# update local address of tunnelbroker interface +# https://git.eworm.de/cgit/routeros-scripts/about/doc/update-tunnelbroker.md + +:local 0 "update-tunnelbroker"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global CertificateAvailable; +:global LogPrintExit2; +:global ParseKeyValueStore; + +:if ([ $CertificateAvailable "Starfield Secure Certificate Authority - G2" ] = false) do={ + $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; +} + +:foreach Interface in=[ /interface/6to4/find where comment~"^tunnelbroker" !disabled ] do={ + :local I 0; + :local Response ""; + :local InterfaceVal [ /interface/6to4/get $Interface ]; + :local Comment [ $ParseKeyValueStore ($InterfaceVal->"comment") ]; + + :while ($I < 3 && $Response = "") do={ + :do { + :set Response ([ /tool/fetch check-certificate=yes-without-crl \ + ("https://ipv4.tunnelbroker.net/nic/update\?hostname=" . $Comment->"id") \ + user=($Comment->"user") password=($Comment->"pass") output=user as-value ]->"data"); + } on-error={ + :delay 10s; + :set I ($I + 1); + } + } + + :if (!($Response~"^(good|nochg) ")) do={ + $LogPrintExit2 error $0 ("Failed sending the local address to tunnelbroker or unexpected response!") true; + } + + :local PublicAddress [ :pick $Response ([ :find $Response " " ] + 1) [ :find $Response "\n" ] ]; + + :if ($PublicAddress != $InterfaceVal->"local-address") do={ + :if ([ :len [ /ip/address find where address~("^" . $PublicAddress . "/") ] ] < 1) do={ + $LogPrintExit2 warning $0 ("The address " . $PublicAddress . " is not configured on your device. NAT by ISP?") false; + } + + $LogPrintExit2 info $0 ("Local address changed, updating tunnel configuration with address: " . $PublicAddress) false; + /interface/6to4/set $Interface local-address=$PublicAddress; + } +} |