diff --git a/.gitignore b/.gitignore index cf89f87..f29d4e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,3 @@ -# backup and temporary files *~ - -# patches and related files -*.orig *.patch -*.rej - -# html files (as generated from markdown) *.html - -# Mac OS X folder settings file -.DS_Store diff --git a/BRANCHES.md b/BRANCHES.md deleted file mode 100644 index 8a0bdad..0000000 --- a/BRANCHES.md +++ /dev/null @@ -1,50 +0,0 @@ -Installing from branches -======================== - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](README.md) - -> ⚠️ **Warning**: Living on the edge? Great, read on! -> If not: Please use the `main` branch and leave this page! - -These scripts are developed in a [git](https://git-scm.com/) repository. -Development and experimental branches are used to provide early access -for specific changes. You can install scripts from these branches -for testing. - -## Install single script - -To install a single script from `next` branch: - - $ScriptInstallUpdate script-name "base-url=https://rsc.eworm.de/next/"; - -## Switch existing script - -Alternatively switch an existing script to update from `next` branch: - - /system/script/set comment="base-url=https://rsc.eworm.de/next/" script-name; - $ScriptInstallUpdate; - -## Switch installation - -Last but not least - to switch the complete installation to the `next` -branch edit `global-config-overlay` and add: - - :global ScriptUpdatesBaseUrl "https://rsc.eworm.de/next/"; - -... then reload the configuration and update: - - /system/script/run global-config; - $ScriptInstallUpdate; - -> ℹ️ **Info**: Replace `next` with *whatever* to use another specific branch. - ---- -[⬅️ Go back to main README](README.md) -[⬆️ Go back to top](#top) diff --git a/CERTIFICATES.d/01-dialog-A.avif b/CERTIFICATES.d/01-dialog-A.avif deleted file mode 100644 index 2fc3c9b..0000000 Binary files a/CERTIFICATES.d/01-dialog-A.avif and /dev/null differ diff --git a/CERTIFICATES.d/02-dialog-B.avif b/CERTIFICATES.d/02-dialog-B.avif deleted file mode 100644 index 5e408ab..0000000 Binary files a/CERTIFICATES.d/02-dialog-B.avif and /dev/null differ diff --git a/CERTIFICATES.d/03-window.avif b/CERTIFICATES.d/03-window.avif deleted file mode 100644 index 96039a3..0000000 Binary files a/CERTIFICATES.d/03-window.avif and /dev/null differ diff --git a/CERTIFICATES.d/04-certificate.avif b/CERTIFICATES.d/04-certificate.avif deleted file mode 100644 index e666314..0000000 Binary files a/CERTIFICATES.d/04-certificate.avif and /dev/null differ diff --git a/CERTIFICATES.md b/CERTIFICATES.md deleted file mode 100644 index 5432d78..0000000 --- a/CERTIFICATES.md +++ /dev/null @@ -1,82 +0,0 @@ -Certificate name from browser -============================= - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](README.md) - -All well known desktop, mobile and server operating systems come with a -certificate store that is populated with a set of well known and trusted -certificates, acting as *trust anchors*. - -However RouterOS does not, still sometimes a specific certificate is -required to properly verify a chain of trust. One example is downloading -the scripts from this repository with `fetch` command, thus the very -first step of [installation](README.md#the-long-way-in-detail) is importing -the certificate. - -The scripts can install additional certificates when required. This happens -from this repository if available, or from [mkcert.org](https://mkcert.org) -as a fallback. - -Get the certificate's CommonName --------------------------------- - -But how to determine what certificate may be required? Often easiest way -is to use a desktop browser to get that information. This demonstration uses -[Mozilla Firefox](https://www.mozilla.org/firefox/). - -Let's assume we want to make sure the certificate for -[git.eworm.de](https://git.eworm.de/) is available. Open that page in the -browser, then click the *lock* icon in addressbar, followed by "*Connection -secure*". - -![screenshot: dialog A](CERTIFICATES.d/01-dialog-A.avif) - -The dialog will change, click "*More information*". - -![screenshot: dialog B](CERTIFICATES.d/02-dialog-B.avif) - -A new window opens, click the button "*View Certificate*". (That window -can be closed now.) - -![screenshot: window](CERTIFICATES.d/03-window.avif) - -A new tab opens, showing information on the server certificate and its -chain of trust. The leftmost certificate is what we are interested in. - -![screenshot: certificate](CERTIFICATES.d/04-certificate.avif) - -Now we know that "`ISRG Root X2`" is required, some scripts need just -that information. - -Import a certificate by CommonName ----------------------------------- - -Running the function `$CertificateAvailable` with that name as parameter -makes sure the certificate is available in the device's store: - - $CertificateAvailable "ISRG Root X2"; - -If the certificate is actually available already nothing happens, and there -is no output. Otherwise the certificate is downloaded and imported. - -If importing a certificate with that exact name fails a warning is given -and nothing is actually imported. - -See also --------- - -* [Download, import and update firewall address-lists](doc/fw-addr-lists.md) -* [Manage DNS and DoH servers from netwatch](doc/netwatch-dns.md) -* [Send notifications via Matrix](doc/mod/notification-matrix.md) -* [Send notifications via Ntfy](doc/mod/notification-ntfy.md) - ---- -[⬅️ Go back to main README](README.md) -[⬆️ Go back to top](#top) diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index 0b35c40..a427aed 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -1,29 +1,18 @@ Past Contributions ================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) +[◀ Go back to main README](README.md) -[⬅️ Go back to main README](README.md) - -Thanks a lot for your contributions! ❤️ +Thanks a lot for your contributions! ## Patches These persons contributed code or documentation. See the git history for details! -* [Anatoly Bubenkov](mailto:bubenkoff@gmail.com) (@bubenkoff) * [Ben Harris](mailto:mail@bharr.is) (@bharrisau) * [Daniel Ziegenberg](mailto:daniel@ziegenberg.at) (@ziegenberg) -* [Ignacio Serrano](mailto:ignic@ignic.com) (@ignic) * [Michael Gisbers](mailto:michael@gisbers.de) (@mgisbers) -* [Miquel Bonastre](mailto:mbonastre@yahoo.com) (@mbonastre) -* @netravnen * [netztrip](mailto:dave-tvg@netztrip.de) (@netztrip) * [Stefan Müller](mailto:stefan.mueller.83@gmail.com) (@PackElend) @@ -33,31 +22,20 @@ Add yourself to the list, [donate with PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J)! * Abdul Mannan Abbasi -* Andrea Ruffini Perico * Andrew Cox * Christoph Boss (@Kampfwurst) -* Daniel Ziegenberg (@ziegenberg) * Devin Dean (@dd2594gh) * Evaldo Gardenal -* Florian Estraviz -* Giorgio Bikos -* Harold Schoemaker -* Hugo BV * Klaus Michael Rübsam -* Leonardo Valeri Manera * Linux-Schmie.de Michael Gisbers * Manuel Kuhn * Marek Čábák * Oleksandr Yukhymchuk * Peter Holtkamp -* Peter Ponzel * Reiner Vehrenkamp -* Richard Österreicher -* Simon Hitzemann * Sunny Chu (@sunnychuchu) -* Ulrich Wessendorf * Zac Kornilakis --- -[⬅️ Go back to main README](README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](README.md) +[▲ Go back to top](#top) diff --git a/DEBUG.md b/DEBUG.md deleted file mode 100644 index d5e9beb..0000000 --- a/DEBUG.md +++ /dev/null @@ -1,63 +0,0 @@ -Debug output and logs -===================== - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](README.md) - -Sometimes scripts do not behave as expected. In these cases debug output -or logs can help. - -## Debug output - -Run this command in a terminal: - - :set PrintDebug true; - -You will then see debug output when running the script from terminal. - -To revert to default output run: - - :set PrintDebug false; - -### Debug output for specific script - -Even having debug output for a specific script or function only (or a -set of) is possible. To enable debug output for `telegram-chat` run: - - :set ($PrintDebugOverride->"telegram-chat") true; - -## Debug logs - -The debug info can go to system log. To make it show up in `memory` run: - - /system/logging/add topics=script,debug action=memory; - -Other actions (`disk`, `email`, `remote` or `support`) can be used as -well. I do not recommend using `echo` - use [debug output](#debug-output) -instead. - -Disable or remote that setting to restore regular logging. - -## Verbose output - -Specific scripts can generate huge amount of output. These do use a function -`$LogPrintVerbose`, which is declared, but has no code, intentionally. - -If you *really* want that output set the function to be the same as -`$LogPrint`: - - :set LogPrintVerbose $LogPrint; - -To revert that change just run: - - :set LogPrintVerbose; - ---- -[⬅️ Go back to main README](README.md) -[⬆️ Go back to top](#top) diff --git a/INITIAL-COMMANDS.md b/INITIAL-COMMANDS.md index 8b64d28..212e8be 100644 --- a/INITIAL-COMMANDS.md +++ b/INITIAL-COMMANDS.md @@ -1,53 +1,36 @@ Initial commands ================ -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) +[◀ Go back to main README](README.md) -[⬅️ Go back to main README](README.md) - -> ⚠️ **Warning**: These command are inteneded for initial setup. If you are +> ⚠️ **Warning**: These commands are inteneded for initial setup. If you are > not aware of the procedure please follow > [the long way in detail](README.md#the-long-way-in-detail). Run the complete base installation: { - /tool/fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/ISRG-Root-X2.pem" dst-path="isrg-root-x2.pem" as-value; + / tool fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/ISRG-Root-X2.pem" dst-path="isrg-root-x2.pem" as-value; :delay 1s; - /certificate/import file-name="isrg-root-x2.pem" passphrase=""; - :if ([ :len [ /certificate/find where fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470" ] ] != 1) do={ + / certificate import file-name=isrg-root-x2.pem passphrase=""; + :if ([ :len [ / certificate find where fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470" ] ] != 1) do={ :error "Something is wrong with your certificates!"; }; + / file remove "isrg-root-x2.pem"; :delay 1s; - /system/script/set name=("global-config-overlay-" . [ /system/clock/get date ] . "-" . [ /system/clock/get time ]) [ find where name="global-config-overlay" ]; :foreach Script in={ "global-config"; "global-config-overlay"; "global-functions" } do={ - /system/script/remove [ find where name=$Script ]; - /system/script/add name=$Script owner=$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 add name=$Script source=([ / tool fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . "\?h=routeros-v6") output=user as-value]->"data"); }; - /system/script { run global-config; run global-functions; }; - /system/scheduler/remove [ find where name="global-scripts" ]; - /system/scheduler/add name="global-scripts" start-time=startup on-event="/system/script { run global-config; run global-functions; }"; + / 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; }"; :global CertificateNameByCN; $CertificateNameByCN "ISRG Root X2"; - }; + } -Then continue setup with -[scheduled automatic updates](README.md#scheduled-automatic-updates) or -[editing configuration](README.md#editing-configuration). +Optional to update the scripts automatically: -## Fix existing installation - -The [initial commands](#initial-commands) above allow to fix an existing -installation in case it ever breaks. If `global-config-overlay` did exist -before it is renamed with a date and time suffix (like -`global-config-overlay-2024-01-25-09:33:12`). Make sure to restore the -configuration overlay if required. + / system scheduler add name="ScriptInstallUpdate" start-time=startup interval=1d on-event=":global ScriptInstallUpdate; \$ScriptInstallUpdate;"; --- -[⬅️ Go back to main README](README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](README.md) +[▲ Go back to top](#top) diff --git a/Makefile b/Makefile index d21713c..9f21255 100644 --- a/Makefile +++ b/Makefile @@ -2,35 +2,24 @@ # template scripts -> final scripts # markdown files -> html files -CAPSMAN = $(wildcard *.capsman.rsc) -LOCAL = $(wildcard *.local.rsc) -WIFI = $(wildcard *.wifi.rsc) +TEMPLATE = $(wildcard *.template) +CAPSMAN = $(TEMPLATE:.template=.capsman) +LOCAL = $(TEMPLATE:.template=.local) MARKDOWN = $(wildcard *.md doc/*.md doc/mod/*.md) HTML = $(MARKDOWN:.md=.html) -all: $(CAPSMAN) $(LOCAL) $(WIFI) $(HTML) +all: $(CAPSMAN) $(LOCAL) $(HTML) %.html: %.md Makefile markdown $< | sed 's/href="\([-_\./[:alnum:]]*\)\.md"/href="\1.html"/g' > $@ -%.capsman.rsc: %.template.rsc Makefile - sed -e '/\/interface\/wifi\//d' -e '/\/interface\/wireless\//d' -e 's|%TEMPL%|.capsman|' \ - -e '/^# NOT \/caps-man\/ #$$/,/^# NOT \/caps-man\/ #$$/d' \ +%.local: %.template Makefile + sed -e '/\/ caps-man/d' -e 's|%PATH%|interface wireless|' -e 's|%TEMPL%|$(suffix $@)|' \ -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \ < $< > $@ -%.local.rsc: %.template.rsc Makefile - sed -e '/\/caps-man\//d' -e '/\/interface\/wifi\//d' -e 's|%TEMPL%|.local|' \ - -e '/^# NOT \/interface\/wireless\/ #$$/,/^# NOT \/interface\/wireless\/ #$$/d' \ +%.capsman: %.template Makefile + sed -e '/\/ interface wireless/d' -e 's/%PATH%/caps-man/' -e 's/%TEMPL%/$(suffix $@)/' \ -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \ < $< > $@ - -%.wifi.rsc: %.template.rsc Makefile - sed -e '/\/caps-man\//d' -e '/\/interface\/wireless\//d' -e 's|%TEMPL%|.wifi|' \ - -e '/^# NOT \/interface\/wifi\/ #$$/,/^# NOT \/interface\/wifi\/ #$$/d' \ - -e '/^# !!/,/^# !!/c # !! Do not edit this file, it is generated from template!' \ - < $< > $@ - -clean: - rm -f $(HTML) diff --git a/README.d/01-download-certs.avif b/README.d/01-download-certs.avif index d41ca05..41fa5ca 100644 Binary files a/README.d/01-download-certs.avif and b/README.d/01-download-certs.avif differ diff --git a/README.d/02-import-certs.avif b/README.d/02-import-certs.avif index bf7d577..09fe586 100644 Binary files a/README.d/02-import-certs.avif and b/README.d/02-import-certs.avif differ diff --git a/README.d/03-check-certs.avif b/README.d/03-check-certs.avif index 4717b3e..52c4df8 100644 Binary files a/README.d/03-check-certs.avif and b/README.d/03-check-certs.avif differ diff --git a/README.d/04-import-scripts.avif b/README.d/04-import-scripts.avif index 53439e4..c86a5c8 100644 Binary files a/README.d/04-import-scripts.avif and b/README.d/04-import-scripts.avif differ diff --git a/README.d/05-edit-global-config-overlay.avif b/README.d/05-edit-global-config-overlay.avif new file mode 100644 index 0000000..8a8a173 Binary files /dev/null and b/README.d/05-edit-global-config-overlay.avif differ diff --git a/README.d/05-run-and-schedule-scripts.avif b/README.d/05-run-and-schedule-scripts.avif deleted file mode 100644 index 37e1173..0000000 Binary files a/README.d/05-run-and-schedule-scripts.avif and /dev/null differ diff --git a/README.d/06-run-and-schedule-scripts.avif b/README.d/06-run-and-schedule-scripts.avif new file mode 100644 index 0000000..b59c2cb Binary files /dev/null and b/README.d/06-run-and-schedule-scripts.avif differ diff --git a/README.d/06-schedule-update.avif b/README.d/06-schedule-update.avif deleted file mode 100644 index 7c96f3a..0000000 Binary files a/README.d/06-schedule-update.avif and /dev/null differ diff --git a/README.d/07-edit-global-config-overlay.avif b/README.d/07-edit-global-config-overlay.avif deleted file mode 100644 index f87fda8..0000000 Binary files a/README.d/07-edit-global-config-overlay.avif and /dev/null differ diff --git a/README.d/07-schedule-update.avif b/README.d/07-schedule-update.avif new file mode 100644 index 0000000..cb43b48 Binary files /dev/null and b/README.d/07-schedule-update.avif differ diff --git a/README.d/08-apply-configuration.avif b/README.d/08-apply-configuration.avif deleted file mode 100644 index b66af1a..0000000 Binary files a/README.d/08-apply-configuration.avif and /dev/null differ diff --git a/README.d/08-update-scripts.avif b/README.d/08-update-scripts.avif new file mode 100644 index 0000000..434bc84 Binary files /dev/null and b/README.d/08-update-scripts.avif differ diff --git a/README.d/09-install-scripts.avif b/README.d/09-install-scripts.avif new file mode 100644 index 0000000..467b9e4 Binary files /dev/null and b/README.d/09-install-scripts.avif differ diff --git a/README.d/09-update-scripts.avif b/README.d/09-update-scripts.avif deleted file mode 100644 index f549fef..0000000 Binary files a/README.d/09-update-scripts.avif and /dev/null differ diff --git a/README.d/10-install-scripts.avif b/README.d/10-install-scripts.avif deleted file mode 100644 index 00225b1..0000000 Binary files a/README.d/10-install-scripts.avif and /dev/null differ diff --git a/README.d/10-schedule-script.avif b/README.d/10-schedule-script.avif new file mode 100644 index 0000000..945a7df Binary files /dev/null and b/README.d/10-schedule-script.avif differ diff --git a/README.d/11-schedule-script.avif b/README.d/11-schedule-script.avif deleted file mode 100644 index d6eb0f8..0000000 Binary files a/README.d/11-schedule-script.avif and /dev/null differ diff --git a/README.d/11-setup-lease-script.avif b/README.d/11-setup-lease-script.avif new file mode 100644 index 0000000..168481b Binary files /dev/null and b/README.d/11-setup-lease-script.avif differ diff --git a/README.d/12-install-custom-script.avif b/README.d/12-install-custom-script.avif new file mode 100644 index 0000000..fb53606 Binary files /dev/null and b/README.d/12-install-custom-script.avif differ diff --git a/README.d/12-setup-lease-script.avif b/README.d/12-setup-lease-script.avif deleted file mode 100644 index fb4024e..0000000 Binary files a/README.d/12-setup-lease-script.avif and /dev/null differ diff --git a/README.d/13-install-custom-script.avif b/README.d/13-install-custom-script.avif deleted file mode 100644 index 2f01c43..0000000 Binary files a/README.d/13-install-custom-script.avif and /dev/null differ diff --git a/README.d/14-remove-script.avif b/README.d/14-remove-script.avif deleted file mode 100644 index a5c7daf..0000000 Binary files a/README.d/14-remove-script.avif and /dev/null differ diff --git a/README.d/hello-world.rsc b/README.d/hello-world.rsc index 6404781..17ec575 100644 --- a/README.d/hello-world.rsc +++ b/README.d/hello-world.rsc @@ -1,3 +1,3 @@ #!rsc by RouterOS -:put ("Hello World from " . [ /system/identity/get name ] . "!"); +:put ("Hello World from " . [ / system identity get name ] . "!"); diff --git a/README.d/notification-news-and-changes.avif b/README.d/notification-news-and-changes.avif deleted file mode 100644 index d91b8a0..0000000 Binary files a/README.d/notification-news-and-changes.avif and /dev/null differ diff --git a/README.md b/README.md index fae6986..ebc406b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ RouterOS Scripts [![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) [![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) [![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) +[![required RouterOS version](https://img.shields.io/badge/RouterOS-6.49-yellow?style=flat)](https://mikrotik.com/download/changelogs/) [![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) [![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) @@ -21,33 +21,11 @@ to manage RouterOS devices or extend their functionality. Requirements ------------ -### Software (RouterOS) - Latest version of the scripts require recent RouterOS to function properly. -Make sure to install latest updates before you begin. If new functionality -or a breaking change in RouterOS `7.n` is used in my scripts I push my -change some time after `7.(n+1)` was released. At any time you should have -at least two minor and their bugfix releases to choose from. +Make sure to install latest updates before you begin. Specific scripts may require even newer RouterOS version. -> ℹ️ **Info**: The `main` branch is now RouterOS v7 only. If you are still -> running RouterOS v6 switch to `routeros-v6` branch! - -Starting with RouterOS 7.17 the -[device-mode](https://help.mikrotik.com/docs/spaces/ROS/pages/93749258/Device-mode) -has been extended to give more fine-grained control over what features are -available. You need to enable `scheduler` and `fetch` at least, specific -scripts may require additional features. - -### Hardware - -RouterOS packages increase in size with each release. This becomes a -problem for devices with 16MB storage and below, those with an ARM CPU -are specifically affected. - -Huge configuration and lots of scripts give an extra risk. **Take care!** - Initial setup ------------- @@ -75,31 +53,27 @@ download the certificates. If you intend to download the scripts from a different location (for example from github.com) install the corresponding certificate chain. - /tool/fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/ISRG-Root-X2.pem" dst-path="isrg-root-x2.pem"; + / tool fetch "https://git.eworm.de/cgit/routeros-scripts/plain/certs/ISRG-Root-X2.pem" dst-path="isrg-root-x2.pem"; ![screenshot: download certs](README.d/01-download-certs.avif) Note that the commands above do *not* verify server certificate, so if you want to be safe download with your workstations's browser and transfer the -file to your MikroTik device. +files to your MikroTik device. * [ISRG Root X2](https://letsencrypt.org/certs/isrg-root-x2.pem) -Then we import the certificate. +Then we import the certificates. - /certificate/import file-name="isrg-root-x2.pem" passphrase=""; - -Do not worry that the command is not shown - that happens because it contains -a sensitive property, the passphrase. + / certificate import file-name=isrg-root-x2.pem passphrase=""; ![screenshot: import certs](README.d/02-import-certs.avif) -For basic verification we rename the certificate and print it by -fingerprint. Make sure exactly this one certificate ("*ISRG-Root-X2*") -is shown. +For basic verification we rename the certificate and print the count. Make +sure the certificate count is **one**. - /certificate/set name="ISRG-Root-X2" [ find where common-name="ISRG Root X2" ]; - /certificate/print proplist=name,fingerprint where fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470"; + / certificate set name="ISRG-Root-X2" [ find where common-name="ISRG Root X2" ]; + / certificate print count-only where fingerprint="69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470"; ![screenshot: check certs](README.d/03-check-certs.avif) @@ -111,56 +85,49 @@ 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 owner=$Script source=([ /tool/fetch check-certificate=yes-without-crl ("https://git.eworm.de/cgit/routeros-scripts/plain/" . $Script . ".rsc") 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 . "\?h=routeros-v6") output=user as-value]->"data"); }; ![screenshot: import scripts](README.d/04-import-scripts.avif) -And finally load configuration and functions and add the scheduler. - - /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; }"; - -![screenshot: run and schedule scripts](README.d/05-run-and-schedule-scripts.avif) - -### Scheduled automatic updates - -The last step is optional: Add this scheduler **only** if you want the -scripts to be updated automatically! - - /system/scheduler/add name="ScriptInstallUpdate" start-time=startup interval=1d on-event=":global ScriptInstallUpdate; \$ScriptInstallUpdate;"; - -![screenshot: schedule update](README.d/06-schedule-update.avif) - -Editing configuration ---------------------- - The configuration needs to be tweaked for your needs. Edit -`global-config-overlay`, copy relevant configuration from -[`global-config`](global-config.rsc) (the one without `-overlay`). +`global-config-overlay`, copy configuration from +[`global-config`](global-config) (the one without `-overlay`). Save changes and exit with `Ctrl-o`. - /system/script/edit global-config-overlay source; + / system script edit global-config-overlay source; -![screenshot: edit global-config-overlay](README.d/07-edit-global-config-overlay.avif) +![screenshot: edit global-config-overlay](README.d/05-edit-global-config-overlay.avif) -Additionally creating configuration snippets is supported. The script name -of these snippets has to start with `global-config-overlay.d/` to make them -being loaded automatically. This allows to split off parts of the -configuration. +And finally load configuration and functions and add the scheduler. -To apply your changes run `global-config`, which will automatically load -the overlay as well: + / 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; }"; - /system/script/run global-config; +![screenshot: run and schedule scripts](README.d/06-run-and-schedule-scripts.avif) -![screenshot: apply configuration](README.d/08-apply-configuration.avif) +The last step is optional: Add this scheduler **only** if you want the scripts +to be updated automatically! -This last step is required when ever you make changes to your configuration. + / system scheduler add name="ScriptInstallUpdate" start-time=startup interval=1d on-event=":global ScriptInstallUpdate; \$ScriptInstallUpdate;"; -> ℹ️ **Info**: It is recommended to edit the configuration using the command -> line interface. If using Winbox on Windows OS, the line endings may be -> missing. To fix this run: -> `/system/script/set source=[ :tocrlf [ get global-config-overlay source ] ] global-config-overlay;` +![screenshot: schedule update](README.d/07-schedule-update.avif) + +### Changes for RouterOS v6 + +Let's consider RouterOS v6 being legacy. If you want to stay with RouterOS +v6 for some time add these lines to your `global-config-overlay`, if missing: + + # Use branch routeros-v6 with RouterOS v6: + :global ScriptUpdatesUrlSuffix "\?h=routeros-v6"; + +Then reload the configuration. + +### Changes for RouterOS v7 + +RouterOS v7 is the future, and default branch `main` expects it. Just drop +`$ScriptUpdatesUrlSuffix` from your `global-config-overlay` to use that. + +Then reload the configuration and continue below to update scripts. Updating scripts ---------------- @@ -170,12 +137,7 @@ everything is up-to-date it will not produce any output. $ScriptInstallUpdate; -![screenshot: update scripts](README.d/09-update-scripts.avif) - -If the update includes news or requires configuration changes a notification -is sent - in addition to terminal output and log messages. - -![news and changes notification](README.d/notification-news-and-changes.avif) +![screenshot: update scripts](README.d/08-update-scripts.avif) Adding a script --------------- @@ -185,29 +147,29 @@ a comma separated list of script names. $ScriptInstallUpdate check-certificates,check-routeros-update; -![screenshot: install scripts](README.d/10-install-scripts.avif) +![screenshot: install scripts](README.d/09-install-scripts.avif) Scheduler and events -------------------- Most scripts are designed to run regularly from [scheduler](https://wiki.mikrotik.com/wiki/Manual:System/Scheduler). We just -added `check-routeros-update`, so let's run it daily to make sure not to +added `check-routeros-update`, so let's run it every hour to make sure not to miss an update. - /system/scheduler/add name="check-routeros-update" interval=1d start-time=startup on-event="/system/script/run check-routeros-update;"; + / system scheduler add name="check-routeros-update" interval=1h on-event="/ system script run check-routeros-update;"; -![screenshot: schedule script](README.d/11-schedule-script.avif) +![screenshot: schedule script](README.d/10-schedule-script.avif) Some events can run a script. If you want your DHCP hostnames to be available in DNS use `dhcp-to-dns` with the events from dhcp server. For a regular cleanup add a scheduler entry. $ScriptInstallUpdate dhcp-to-dns,lease-script; - /ip/dhcp-server/set lease-script=lease-script [ find ]; - /system/scheduler/add name="dhcp-to-dns" interval=5m on-event="/system/script/run dhcp-to-dns;"; + / ip dhcp-server set lease-script=lease-script [ find ]; + / system scheduler add name="dhcp-to-dns" interval=5m on-event="/ system script run dhcp-to-dns;"; -![screenshot: setup lease script](README.d/12-setup-lease-script.avif) +![screenshot: setup lease script](README.d/11-setup-lease-script.avif) There's much more to explore... Have fun! @@ -231,10 +193,9 @@ Available scripts * [Comment DHCP leases with info from access list](doc/dhcp-lease-comment.md) * [Create DNS records for DHCP leases](doc/dhcp-to-dns.md) * [Automatically upgrade firmware and reboot](doc/firmware-upgrade-reboot.md) -* [Download, import and update firewall address-lists](doc/fw-addr-lists.md) * [Wait for global functions und modules](doc/global-wait.md) * [Send GPS position to server](doc/gps-track.md) -* [Use WPA network with hotspot credentials](doc/hotspot-to-wpa.md) +* [Use WPA2 network with hotspot credentials](doc/hotspot-to-wpa.md) * [Create DNS records for IPSec peers](doc/ipsec-to-dns.md) * [Update configuration on IPv6 prefix change](doc/ipv6-update.md) * [Manage IP addresses with bridge status](doc/ip-addr-bridge.md) @@ -244,17 +205,23 @@ Available scripts * [Mode button with multiple presses](doc/mode-button.md) * [Manage DNS and DoH servers from netwatch](doc/netwatch-dns.md) * [Notify on host up and down](doc/netwatch-notify.md) +* [Manage remote logging](doc/netwatch-syslog.md) * [Visualize OSPF state via LEDs](doc/ospf-to-leds.md) * [Manage system update](doc/packages-update.md) * [Run scripts on ppp connection](doc/ppp-on-up.md) +* [Rotate NTP servers](doc/rotate-ntp.md) * [Act on received SMS](doc/sms-action.md) * [Forward received SMS](doc/sms-forward.md) +* [Import SSH keys](doc/ssh-keys-import.md) * [Play Super Mario theme](doc/super-mario-theme.md) -* [Chat with your router and send commands via Telegram bot](doc/telegram-chat.md) * [Install LTE firmware upgrade](doc/unattended-lte-firmware-upgrade.md) * [Update GRE configuration with dynamic addresses](doc/update-gre-address.md) * [Update tunnelbroker configuration](doc/update-tunnelbroker.md) +[comment]: # (TODO: currently undocumented) +[comment]: # (* learn-mac-based-vlan) +[comment]: # (* manage-umts) + Available modules ----------------- @@ -262,12 +229,9 @@ Available modules * [Manage VLANs on bridge ports](doc/mod/bridge-port-vlan.md) * [Inspect variables](doc/mod/inspectvar.md) * [IP address calculation](doc/mod/ipcalc.md) -* [Send notifications via e-mail](doc/mod/notification-email.md) * [Send notifications via Matrix](doc/mod/notification-matrix.md) -* [Send notifications via Ntfy](doc/mod/notification-ntfy.md) * [Send notifications via Telegram](doc/mod/notification-telegram.md) * [Download script and run it once](doc/mod/scriptrunonce.md) -* [Import ssh keys for public key authentication](doc/mod/ssh-keys-import.md) Installing custom scripts & modules ----------------------------------- @@ -278,9 +242,12 @@ 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 "base-url=https://git.eworm.de/cgit/routeros-scripts-custom/plain/"; + $ScriptInstallUpdate hello-world.rsc "base-url=https://git.eworm.de/cgit/routeros-scripts/plain/README.d/" -![screenshot: install custom script](README.d/13-install-custom-script.avif) +![screenshot: install custom script](README.d/12-install-custom-script.avif) + +(Yes, the example url still belongs to the repository for easy +handling - but the url can be what ever you use.) For a script to be considered valid it has to begin with a *magic token*. Have a look at [any script](README.d/hello-world.rsc) and copy the first line @@ -289,38 +256,6 @@ without modification. Starting a script's name with `mod/` makes it a module and it is run automatically by `global-functions`. -### Linked custom scripts & modules - -> ⚠️ **Warning**: These links are being provided for your convenience only; -> they do not constitute an endorsement or an approval by me. I bear no -> responsibility for the accuracy, legality or content of the external site -> or for that of subsequent links. Contact the external site for answers to -> questions regarding its content. - -* [Hello World](https://git.eworm.de/cgit/routeros-scripts-custom/about/doc/hello-world.md) - (This is a demo script to show how the linking to external documentation - will be done.) - -> ℹ️ **Info**: You have your own set of scripts and/or modules and want these -> to be listed here? There should be a general info page that links here, -> and documentation for each script. You can start by cloning my -> [Custom RouterOS-Scripts](https://git.eworm.de/cgit/routeros-scripts-custom/) -> (or fork on [GitHub](https://github.com/eworm-de/routeros-scripts-custom) -> or [GitLab](https://gitlab.com/eworm-de/routeros-scripts-custom)) and make -> your changes. Then please [get in contact](#patches-issues-and-whishlist)... - -Removing a script ------------------ - -There is no specific function for script removal. Just remove it from -configuration... - - /system/script/remove to-be-removed; - -![screenshot: remove script](README.d/14-remove-script.avif) - -Possibly a scheduler and other configuration has to be removed as well. - Contact ------- @@ -334,14 +269,12 @@ support! Contribute ---------- -Thanks a lot for [past contributions](CONTRIBUTIONS.md)! ❤️ +Thanks a lot for [past contributions](CONTRIBUTIONS.md)! ### Patches, issues and whishlist Feel free to contact me via e-mail or open an -[issue](https://github.com/eworm-de/routeros-scripts/issues) or -[pull request](https://github.com/eworm-de/routeros-scripts/pulls) -at github. +[issue at github](https://github.com/eworm-de/routeros-scripts/issues). ### Donate @@ -380,4 +313,4 @@ Mirror: [GitLab.com](https://gitlab.com/eworm-de/routeros-scripts#routeros-scripts) --- -[⬆️ Go back to top](#top) +[▲ Go back to top](#top) diff --git a/accesslist-duplicates.capsman b/accesslist-duplicates.capsman new file mode 100644 index 0000000..74cf3b3 --- /dev/null +++ b/accesslist-duplicates.capsman @@ -0,0 +1,42 @@ +#!rsc by RouterOS +# RouterOS script: accesslist-duplicates.capsman +# Copyright (c) 2018-2022 Christian Hesse +# 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 [ :toarray "" ]; +:local Shown [ :toarray "" ]; + +: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.capsman.rsc b/accesslist-duplicates.capsman.rsc deleted file mode 100644 index 27546c8..0000000 --- a/accesslist-duplicates.capsman.rsc +++ /dev/null @@ -1,37 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: accesslist-duplicates.capsman -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# print duplicate antries in wireless access list -# https://rsc.eworm.de/doc/accesslist-duplicates.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :local Seen ({}); - - :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 ]; - :if ($Seen->$Mac = 1) do={ - /caps-man/access-list/print where mac-address=$Mac; - :local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ]; - - :if ([ :typeof $Remove ] = "num") do={ - :put ("Removing numeric id " . $Remove . "...\n"); - /caps-man/access-list/remove $Remove; - } - } - :set ($Seen->$Mac) 1; - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/accesslist-duplicates.local b/accesslist-duplicates.local new file mode 100644 index 0000000..0aa946c --- /dev/null +++ b/accesslist-duplicates.local @@ -0,0 +1,42 @@ +#!rsc by RouterOS +# RouterOS script: accesslist-duplicates.local +# Copyright (c) 2018-2022 Christian Hesse +# 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 [ :toarray "" ]; +:local Shown [ :toarray "" ]; + +: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.local.rsc b/accesslist-duplicates.local.rsc deleted file mode 100644 index 589815d..0000000 --- a/accesslist-duplicates.local.rsc +++ /dev/null @@ -1,37 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: accesslist-duplicates.local -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# print duplicate antries in wireless access list -# https://rsc.eworm.de/doc/accesslist-duplicates.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :local Seen ({}); - - :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 ]; - :if ($Seen->$Mac = 1) do={ - /interface/wireless/access-list/print where mac-address=$Mac; - :local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ]; - - :if ([ :typeof $Remove ] = "num") do={ - :put ("Removing numeric id " . $Remove . "...\n"); - /interface/wireless/access-list/remove $Remove; - } - } - :set ($Seen->$Mac) 1; - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/accesslist-duplicates.template b/accesslist-duplicates.template new file mode 100644 index 0000000..f1862bb --- /dev/null +++ b/accesslist-duplicates.template @@ -0,0 +1,43 @@ +#!rsc by RouterOS +# RouterOS script: accesslist-duplicates%TEMPL% +# Copyright (c) 2018-2022 Christian Hesse +# 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 +# +# !! This is just a template! Replace '%PATH%' with 'caps-man' +# !! or 'interface wireless'! + +:local 0 "accesslist-duplicates%TEMPL%"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Read; + +:local Seen [ :toarray "" ]; +:local Shown [ :toarray "" ]; + +:foreach AccList in=[ / %PATH% access-list find where mac-address!="00:00:00:00:00:00" ] do={ + :local Mac [ / %PATH% 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={ + / %PATH% 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"); + / %PATH% access-list remove $Remove; + } + } + } + } + :set Seen ($Seen, $Mac); +} diff --git a/accesslist-duplicates.template.rsc b/accesslist-duplicates.template.rsc deleted file mode 100644 index ccbca3d..0000000 --- a/accesslist-duplicates.template.rsc +++ /dev/null @@ -1,46 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: accesslist-duplicates%TEMPL% -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# print duplicate antries in wireless access list -# https://rsc.eworm.de/doc/accesslist-duplicates.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :local Seen ({}); - - :foreach AccList in=[ /caps-man/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ - :foreach AccList in=[ /interface/wifi/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ - :foreach AccList in=[ /interface/wireless/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ - :local Mac [ /caps-man/access-list/get $AccList mac-address ]; - :local Mac [ /interface/wifi/access-list/get $AccList mac-address ]; - :local Mac [ /interface/wireless/access-list/get $AccList mac-address ]; - :if ($Seen->$Mac = 1) do={ - /caps-man/access-list/print where mac-address=$Mac; - /interface/wifi/access-list/print where mac-address=$Mac; - /interface/wireless/access-list/print where mac-address=$Mac; - :local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ]; - - :if ([ :typeof $Remove ] = "num") do={ - :put ("Removing numeric id " . $Remove . "...\n"); - /caps-man/access-list/remove $Remove; - /interface/wifi/access-list/remove $Remove; - /interface/wireless/access-list/remove $Remove; - } - } - :set ($Seen->$Mac) 1; - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/accesslist-duplicates.wifi.rsc b/accesslist-duplicates.wifi.rsc deleted file mode 100644 index 527ebb4..0000000 --- a/accesslist-duplicates.wifi.rsc +++ /dev/null @@ -1,37 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: accesslist-duplicates.wifi -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# print duplicate antries in wireless access list -# https://rsc.eworm.de/doc/accesslist-duplicates.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :local Seen ({}); - - :foreach AccList in=[ /interface/wifi/access-list/find where mac-address!="00:00:00:00:00:00" ] do={ - :local Mac [ /interface/wifi/access-list/get $AccList mac-address ]; - :if ($Seen->$Mac = 1) do={ - /interface/wifi/access-list/print where mac-address=$Mac; - :local Remove [ :tonum [ /terminal/ask prompt="\nNumeric id to remove, any key to skip!" ] ]; - - :if ([ :typeof $Remove ] = "num") do={ - :put ("Removing numeric id " . $Remove . "...\n"); - /interface/wifi/access-list/remove $Remove; - } - } - :set ($Seen->$Mac) 1; - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/backup-cloud b/backup-cloud new file mode 100644 index 0000000..d1d9f14 --- /dev/null +++ b/backup-cloud @@ -0,0 +1,58 @@ +#!rsc by RouterOS +# RouterOS script: backup-cloud +# Copyright (c) 2013-2022 Christian Hesse +# 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 "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-cloud.rsc b/backup-cloud.rsc deleted file mode 100644 index c4e23b2..0000000 --- a/backup-cloud.rsc +++ /dev/null @@ -1,104 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: backup-cloud -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: backup-script, order=40 -# requires RouterOS, version=7.15 -# -# upload backup to MikroTik cloud -# https://rsc.eworm.de/doc/backup-cloud.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global BackupRandomDelay; - :global Identity; - :global PackagesUpdateBackupFailure; - - :global DeviceInfo; - :global FormatLine; - :global HumanReadableNum; - :global LogPrint; - :global MkDir; - :global RandomDelay; - :global RmDir; - :global ScriptFromTerminal; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global WaitForFile; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={ - $LogPrint warning $ScriptName ("Running from backup partition, refusing to act."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - $WaitFullyConnected; - - :if ([ $ScriptFromTerminal $ScriptName ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; - } - - :if ([ $MkDir ("tmpfs/backup-cloud") ] = false) do={ - $LogPrint error $ScriptName ("Failed creating directory!"); - :set ExitOK true; - :error false; - } - - :local I 5; - :do { - :execute { - :global BackupPassword; - - :local Backup ([ /system/backup/cloud/find ]->0); - :if ([ :typeof $Backup ] = "id") do={ - /system/backup/cloud/upload-file action=create-and-upload \ - password=$BackupPassword replace=$Backup; - } else={ - /system/backup/cloud/upload-file action=create-and-upload \ - password=$BackupPassword; - } - /file/add name="tmpfs/backup-cloud/done"; - } as-string; - :set I ($I - 1); - } while=([ $WaitForFile "tmpfs/backup-cloud/done" 200ms ] = false && $I > 0); - - :if ([ $WaitForFile "tmpfs/backup-cloud/done" ] = true) do={ - :if ($I < 4) do={ - :log warning ($ScriptName . ": Retry successful, please discard previous connection errors."); - } - - :local Cloud [ /system/backup/cloud/get ([ find ]->0) ]; - - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "floppy-disk,cloud" ] . "Cloud backup"); \ - message=("Uploaded backup for " . $Identity . " to cloud.\n\n" . \ - [ $DeviceInfo ] . "\n\n" . \ - [ $FormatLine "Name" ($Cloud->"name") ] . "\n" . \ - [ $FormatLine "Size" ([ $HumanReadableNum ($Cloud->"size") 1024 ] . "B") ] . "\n" . \ - [ $FormatLine "Download key" ($Cloud->"secret-download-key") ]); silent=true }); - } else={ - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Cloud backup failed"); \ - message=("Failed uploading backup for " . $Identity . " to cloud!\n\n" . [ $DeviceInfo ]) }); - $LogPrint error $ScriptName ("Failed uploading backup for " . $Identity . " to cloud!"); - :set PackagesUpdateBackupFailure true; - } - $RmDir "tmpfs/backup-cloud"; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/backup-email b/backup-email new file mode 100644 index 0000000..bda9215 --- /dev/null +++ b/backup-email @@ -0,0 +1,80 @@ +#!rsc by RouterOS +# RouterOS script: backup-email +# Copyright (c) 2013-2022 Christian Hesse +# 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 Domain; +:global Identity; + +:global CharacterReplace; +:global DeviceInfo; +:global LogPrintExit2; +:global MkDir; +:global RandomDelay; +:global ScriptFromTerminal; +:global SendEMail2; +: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; +} + +:if ([ $MkDir $0 ] = false) do={ + $LogPrintExit2 error $0 ("Failed creating directory!") true; +} + +# filename based on identity +:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; +:local FilePath ($0 . "/" . $FileName); +:local BackupFile "none"; +:local ConfigFile "none"; +:local Attach [ :toarray "" ]; + +# 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 file=$FilePath; + $WaitForFile ($FilePath . ".rsc"); + :set ConfigFile ($FileName . ".rsc"); + :set Attach ($Attach, ($FilePath . ".rsc")); +} + +# 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" . \ + "Config file: " . $ConfigFile); \ + attach=$Attach; remove-attach=true }); diff --git a/backup-email.rsc b/backup-email.rsc deleted file mode 100644 index d097301..0000000 --- a/backup-email.rsc +++ /dev/null @@ -1,140 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: backup-email -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: backup-script, order=20 -# requires RouterOS, version=7.15 -# -# create and email backup and config file -# https://rsc.eworm.de/doc/backup-email.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global BackupPassword; - :global BackupRandomDelay; - :global BackupSendBinary; - :global BackupSendExport; - :global BackupSendGlobalConfig; - :global Domain; - :global Identity; - :global PackagesUpdateBackupFailure; - - :global CleanName; - :global DeviceInfo; - :global FormatLine; - :global LogPrint; - :global MkDir; - :global RandomDelay; - :global ScriptFromTerminal; - :global ScriptLock; - :global SendEMail2; - :global SymbolForNotification; - :global WaitForFile; - :global WaitFullyConnected; - - :if ([ :typeof $SendEMail2 ] = "nothing") do={ - $LogPrint error $ScriptName ("The module for sending notifications via e-mail is not installed."); - :set ExitOK true; - :error false; - } - - :if ($BackupSendBinary != true && \ - $BackupSendExport != true) do={ - $LogPrint error $ScriptName ("Configured to send neither backup nor config export."); - :set ExitOK true; - :error false; - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={ - $LogPrint warning $ScriptName ("Running from backup partition, refusing to act."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - $WaitFullyConnected; - - :if ([ $ScriptFromTerminal $ScriptName ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; - } - - # filename based on identity - :local DirName ("tmpfs/" . $ScriptName); - :local FileName [ $CleanName ($Identity . "." . $Domain) ]; - :local FilePath ($DirName . "/" . $FileName); - :local BackupFile "none"; - :local ExportFile "none"; - :local ConfigFile "none"; - :local Attach ({}); - - :if ([ $MkDir $DirName ] = false) do={ - $LogPrint error $ScriptName ("Failed creating directory!"); - :set ExitOK true; - :error false; - } - - # 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={ - # Do *NOT* use '/file/add ...' here, as it is limited to 4095 bytes! - :execute script={ :put [ /system/script/get global-config-overlay source ]; } \ - file=($FilePath . ".conf\00"); - $WaitForFile ($FilePath . ".conf"); - :set ConfigFile ($FileName . ".conf"); - :set Attach ($Attach, ($FilePath . ".conf")); - } - - # send email with status and files - $SendEMail2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "floppy-disk,incoming-envelope" ] . \ - "Backup & Config"); \ - message=("See attached files for backup and config export for " . \ - $Identity . ".\n\n" . \ - [ $DeviceInfo ] . "\n\n" . \ - [ $FormatLine "Backup file" $BackupFile ] . "\n" . \ - [ $FormatLine "Export file" $ExportFile ] . "\n" . \ - [ $FormatLine "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={ - $LogPrint warning $ScriptName ("Files are still available, sending e-mail failed."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - :delay 1s; - :set I ($I + 1); - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/backup-partition b/backup-partition new file mode 100644 index 0000000..c72c7f9 --- /dev/null +++ b/backup-partition @@ -0,0 +1,36 @@ +#!rsc by RouterOS +# RouterOS script: backup-partition +# Copyright (c) 2022 Christian Hesse +# 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-partition.rsc b/backup-partition.rsc deleted file mode 100644 index 1f0cf2e..0000000 --- a/backup-partition.rsc +++ /dev/null @@ -1,126 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: backup-partition -# Copyright (c) 2022-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: backup-script, order=70 -# requires RouterOS, version=7.15 -# requires device-mode, scheduler -# -# save configuration to fallback partition -# https://rsc.eworm.de/doc/backup-partition.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global BackupPartitionCopyBeforeFeatureUpdate; - :global PackagesUpdateBackupFailure; - - :global LogPrint; - :global ScriptFromTerminal; - :global ScriptLock; - :global VersionToNum; - - :local CopyTo do={ - :local ScriptName [ :tostr $1 ]; - :local FallbackTo [ :toid $2 ]; - :local FallbackToName [ :tostr $3 ]; - - :global LogPrint; - - :do { - /partitions/copy-to $FallbackTo; - $LogPrint info $ScriptName ("Copied RouterOS to partition '" . $FallbackToName . "'."); - :return true; - } on-error={ - $LogPrint error $ScriptName ("Failed copying RouterOS to partition '" . $FallbackToName . "'!"); - :return false; - } - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={ - $LogPrint warning $ScriptName ("Running from backup partition, refusing to act."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :if ([ :len [ /partitions/find ] ] < 2) do={ - $LogPrint error $ScriptName ("Device does not have a fallback partition."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :local ActiveRunning [ /partitions/find where active running ]; - - :if ([ :len $ActiveRunning ] < 1) do={ - $LogPrint error $ScriptName ("Device is not running from active partition."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :local FallbackToName [ /partitions/get $ActiveRunning fallback-to ]; - :local FallbackTo [ /partition/find where name=$FallbackToName !active ]; - - :if ([ :len $FallbackTo ] < 1) do={ - $LogPrint error $ScriptName ("There is no inactive partition named '" . $FallbackToName . "'."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :if ([ /partitions/get $ActiveRunning version ] != [ /partitions/get $FallbackTo version]) do={ - :if ([ $ScriptFromTerminal $ScriptName ] = true) do={ - :put ("The partitions have different RouterOS versions. Copy over to '" . $FallbackToName . "'? [y/N]"); - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - :if ([ $CopyTo $ScriptName $FallbackTo $FallbackToName ] = false) do={ - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - } - } else={ - :local Update [ /system/package/update/get ]; - :local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; - :local NumLatest [ $VersionToNum ($Update->"latest-version") ]; - :local BitMask [ $VersionToNum "255.255zero0" ]; - :if ($BackupPartitionCopyBeforeFeatureUpdate = true && $NumLatest > 0 && \ - ($NumInstalled & $BitMask) != ($NumLatest & $BitMask)) do={ - :if ([ $CopyTo $ScriptName $FallbackTo $FallbackToName ] = false) do={ - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - } - } - } - - :do { - /system/scheduler/add start-time=startup name="running-from-backup-partition" \ - on-event=(":log warning (\"Running from partition '\" . " . \ - "[ /partitions/get [ find where running ] name ] . \"'!\")"); - /partitions/save-config-to $FallbackTo; - /system/scheduler/remove "running-from-backup-partition"; - $LogPrint info $ScriptName ("Saved configuration to partition '" . $FallbackToName . "'."); - } on-error={ - /system/scheduler/remove [ find where name="running-from-backup-partition" ]; - $LogPrint error $ScriptName ("Failed saving configuration to partition '" . $FallbackToName . "'!"); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/backup-upload b/backup-upload new file mode 100644 index 0000000..6aba3e7 --- /dev/null +++ b/backup-upload @@ -0,0 +1,106 @@ +#!rsc by RouterOS +# RouterOS script: backup-upload +# Copyright (c) 2013-2022 Christian Hesse +# 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 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; +} + +:if ([ $MkDir $0 ] = false) do={ + $LogPrintExit2 error $0 ("Failed creating directory!") true; +} + +# filename based on identity +:local FileName [ $CharacterReplace ($Identity . "." . $Domain) "." "_" ]; +:local FilePath ($0 . "/" . $FileName); +:local BackupFile "none"; +:local ConfigFile "none"; +:local Failed 0; + +# 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 file=$FilePath; + $WaitForFile ($FilePath . ".rsc"); + + :do { + / tool fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".rsc") \ + user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".rsc"); + :set ConfigFile ($FileName . ".rsc"); + } on-error={ + $LogPrintExit2 error $0 ("Uploading configuration export failed!") false; + :set ConfigFile "failed"; + :set Failed 1; + } + + / file remove ($FilePath . ".rsc"); +} + +$SendNotification2 ({ origin=$0; \ + subject=[ $IfThenElse ($Failed > 0) \ + ([ $SymbolForNotification "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" . \ + "Config file: " . $ConfigFile); silent=true }); + +:if ($Failed = 1) do={ + :error "An error occured!"; +} diff --git a/backup-upload.rsc b/backup-upload.rsc deleted file mode 100644 index 14c3914..0000000 --- a/backup-upload.rsc +++ /dev/null @@ -1,178 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: backup-upload -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: backup-script, order=50 -# requires RouterOS, version=7.15 -# requires device-mode, fetch -# -# create and upload backup and config file -# https://rsc.eworm.de/doc/backup-upload.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global BackupPassword; - :global BackupRandomDelay; - :global BackupSendBinary; - :global BackupSendExport; - :global BackupSendGlobalConfig; - :global BackupUploadPass; - :global BackupUploadUrl; - :global BackupUploadUser; - :global Domain; - :global Identity; - :global PackagesUpdateBackupFailure; - - :global CleanName; - :global DeviceInfo; - :global IfThenElse; - :global LogPrint; - :global MkDir; - :global RandomDelay; - :global RmDir; - :global RmFile; - :global ScriptFromTerminal; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global WaitForFile; - :global WaitFullyConnected; - - :if ($BackupSendBinary != true && \ - $BackupSendExport != true) do={ - $LogPrint error $ScriptName ("Configured to send neither backup nor config export."); - :set ExitOK true; - :error false; - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={ - $LogPrint warning $ScriptName ("Running from backup partition, refusing to act."); - :set PackagesUpdateBackupFailure true; - :set ExitOK true; - :error false; - } - - $WaitFullyConnected; - - :if ([ $ScriptFromTerminal $ScriptName ] = false && $BackupRandomDelay > 0) do={ - $RandomDelay $BackupRandomDelay; - } - - # filename based on identity - :local DirName ("tmpfs/" . $ScriptName); - :local FileName [ $CleanName ($Identity . "." . $Domain) ]; - :local FilePath ($DirName . "/" . $FileName); - :local BackupFile "none"; - :local ExportFile "none"; - :local ConfigFile "none"; - :local Failed 0; - - :if ([ $MkDir $DirName ] = false) do={ - $LogPrint error $ScriptName ("Failed creating directory!"); - :set ExitOK true; - :error false; - } - - # 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 [ /file/get ($FilePath . ".backup") ]; - :set ($BackupFile->"name") ($FileName . ".backup"); - } on-error={ - $LogPrint error $ScriptName ("Uploading backup file failed!"); - :set BackupFile "failed"; - :set Failed 1; - } - - $RmFile ($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 [ /file/get ($FilePath . ".rsc") ]; - :set ($ExportFile->"name") ($FileName . ".rsc"); - } on-error={ - $LogPrint error $ScriptName ("Uploading configuration export failed!"); - :set ExportFile "failed"; - :set Failed 1; - } - - $RmFile ($FilePath . ".rsc"); - } - - # global-config-overlay - :if ($BackupSendGlobalConfig = true) do={ - # Do *NOT* use '/file/add ...' here, as it is limited to 4095 bytes! - :execute script={ :put [ /system/script/get global-config-overlay source ]; } \ - file=($FilePath . ".conf\00"); - $WaitForFile ($FilePath . ".conf"); - - :do { - /tool/fetch upload=yes url=($BackupUploadUrl . "/" . $FileName . ".conf") \ - user=$BackupUploadUser password=$BackupUploadPass src-path=($FilePath . ".conf"); - :set ConfigFile [ /file/get ($FilePath . ".conf") ]; - :set ($ConfigFile->"name") ($FileName . ".conf"); - } on-error={ - $LogPrint error $ScriptName ("Uploading global-config-overlay failed!"); - :set ConfigFile "failed"; - :set Failed 1; - } - - $RmFile ($FilePath . ".conf"); - } - - :local FileInfo do={ - :local Name $1; - :local File $2; - - :global FormatLine; - :global HumanReadableNum; - :global IfThenElse; - - :return \ - [ $IfThenElse ([ :typeof $File ] = "array") \ - ($Name . ":\n" . [ $FormatLine " name" ($File->"name") ] . "\n" . \ - [ $FormatLine " size" ([ $HumanReadableNum ($File->"size") 1024 ] . "B") ]) \ - [ $FormatLine $Name $File ] ]; - } - - $SendNotification2 ({ origin=$ScriptName; \ - subject=[ $IfThenElse ($Failed > 0) \ - ([ $SymbolForNotification "floppy-disk,warning-sign" ] . "Backup & Config upload with failure") \ - ([ $SymbolForNotification "floppy-disk,arrow-up" ] . "Backup & Config upload") ]; \ - message=("Backup and config export upload for " . $Identity . ".\n\n" . \ - [ $DeviceInfo ] . "\n\n" . \ - [ $FileInfo "Backup file" $BackupFile ] . "\n" . \ - [ $FileInfo "Export file" $ExportFile ] . "\n" . \ - [ $FileInfo "Config file" $ConfigFile ]); silent=true }); - - :if ($Failed = 1) do={ - :set PackagesUpdateBackupFailure true; - } - $RmDir $DirName; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/capsman-download-packages b/capsman-download-packages new file mode 100644 index 0000000..0eae744 --- /dev/null +++ b/capsman-download-packages @@ -0,0 +1,89 @@ +#!rsc by RouterOS +# RouterOS script: capsman-download-packages +# Copyright (c) 2018-2022 Christian Hesse +# Michael Gisbers +# 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 ($File->"package-name" = "wireless@") do={ + :set ($File->"package-name") "wireless"; + } + :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-download-packages.capsman.rsc b/capsman-download-packages.capsman.rsc deleted file mode 100644 index 25c43f5..0000000 --- a/capsman-download-packages.capsman.rsc +++ /dev/null @@ -1,92 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-download-packages.capsman -# Copyright (c) 2018-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# download and cleanup packages for CAP installation from CAPsMAN -# https://rsc.eworm.de/doc/capsman-download-packages.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CleanFilePath; - :global DownloadPackage; - :global LogPrint; - :global MkDir; - :global RmFile; - :global ScriptLock; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $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={ - $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages."); - :set ExitOK true; - :error false; - } - - :if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ - :if ([ $MkDir $PackagePath ] = false) do={ - $LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \ - $PackagePath . ") failed!"); - :set ExitOK true; - :error false; - } - $LogPrint info $ScriptName ("Created directory at CAPsMAN package path (" . $PackagePath . \ - "). Please place your packages!"); - } - - :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; - $RmFile ($File->"name"); - } - } - - :if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ - $LogPrint info $ScriptName ("No packages available, downloading default set."); - :foreach Arch in={ "arm"; "mipsbe" } do={ - :foreach Package in={ "routeros"; "wireless" } do={ - :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={ - :set Updated true; - } - } - } - } - - :if ($Updated = true) do={ - :local Scripts [ /system/script/find where source~"\n# provides: capsman-rolling-upgrade.capsman\r?\n" ]; - :if ([ :len $Scripts ] > 0) do={ - :foreach Script in=$Scripts do={ - /system/script/run $Script; - } - } else={ - /caps-man/remote-cap/upgrade [ find where version!=$InstalledVersion ]; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/capsman-download-packages.template.rsc b/capsman-download-packages.template.rsc deleted file mode 100644 index b269838..0000000 --- a/capsman-download-packages.template.rsc +++ /dev/null @@ -1,103 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-download-packages%TEMPL% -# Copyright (c) 2018-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# download and cleanup packages for CAP installation from CAPsMAN -# https://rsc.eworm.de/doc/capsman-download-packages.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CleanFilePath; - :global DownloadPackage; - :global LogPrint; - :global MkDir; - :global RmFile; - :global ScriptLock; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - :local PackagePath [ $CleanFilePath [ /caps-man/manager/get package-path ] ]; - :local PackagePath [ $CleanFilePath [ /interface/wifi/capsman/get package-path ] ]; - :local InstalledVersion [ /system/package/update/get installed-version ]; - :local Updated false; - - :if ([ :len $PackagePath ] = 0) do={ - $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages."); - :set ExitOK true; - :error false; - } - - :if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ - :if ([ $MkDir $PackagePath ] = false) do={ - $LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \ - $PackagePath . ") failed!"); - :set ExitOK true; - :error false; - } - $LogPrint info $ScriptName ("Created directory at CAPsMAN package path (" . $PackagePath . \ - "). Please place your packages!"); - } - - :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; - $RmFile ($File->"name"); - } - } - - :if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ - $LogPrint info $ScriptName ("No packages available, downloading default set."); -# NOT /interface/wifi/ # - :foreach Arch in={ "arm"; "mipsbe" } do={ - :foreach Package in={ "routeros"; "wireless" } do={ -# NOT /interface/wifi/ # -# NOT /caps-man/ # - :foreach Arch in={ "arm"; "arm64" } do={ - :local Packages { "arm"={ "routeros"; "wifi-qcom"; "wifi-qcom-ac" }; - "arm64"={ "routeros"; "wifi-qcom" } }; - :foreach Package in=($Packages->$Arch) do={ -# NOT /caps-man/ # - :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={ - :set Updated true; - } - } - } - } - - :if ($Updated = true) do={ - :local Scripts [ /system/script/find where source~"\n# provides: capsman-rolling-upgrade%TEMPL%\r?\n" ]; - :if ([ :len $Scripts ] > 0) do={ - :foreach Script in=$Scripts do={ - /system/script/run $Script; - } - } else={ - /caps-man/remote-cap/upgrade [ find where version!=$InstalledVersion ]; - /interface/wifi/capsman/remote-cap/upgrade [ find where version!=$InstalledVersion ]; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/capsman-download-packages.wifi.rsc b/capsman-download-packages.wifi.rsc deleted file mode 100644 index 901bb0a..0000000 --- a/capsman-download-packages.wifi.rsc +++ /dev/null @@ -1,94 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-download-packages.wifi -# Copyright (c) 2018-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# download and cleanup packages for CAP installation from CAPsMAN -# https://rsc.eworm.de/doc/capsman-download-packages.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CleanFilePath; - :global DownloadPackage; - :global LogPrint; - :global MkDir; - :global RmFile; - :global ScriptLock; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - :local PackagePath [ $CleanFilePath [ /interface/wifi/capsman/get package-path ] ]; - :local InstalledVersion [ /system/package/update/get installed-version ]; - :local Updated false; - - :if ([ :len $PackagePath ] = 0) do={ - $LogPrint warning $ScriptName ("The CAPsMAN package path is not defined, can not download packages."); - :set ExitOK true; - :error false; - } - - :if ([ :len [ /file/find where name=$PackagePath type="directory" ] ] = 0) do={ - :if ([ $MkDir $PackagePath ] = false) do={ - $LogPrint warning $ScriptName ("Creating directory at CAPsMAN package path (" . \ - $PackagePath . ") failed!"); - :set ExitOK true; - :error false; - } - $LogPrint info $ScriptName ("Created directory at CAPsMAN package path (" . $PackagePath . \ - "). Please place your packages!"); - } - - :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; - $RmFile ($File->"name"); - } - } - - :if ([ :len [ /file/find where type=package name~("^" . $PackagePath) ] ] = 0) do={ - $LogPrint info $ScriptName ("No packages available, downloading default set."); - :foreach Arch in={ "arm"; "arm64" } do={ - :local Packages { "arm"={ "routeros"; "wifi-qcom"; "wifi-qcom-ac" }; - "arm64"={ "routeros"; "wifi-qcom" } }; - :foreach Package in=($Packages->$Arch) do={ - :if ([ $DownloadPackage $Package $InstalledVersion $Arch $PackagePath ] = true) do={ - :set Updated true; - } - } - } - } - - :if ($Updated = true) do={ - :local Scripts [ /system/script/find where source~"\n# provides: capsman-rolling-upgrade.wifi\r?\n" ]; - :if ([ :len $Scripts ] > 0) do={ - :foreach Script in=$Scripts do={ - /system/script/run $Script; - } - } else={ - /interface/wifi/capsman/remote-cap/upgrade [ find where version!=$InstalledVersion ]; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/capsman-rolling-upgrade b/capsman-rolling-upgrade new file mode 100644 index 0000000..2893915 --- /dev/null +++ b/capsman-rolling-upgrade @@ -0,0 +1,36 @@ +#!rsc by RouterOS +# RouterOS script: capsman-rolling-upgrade +# Copyright (c) 2018-2022 Christian Hesse +# Michael Gisbers +# 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/capsman-rolling-upgrade.capsman.rsc b/capsman-rolling-upgrade.capsman.rsc deleted file mode 100644 index 791b3db..0000000 --- a/capsman-rolling-upgrade.capsman.rsc +++ /dev/null @@ -1,50 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-rolling-upgrade.capsman -# Copyright (c) 2018-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# provides: capsman-rolling-upgrade.capsman -# requires RouterOS, version=7.15 -# -# upgrade CAPs one after another -# https://rsc.eworm.de/doc/capsman-rolling-upgrade.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :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={ - $LogPrint info $ScriptName ("Starting upgrade for " . $RemoteCapVal->"name" . \ - " (" . $RemoteCapVal->"identity" . ")..."); - /caps-man/remote-cap/upgrade $RemoteCap; - } else={ - $LogPrint warning $ScriptName ("Remote CAP vanished, skipping upgrade."); - } - :delay ($Delay . "s"); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/capsman-rolling-upgrade.template.rsc b/capsman-rolling-upgrade.template.rsc deleted file mode 100644 index 0b1cc2b..0000000 --- a/capsman-rolling-upgrade.template.rsc +++ /dev/null @@ -1,58 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-rolling-upgrade%TEMPL% -# Copyright (c) 2018-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# provides: capsman-rolling-upgrade%TEMPL% -# requires RouterOS, version=7.15 -# -# upgrade CAPs one after another -# https://rsc.eworm.de/doc/capsman-rolling-upgrade.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :local InstalledVersion [ /system/package/update/get installed-version ]; - - :local RemoteCapCount [ :len [ /caps-man/remote-cap/find ] ]; - :local RemoteCapCount [ :len [ /interface/wifi/capsman/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={ - :foreach RemoteCap in=[ /interface/wifi/capsman/remote-cap/find where version!=$InstalledVersion ] do={ - :local RemoteCapVal [ /caps-man/remote-cap/get $RemoteCap ]; - :local RemoteCapVal [ /interface/wifi/capsman/remote-cap/get $RemoteCap ]; - :if ([ :len $RemoteCapVal ] > 1) do={ -# NOT /caps-man/ # - :set ($RemoteCapVal->"name") ($RemoteCapVal->"common-name"); -# NOT /caps-man/ # - $LogPrint info $ScriptName ("Starting upgrade for " . $RemoteCapVal->"name" . \ - " (" . $RemoteCapVal->"identity" . ")..."); - /caps-man/remote-cap/upgrade $RemoteCap; - /interface/wifi/capsman/remote-cap/upgrade $RemoteCap; - } else={ - $LogPrint warning $ScriptName ("Remote CAP vanished, skipping upgrade."); - } - :delay ($Delay . "s"); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/capsman-rolling-upgrade.wifi.rsc b/capsman-rolling-upgrade.wifi.rsc deleted file mode 100644 index 4afdee2..0000000 --- a/capsman-rolling-upgrade.wifi.rsc +++ /dev/null @@ -1,51 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: capsman-rolling-upgrade.wifi -# Copyright (c) 2018-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# provides: capsman-rolling-upgrade.wifi -# requires RouterOS, version=7.15 -# -# upgrade CAPs one after another -# https://rsc.eworm.de/doc/capsman-rolling-upgrade.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :local InstalledVersion [ /system/package/update/get installed-version ]; - - :local RemoteCapCount [ :len [ /interface/wifi/capsman/remote-cap/find ] ]; - :if ($RemoteCapCount > 0) do={ - :local Delay (600 / $RemoteCapCount); - :if ($Delay > 120) do={ :set Delay 120; } - :foreach RemoteCap in=[ /interface/wifi/capsman/remote-cap/find where version!=$InstalledVersion ] do={ - :local RemoteCapVal [ /interface/wifi/capsman/remote-cap/get $RemoteCap ]; - :if ([ :len $RemoteCapVal ] > 1) do={ - :set ($RemoteCapVal->"name") ($RemoteCapVal->"common-name"); - $LogPrint info $ScriptName ("Starting upgrade for " . $RemoteCapVal->"name" . \ - " (" . $RemoteCapVal->"identity" . ")..."); - /interface/wifi/capsman/remote-cap/upgrade $RemoteCap; - } else={ - $LogPrint warning $ScriptName ("Remote CAP vanished, skipping upgrade."); - } - :delay ($Delay . "s"); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/certificate-renew-issued b/certificate-renew-issued new file mode 100644 index 0000000..72b723f --- /dev/null +++ b/certificate-renew-issued @@ -0,0 +1,38 @@ +#!rsc by RouterOS +# RouterOS script: certificate-renew-issued +# Copyright (c) 2019-2022 Christian Hesse +# 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/certificate-renew-issued.rsc b/certificate-renew-issued.rsc deleted file mode 100644 index 91a48de..0000000 --- a/certificate-renew-issued.rsc +++ /dev/null @@ -1,52 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: certificate-renew-issued -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# renew locally issued certificates -# https://rsc.eworm.de/doc/certificate-renew-issued.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CertIssuedExportPass; - - :global LogPrint; - :global MkDir; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :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")); - $LogPrint info $ScriptName ("Issued a new certificate for '" . $CertVal->"common-name" . \ - "', exported to 'cert-issued/" . $CertVal->"common-name" . ".p12'."); - } else={ - $LogPrint warning $ScriptName ("Failed creating directory, not exporting certificate."); - } - } else={ - $LogPrint info $ScriptName ("Issued a new certificate for '" . $CertVal->"common-name" . "'."); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/certs/Certum-Trusted-Network-CA.pem b/certs/Certum-Trusted-Network-CA.pem deleted file mode 100644 index a48e706..0000000 --- a/certs/Certum-Trusted-Network-CA.pem +++ /dev/null @@ -1,29 +0,0 @@ -# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA" -# Serial: 279744 -# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 -# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e -# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e ------BEGIN CERTIFICATE----- -MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM -MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D -ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU -cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 -WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg -Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw -IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH -UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM -TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU -BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM -kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x -AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV -HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y -sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL -I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 -J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY -VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI -03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= ------END CERTIFICATE----- diff --git a/certs/DigiCert Global Root CA.pem b/certs/DigiCert Global Root CA.pem new file mode 100644 index 0000000..b0f0013 --- /dev/null +++ b/certs/DigiCert Global Root CA.pem @@ -0,0 +1,29 @@ +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- diff --git a/certs/DigiCert-Global-Root-G2.pem b/certs/DigiCert Global Root G2.pem similarity index 100% rename from certs/DigiCert-Global-Root-G2.pem rename to certs/DigiCert Global Root G2.pem diff --git a/certs/DigiCert-Global-Root-G3.pem b/certs/DigiCert-Global-Root-G3.pem deleted file mode 100644 index 12324dc..0000000 --- a/certs/DigiCert-Global-Root-G3.pem +++ /dev/null @@ -1,22 +0,0 @@ -# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G3" -# Serial: 7089244469030293291760083333884364146 -# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca -# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e -# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 ------BEGIN CERTIFICATE----- -MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe -Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw -EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x -IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG -fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO -Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd -BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx -AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ -oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 -sycX ------END CERTIFICATE----- diff --git a/certs/GTS-Root-R1.pem b/certs/GTS Root R1.pem similarity index 100% rename from certs/GTS-Root-R1.pem rename to certs/GTS Root R1.pem diff --git a/certs/GTS-Root-R4.pem b/certs/GTS Root R4.pem similarity index 100% rename from certs/GTS-Root-R4.pem rename to certs/GTS Root R4.pem diff --git a/certs/Go-Daddy-Root-Certificate-Authority-G2.pem b/certs/Go Daddy Root Certificate Authority - G2.pem similarity index 100% rename from certs/Go-Daddy-Root-Certificate-Authority-G2.pem rename to certs/Go Daddy Root Certificate Authority - G2.pem diff --git a/certs/ISRG-Root-X1.pem b/certs/ISRG Root X1.pem similarity index 100% rename from certs/ISRG-Root-X1.pem rename to certs/ISRG Root X1.pem diff --git a/certs/ISRG-Root-X2.pem b/certs/ISRG Root X2.pem similarity index 100% rename from certs/ISRG-Root-X2.pem rename to certs/ISRG Root X2.pem diff --git a/certs/Makefile b/certs/Makefile deleted file mode 100644 index 3ccad6e..0000000 --- a/certs/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -# Makefile to check certificates - -CURL = curl \ - --capath /dev/null \ - --connect-timeout 5 \ - --output /dev/null \ - --silent - -DOMAINS_DUAL = \ - api.macvendors.com/GTS-Root-R4 \ - api.telegram.org/Go-Daddy-Root-Certificate-Authority-G2 \ - cloudflare-dns.com/DigiCert-Global-Root-G2 \ - dns.google/GTS-Root-R4 \ - dns.quad9.net/DigiCert-Global-Root-G3 \ - git.eworm.de/ISRG-Root-X2 \ - lists.blocklist.de/Certum-Trusted-Network-CA \ - matrix.org/GTS-Root-R4 \ - raw.githubusercontent.com/USERTrust-RSA-Certification-Authority \ - rsc.eworm.de/ISRG-Root-X2 \ - upgrade.mikrotik.com/ISRG-Root-X1 -DOMAINS_IPV4 = \ - 1.1.1.1/DigiCert-Global-Root-G2 \ - 8.8.8.8/GTS-Root-R1 \ - 9.9.9.9/DigiCert-Global-Root-G3 \ - api.mullvad.net/ISRG-Root-X1 \ - ipv4.showipv6.de/ISRG-Root-X1 \ - ipv4.tunnelbroker.net/Starfield-Root-Certificate-Authority-G2 \ - mkcert.org/ISRG-Root-X1 \ - ntfy.sh/ISRG-Root-X1 \ - www.dshield.org/ISRG-Root-X1 \ - www.spamhaus.org/GTS-Root-R4 -DOMAINS_IPV6 = \ - [2606\:4700\:4700\:\:1111]/DigiCert-Global-Root-G2 \ - [2001\:4860\:4860\:\:8888]/GTS-Root-R1 \ - [2620\:fe\:\:9]/DigiCert-Global-Root-G3 \ - ipv6.showipv6.de/ISRG-Root-X1 - -.PHONY: $(DOMAINS_DUAL) $(DOMAINS_IPV4) $(DOMAINS_IPV6) - -all: $(DOMAINS_DUAL) $(DOMAINS_IPV4) $(DOMAINS_IPV6) - -$(DOMAINS_DUAL): -ifndef NOIPV4 - $(CURL) -4 --cacert $(notdir $@).pem https://$(dir $@) -endif -ifndef NOIPV6 - $(CURL) -6 --cacert $(notdir $@).pem https://$(dir $@) -endif - -$(DOMAINS_IPV4): -ifndef NOIPV4 - $(CURL) -4 --cacert $(notdir $@).pem https://$(dir $@) -endif - -$(DOMAINS_IPV6): -ifndef NOIPV6 - $(CURL) -6 --cacert $(notdir $@).pem https://$(dir $@) -endif diff --git a/certs/Starfield-Root-Certificate-Authority-G2.pem b/certs/Starfield Root Certificate Authority - G2.pem similarity index 100% rename from certs/Starfield-Root-Certificate-Authority-G2.pem rename to certs/Starfield Root Certificate Authority - G2.pem diff --git a/certs/USERTrust-RSA-Certification-Authority.pem b/certs/USERTrust-RSA-Certification-Authority.pem deleted file mode 100644 index 0fbeef6..0000000 --- a/certs/USERTrust-RSA-Certification-Authority.pem +++ /dev/null @@ -1,41 +0,0 @@ -# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Label: "USERTrust RSA Certification Authority" -# Serial: 2645093764781058787591871645665788717 -# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 -# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e -# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- diff --git a/check-certificates b/check-certificates new file mode 100644 index 0000000..1f40496 --- /dev/null +++ b/check-certificates @@ -0,0 +1,135 @@ +#!rsc by RouterOS +# RouterOS script: check-certificates +# Copyright (c) 2013-2022 Christian Hesse +# 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 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; + :foreach PassPhrase in=$CertRenewPass do={ + / certificate import file-name=$CertFileName passphrase=$PassPhrase as-value; + } + / file remove [ find where name=$CertFileName ]; + + :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") ]; + + :do { + / ip ipsec identity set certificate=($CertNewVal->"name") [ / ip ipsec identity find where certificate=($CertVal->"name") ]; + / ip ipsec identity set remote-certificate=($CertNewVal->"name") [ / ip ipsec identity find where remote-certificate=($CertVal->"name") ]; + } on-error={ + $LogPrintExit2 debug $0 ("Setting IPSEC certificates failed. Package 'security' not installed?") false; + } + + :do { + / ip hotspot profile set ssl-certificate=($CertNewVal->"name") [ / ip hotspot profile find where ssl-certificate=($CertVal->"name") ]; + } on-error={ + $LogPrintExit2 debug $0 ("Setting hotspot certificates failed. Package 'hotspot' not installed?") false; + } + + / 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<2w !(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-certificates.rsc b/check-certificates.rsc deleted file mode 100644 index be8e4df..0000000 --- a/check-certificates.rsc +++ /dev/null @@ -1,242 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: check-certificates -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch -# -# check for certificate validity -# https://rsc.eworm.de/doc/check-certificates.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CertRenewTime; - :global CertRenewUrl; - :global CertWarnTime; - :global Identity; - - :global CertificateAvailable - :global EscapeForRegEx; - :global IfThenElse; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global UrlEncode; - :global WaitFullyConnected; - - :local CheckCertificatesDownloadImport do={ - :local ScriptName [ :tostr $1 ]; - :local CertName [ :tostr $2 ]; - :local FetchName [ :tostr $3 ]; - - :global CertRenewUrl; - :global CertRenewPass; - - :global CertificateNameByCN; - :global EscapeForRegEx; - :global FetchUserAgentStr; - :global LogPrint; - :global RmFile; - :global UrlEncode; - :global WaitForFile; - - :foreach Type in={ "p12"; "pem" } do={ - :local CertFileName ([ $UrlEncode $FetchName ] . "." . $Type); - $LogPrint debug $ScriptName ("Trying type '" . $Type . "' for '" . $CertName . \ - "' (file '" . $CertFileName . "')..."); - - :do { - /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) \ - ($CertRenewUrl . $CertFileName) dst-path=$CertFileName as-value; - $WaitForFile $CertFileName; - - :local DecryptionFailed true; - :foreach I,PassPhrase in=$CertRenewPass do={ - :do { - $LogPrint debug $ScriptName ("Trying " . $I . ". passphrase... "); - :local Result [ /certificate/import file-name=$CertFileName passphrase=$PassPhrase as-value ]; - :if ($Result->"decryption-failures" = 0) do={ - $LogPrint debug $ScriptName ("Success!"); - :set DecryptionFailed false; - } - } on-error={ } - } - $RmFile $CertFileName; - - :if ($DecryptionFailed = true) do={ - $LogPrint warning $ScriptName ("Decryption failed for certificate file '" . $CertFileName . "'."); - } - - :foreach CertInChain in=[ /certificate/find where common-name!=$CertName !private-key \ - name~("^" . [ $EscapeForRegEx $CertFileName ] . "_[0-9]+\$") \ - !(subject-alt-name~("(^|\\W)(DNS|IP):" . [ $EscapeForRegEx $CertName ] . "(\\W|\$)")) \ - !(common-name=[]) ] do={ - $CertificateNameByCN [ /certificate/get $CertInChain common-name ]; - } - - :return true; - } on-error={ - $LogPrint debug $ScriptName ("Could not download certificate file '" . $CertFileName . "'."); - } - } - - :return false; - } - - :local FormatInfo do={ - :local Cert $1; - - :global FormatLine; - :global FormatMultiLines; - :global IfThenElse; - - :local FormatExpire do={ - :global CharacterReplace; - :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ]; - } - - :local FormatCertChain do={ - :local Cert $1; - - :global EitherOr; - :global ParseKeyValueStore; - - :local CertVal [ /certificate/get $Cert ]; - - :if ([ :typeof ($CertVal->"issuer") ] = "nothing") do={ - :return "self-signed"; - } - - :local Return ""; - :for I from=0 to=5 do={ - :set Return ($Return . [ $EitherOr ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") \ - ([ $ParseKeyValueStore (($CertVal->"issuer")->0) ]->"CN") ]); - :set CertVal [ /certificate/get [ find where skid=($CertVal->"akid") ] ]; - :if (($CertVal->"akid") = "" || ($CertVal->"akid") = ($CertVal->"skid")) do={ - :return $Return; - } - :set Return ($Return . " -> "); - } - :return ($Return . "..."); - } - - :local CertVal [ /certificate/get $Cert ]; - - :return ( \ - [ $FormatLine "Name" ($CertVal->"name") ] . "\n" . \ - [ $IfThenElse ([ :len ($CertVal->"common-name") ] > 0) ([ $FormatLine "CommonName" ($CertVal->"common-name") ] . "\n") ] . \ - [ $IfThenElse ([ :len ($CertVal->"subject-alt-name") ] > 0) ([ $FormatMultiLines "SubjectAltNames" ($CertVal->"subject-alt-name") ] . "\n") ] . \ - [ $FormatLine "Private key" [ $IfThenElse (($CertVal->"private-key") = true) "available" "missing" ] ] . "\n" . \ - [ $FormatLine "Fingerprint" ($CertVal->"fingerprint") ] . "\n" . \ - [ $IfThenElse ([ :len ($CertVal->"ca") ] > 0) [ $FormatLine "Issuer" ($CertVal->"ca") ] [ $FormatLine "Issuer chain" [ $FormatCertChain $Cert ] ] ] . "\n" . \ - "Validity:\n" . \ - [ $FormatLine " from" ($CertVal->"invalid-before") ] . "\n" . \ - [ $FormatLine " to" ($CertVal->"invalid-after") ] . "\n" . \ - [ $FormatLine "Expires in" [ $IfThenElse (($CertVal->"expired") = true) "expired" [ $FormatExpire ($CertVal->"expires-after") ] ] ]); - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - :foreach Cert in=[ /certificate/find where !revoked !ca !scep-url expires-after<$CertRenewTime ] do={ - :local CertVal [ /certificate/get $Cert ]; - :local LastName; - :local FetchName; - - :do { - :if ([ :len $CertRenewUrl ] = 0) do={ - $LogPrint info $ScriptName ("No CertRenewUrl given."); - :error false; - } - $LogPrint info $ScriptName ("Attempting to renew certificate '" . ($CertVal->"name") . "'."); - - :local ImportSuccess false; - :set LastName ($CertVal->"common-name"); - :set FetchName $LastName; - :set ImportSuccess [ $CheckCertificatesDownloadImport $ScriptName $LastName $FetchName ]; - :foreach SAN in=($CertVal->"subject-alt-name") do={ - :if ($ImportSuccess = false) do={ - :set LastName [ :pick $SAN ([ :find $SAN ":" ] + 1) [ :len $SAN ] ]; - :set FetchName $LastName; - :set ImportSuccess [ $CheckCertificatesDownloadImport $ScriptName $LastName $FetchName ]; - :if ($ImportSuccess = false && [ :pick $LastName 0 2 ] = "*.") do={ - :set FetchName ("star." . [ :pick $LastName 2 [ :len $LastName ] ]); - :set ImportSuccess [ $CheckCertificatesDownloadImport $ScriptName $LastName $FetchName ]; - } - } - } - :if ($ImportSuccess = false) do={ :error false; } - - :if ([ :len ($CertVal->"fingerprint") ] > 0 && $CertVal->"fingerprint" != [ /certificate/get $Cert fingerprint ]) do={ - $LogPrint debug $ScriptName ("Certificate '" . $CertVal->"name" . "' was updated in place."); - :set CertVal [ /certificate/get $Cert ]; - } else={ - $LogPrint debug $ScriptName ("Certificate '" . $CertVal->"name" . "' was not updated, but replaced."); - - :local CertNew [ /certificate/find where name~("^" . [ $EscapeForRegEx [ $UrlEncode $FetchName ] ] . "\\.(p12|pem)_[0-9]+\$") \ - (common-name=($CertVal->"common-name") or subject-alt-name~("(^|\\W)(DNS|IP):" . [ $EscapeForRegEx $LastName ] . "(\\W|\$)")) \ - fingerprint!=[ :tostr ($CertVal->"fingerprint") ] expires-after>$CertRenewTime ]; - :local CertNewVal [ /certificate/get $CertNew ]; - - :if ([ $CertificateAvailable ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") ] = false) do={ - $LogPrint warning $ScriptName ("The certificate chain is not available!"); - } - - :if (($CertVal->"private-key") = true && ($CertVal->"private-key") != ($CertNewVal->"private-key")) do={ - /certificate/remove $CertNew; - $LogPrint warning $ScriptName ("Old certificate '" . ($CertVal->"name") . "' has a private key, new certificate does not. Aborting renew."); - :error false; - } - - /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"); - :set Cert $CertNew; - :set CertVal [ /certificate/get $CertNew ]; - } - - $SendNotification2 ({ origin=$ScriptName; silent=true; \ - subject=([ $SymbolForNotification "lock-with-ink-pen" ] . "Certificate renewed: " . ($CertVal->"name")); \ - message=("A certificate on " . $Identity . " has been renewed.\n\n" . [ $FormatInfo $Cert ]) }); - $LogPrint info $ScriptName ("The certificate '" . ($CertVal->"name") . "' has been renewed."); - } on-error={ - $LogPrint debug $ScriptName ("Could not renew certificate '" . ($CertVal->"name") . "'."); - } - } - - :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={ - $LogPrint debug $ScriptName ("Certificate '" . ($CertVal->"name") . "' is handled by SCEP, skipping."); - } else={ - :local State [ $IfThenElse (($CertVal->"expired") = true) "expired" "is about to expire" ]; - - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "warning-sign" ] . "Certificate warning: " . ($CertVal->"name")); \ - message=("A certificate on " . $Identity . " " . $State . ".\n\n" . [ $FormatInfo $Cert ]) }); - $LogPrint info $ScriptName ("The certificate '" . ($CertVal->"name") . "' " . $State . \ - ", it is invalid after " . ($CertVal->"invalid-after") . "."); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/check-health b/check-health new file mode 100644 index 0000000..28de93e --- /dev/null +++ b/check-health @@ -0,0 +1,121 @@ +#!rsc by RouterOS +# RouterOS script: check-health +# Copyright (c) 2019-2022 Christian Hesse +# 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 CheckHealthLast; +:global CheckHealthTemperature; +:global CheckHealthTemperatureDeviation; +:global CheckHealthTemperatureNotified; +:global CheckHealthVoltageLow; +:global CheckHealthVoltagePercent; +:global Identity; + +:global IfThenElse; +:global LogPrintExit2; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; + +:local FormatVoltage do={ + :local Voltage [ :tonum $1 ]; + :return (($Voltage / 10) . "." . [ :pick $Voltage ([ :len $Voltage ] - 1) ] . "V"); +} + +:local CheckHealthCurrent [ / system health get ]; + +:if ([ :len $CheckHealthCurrent ] = 0) do={ + $LogPrintExit2 error $0 ("Your device does not provide any health values.") true; +} + +:if ([ :typeof $CheckHealthTemperatureNotified ] != "array") do={ + :set CheckHealthTemperatureNotified [ :toarray "" ]; +} + +$ScriptLock $0; + +:foreach Name,Voltage in=$CheckHealthCurrent do={ + :if ($Name ~ "(battery|voltage)" && \ + [ :typeof ($CheckHealthLast->$Name) ] = "num" && \ + [ :typeof $Voltage ] = "num") do={ + :if ($CheckHealthLast->$Name * (100 + $CheckHealthVoltagePercent) < $Voltage * 100 || \ + $CheckHealthLast->$Name * 100 > $Voltage * (100 + $CheckHealthVoltagePercent)) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification ("high-voltage-sign,chart-" . [ $IfThenElse ($CheckHealthLast->$Name < \ + $Voltage) "in" "de" ] . "creasing") ] . "Health warning: " . $Name); \ + message=("The " . $Name . " on " . $Identity . " jumped more than " . $CheckHealthVoltagePercent . "%.\n\n" . \ + "old value: " . [ $FormatVoltage ($CheckHealthLast->$Name) ] . "\n" . \ + "new value: " . [ $FormatVoltage $Voltage ]) }); + } else={ + :if ($Voltage <= $CheckHealthVoltageLow && $CheckHealthLast->$Name > $CheckHealthVoltageLow) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "high-voltage-sign,chart-decreasing" ] . "Health warning: Low " . $Name); \ + message=("The " . $Name . " on " . $Identity . " dropped to " . [ $FormatVoltage $Voltage ] . " below hard limit.") }); + } + :if ($Voltage > $CheckHealthVoltageLow && $CheckHealthLast->$Name <= $CheckHealthVoltageLow) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "high-voltage-sign,chart-increasing" ] . "Health recovery: Low " . $Name); \ + message=("The " . $Name . " on " . $Identity . " recovered to " . [ $FormatVoltage $Voltage ] . " above hard limit.") }); + } + } + } +} + +:foreach Name,PSU in=$CheckHealthCurrent do={ + :if ($Name ~ "psu.*-state" && \ + [ :typeof ($CheckHealthLast->$Name) ] = "str" && \ + [ :typeof $PSU ] = "str") do={ + :if ($CheckHealthLast->$Name = "ok" && \ + $PSU != "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" && \ + $PSU = "ok") do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ + message=("The power supply unit '" . $Name . "' on " . $Identity . " recovered!") }); + } + } +} + +:foreach Name,Temperature in=$CheckHealthCurrent do={ + :if ($Name ~ "temperature" && \ + [ :typeof $Temperature ] = "num") 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 $Name ]; + :while ($Temperature != $Validate) do={ + :set Temperature $Validate; + :set Validate [ / system health get $Name ]; + } + :if ($Temperature > $CheckHealthTemperature->$Name && \ + $CheckHealthTemperatureNotified->$Name != true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "fire" ] . "Health warning: " . $Name); \ + message=("The " . $Name . " on " . $Identity . " is above threshold: " . \ + $Temperature . "\C2\B0" . "C") }); + :set ($CheckHealthTemperatureNotified->$Name) true; + } + :if ($Temperature <= ($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: " . \ + $Temperature . "\C2\B0" . "C") }); + :set ($CheckHealthTemperatureNotified->$Name) false; + } + } +} + +:set CheckHealthLast $CheckHealthCurrent; diff --git a/check-health.d/state.rsc b/check-health.d/state.rsc deleted file mode 100644 index 2991935..0000000 --- a/check-health.d/state.rsc +++ /dev/null @@ -1,48 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: check-health.d/state -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# check for RouterOS health state - state plugin -# https://rsc.eworm.de/doc/check-health.md - -:global CheckHealthPlugins; - -:set ($CheckHealthPlugins->[ :jobname ]) do={ - :local FuncName [ :tostr $0 ]; - - :global CheckHealthLast; - :global Identity; - - :global LogPrint; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ :len [ /system/health/find where type="" name~"-state\$"] ] = 0) do={ - $LogPrint debug $FuncName ("Your device does not provide any state health values."); - :return false; - } - - :foreach State in=[ /system/health/find where type="" name~"-state\$" ] do={ - :local Name [ /system/health/get $State name ]; - :local Value [ /system/health/get $State value ]; - - :if ([ :typeof ($CheckHealthLast->$Name) ] != "nothing") do={ - :if ($CheckHealthLast->$Name = "ok" && \ - $Value != "ok") do={ - $SendNotification2 ({ origin=$FuncName; \ - subject=([ $SymbolForNotification "cross-mark" ] . "Health warning: " . $Name); \ - message=("The device '" . $Name . "' on " . $Identity . " failed!") }); - } - :if ($CheckHealthLast->$Name != "ok" && \ - $Value = "ok") do={ - $SendNotification2 ({ origin=$FuncName; \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Health recovery: " . $Name); \ - message=("The device '" . $Name . "' on " . $Identity . " recovered!") }); - } - } - :set ($CheckHealthLast->$Name) $Value; - } -} diff --git a/check-health.d/temperature.rsc b/check-health.d/temperature.rsc deleted file mode 100644 index a2f632d..0000000 --- a/check-health.d/temperature.rsc +++ /dev/null @@ -1,74 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: check-health.d/temperature -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# check for RouterOS health state - temperature plugin -# https://rsc.eworm.de/doc/check-health.md - -:global CheckHealthPlugins; - -:set ($CheckHealthPlugins->[ :jobname ]) do={ - :local FuncName [ :tostr $0 ]; - - :global CheckHealthLast; - :global CheckHealthTemperature; - :global CheckHealthTemperatureDeviation; - :global CheckHealthTemperatureNotified; - :global Identity; - - :global LogPrint; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ :len [ /system/health/find where type="C" ] ] = 0) do={ - $LogPrint debug $FuncName ("Your device does not provide any voltage health values."); - :return false; - } - - :local TempToNum do={ - :global CharacterReplace; - :local T [ :toarray [ $CharacterReplace $1 "." "," ] ]; - :return ($T->0 * 10 + $T->1); - } - - :if ([ :typeof $CheckHealthTemperatureNotified ] != "array") do={ - :set CheckHealthTemperatureNotified ({}); - } - - :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={ - $LogPrint info $FuncName ("No threshold given for " . $Name . ", assuming 50C."); - :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=$FuncName; \ - 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=$FuncName; \ - 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-health.d/voltage.rsc b/check-health.d/voltage.rsc deleted file mode 100644 index 9071c88..0000000 --- a/check-health.d/voltage.rsc +++ /dev/null @@ -1,63 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: check-health.d/voltage -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# check for RouterOS health state - voltage plugin -# https://rsc.eworm.de/doc/check-health.md - -:global CheckHealthPlugins; - -:set ($CheckHealthPlugins->[ :jobname ]) do={ - :local FuncName [ :tostr $0 ]; - - :global CheckHealthLast; - :global CheckHealthVoltageLow; - :global CheckHealthVoltagePercent; - :global Identity; - - :global FormatLine; - :global IfThenElse; - :global LogPrint; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ :len [ /system/health/find where type="V" ] ] = 0) do={ - $LogPrint debug $FuncName ("Your device does not provide any voltage health values."); - :return false; - } - - :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=$FuncName; \ - 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" . \ - [ $FormatLine "old value" ($CheckHealthLast->$Name . " V") 12 ] . "\n" . \ - [ $FormatLine "new value" ($Value . " V") 12 ]) }); - } else={ - :if ($NumCurr <= $CheckHealthVoltageLow && $NumLast > $CheckHealthVoltageLow) do={ - $SendNotification2 ({ origin=$FuncName; \ - 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=$FuncName; \ - 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; - } -} diff --git a/check-health.rsc b/check-health.rsc deleted file mode 100644 index f02a249..0000000 --- a/check-health.rsc +++ /dev/null @@ -1,110 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: check-health -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# check for RouterOS health state -# https://rsc.eworm.de/doc/check-health.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CheckHealthCPUUtilization; - :global CheckHealthCPUUtilizationNotified; - :global CheckHealthLast; - :global CheckHealthRAMUtilizationNotified; - :global Identity; - - :global FormatLine; - :global HumanReadableNum; - :global IfThenElse; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global ValidateSyntax; - - :local TempToNum do={ - :global CharacterReplace; - :local T [ :toarray [ $CharacterReplace $1 "." "," ] ]; - :return ($T->0 * 10 + $T->1); - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :local Resource [ /system/resource/get ]; - - :set CheckHealthCPUUtilization (($CheckHealthCPUUtilization * 4 + ($Resource->"cpu-load") * 10) / 5); - :if ($CheckHealthCPUUtilization > 750 && $CheckHealthCPUUtilizationNotified != true) do={ - $SendNotification2 ({ origin=$ScriptName; \ - 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=$ScriptName; \ - subject=([ $SymbolForNotification "abacus,chart-decreasing" ] . "Health recovery: CPU utilization"); \ - message=("The average CPU utilization on " . $Identity . " decreased to " . ($CheckHealthCPUUtilization / 10) . "%.") }); - :set CheckHealthCPUUtilizationNotified false; - } - - :local CheckHealthRAMUtilization (($Resource->"total-memory" - $Resource->"free-memory") * 100 / $Resource->"total-memory"); - :if ($CheckHealthRAMUtilization >=80 && $CheckHealthRAMUtilizationNotified != true) do={ - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "card-file-box,chart-increasing" ] . "Health warning: RAM utilization"); \ - message=("The RAM utilization on " . $Identity . " is at " . $CheckHealthRAMUtilization . "%!\n\n" . \ - [ $FormatLine "total" ([ $HumanReadableNum ($Resource->"total-memory") 1024 ] . "B") 8 ] . "\n" . \ - [ $FormatLine "used" ([ $HumanReadableNum ($Resource->"total-memory" - $Resource->"free-memory") 1024 ] . "B") 8 ] . "\n" . \ - [ $FormatLine "free" ([ $HumanReadableNum ($Resource->"free-memory") 1024 ] . "B") 8 ]) }); - :set CheckHealthRAMUtilizationNotified true; - } - :if ($CheckHealthRAMUtilization < 70 && $CheckHealthRAMUtilizationNotified = true) do={ - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "card-file-box,chart-decreasing" ] . "Health recovery: RAM utilization"); \ - message=("The RAM utilization on " . $Identity . " decreased to " . $CheckHealthRAMUtilization . "%.") }); - :set CheckHealthRAMUtilizationNotified false; - } - - :local Plugins [ /system/script/find where name~"^check-health.d/." ]; - :if ([ :len $Plugins ] = 0) do={ - $LogPrint debug $ScriptName ("No plugins installed."); - :set ExitOK true; - :error true; - } - - :global CheckHealthPlugins ({}); - :if ([ :typeof $CheckHealthLast ] != "array") do={ - :set CheckHealthLast ({}); - } - - :foreach Plugin in=$Plugins do={ - :local PluginVal [ /system/script/get $Plugin ]; - :if ([ $ValidateSyntax ($PluginVal->"source") ] = true) do={ - :do { - /system/script/run $Plugin; - } on-error={ - $LogPrint error $ScriptName ("Plugin '" . $ScriptVal->"name" . "' failed to run."); - } - } else={ - $LogPrint error $ScriptName ("Plugin '" . $ScriptVal->"name" . "' failed syntax validation, skipping."); - } - } - - :foreach PluginName,Discard in=$CheckHealthPlugins do={ - ($CheckHealthPlugins->$PluginName) \ - ("\$CheckHealthPlugins->\"" . $PluginName . "\""); - } - - :set CheckHealthPlugins; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/check-lte-firmware-upgrade b/check-lte-firmware-upgrade new file mode 100644 index 0000000..5f6bb69 --- /dev/null +++ b/check-lte-firmware-upgrade @@ -0,0 +1,82 @@ +#!rsc by RouterOS +# RouterOS script: check-lte-firmware-upgrade +# Copyright (c) 2018-2022 Christian Hesse +# 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 [ :toarray "" ]; +} + +: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 info $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 "check-lte-firmware-upgrade" ] = true) do={ + $LogPrintExit2 info $0 ("No firmware upgrade available for LTE interface " . $IntName . ".") false; + } + :return true; + } + + :if ([ $ScriptFromTerminal "check-lte-firmware-upgrade" ] = 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-lte-firmware-upgrade.rsc b/check-lte-firmware-upgrade.rsc deleted file mode 100644 index c5b6cb5..0000000 --- a/check-lte-firmware-upgrade.rsc +++ /dev/null @@ -1,107 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: check-lte-firmware-upgrade -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# check for LTE firmware upgrade, send notification -# https://rsc.eworm.de/doc/check-lte-firmware-upgrade.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global SentLteFirmwareUpgradeNotification; - - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :typeof $SentLteFirmwareUpgradeNotification ] != "array") do={ - :global SentLteFirmwareUpgradeNotification ({}); - } - - :local CheckInterface do={ - :local ScriptName $1; - :local Interface $2; - - :global Identity; - :global SentLteFirmwareUpgradeNotification; - - :global FormatLine; - :global IfThenElse; - :global LogPrint; - :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 as-value ]; - :set Info [ /interface/lte/monitor $Interface once as-value ]; - } on-error={ - $LogPrint debug $ScriptName ("Could not get latest LTE firmware version for interface " . \ - $IntName . "."); - :return false; - } - - :if ([ :len ($Firmware->"latest") ] = 0) do={ - $LogPrint info $ScriptName ("An empty string is not a valid version."); - :return false; - } - - :if (($Firmware->"installed") = ($Firmware->"latest")) do={ - :if ([ $ScriptFromTerminal $ScriptName ] = true) do={ - $LogPrint info $ScriptName ("No firmware upgrade available for LTE interface " . $IntName . "."); - } - :return true; - } - - :if ([ $ScriptFromTerminal $ScriptName ] = 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; - $LogPrint info $ScriptName ("Scheduled lte firmware upgrade for interface " . $IntName . "..."); - :return true; - } else={ - :put "Canceled..."; - } - } - - :if (($SentLteFirmwareUpgradeNotification->$IntName) = ($Firmware->"latest")) do={ - $LogPrint debug $ScriptName ("Already sent the LTE firmware upgrade notification for version " . \ - ($Firmware->"latest") . "."); - :return false; - } - - $LogPrint info $ScriptName ("A new firmware version " . ($Firmware->"latest") . " is available for " . \ - "LTE interface " . $IntName . "."); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "sparkles" ] . "LTE firmware upgrade"); \ - message=("A new firmware version " . ($Firmware->"latest") . " is available for " . \ - "LTE interface " . $IntName . " on " . $Identity . ".\n\n" . \ - [ $IfThenElse ([ :len ($Info->"manufacturer") ] > 0) ([ $FormatLine "Manufacturer" ($Info->"manufacturer") ] . "\n") ] . \ - [ $IfThenElse ([ :len ($Info->"model") ] > 0) ([ $FormatLine "Model" ($Info->"model") ] . "\n") ] . \ - [ $IfThenElse ([ :len ($Info->"revision") ] > 0) ([ $FormatLine "Revision" ($Info->"revision") ] . "\n") ] . \ - "Firmware version:\n" . \ - [ $FormatLine " Installed" ($Firmware->"installed") ] . "\n" . \ - [ $FormatLine " Available" ($Firmware->"latest") ]); silent=true }); - :set ($SentLteFirmwareUpgradeNotification->$IntName) ($Firmware->"latest"); - } - - :foreach Interface in=[ /interface/lte/find ] do={ - $CheckInterface $ScriptName $Interface; - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/check-routeros-update b/check-routeros-update new file mode 100644 index 0000000..8cad07a --- /dev/null +++ b/check-routeros-update @@ -0,0 +1,145 @@ +#!rsc by RouterOS +# RouterOS script: check-routeros-update +# Copyright (c) 2013-2022 Christian Hesse +# 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 SafeUpdateNeighbor; +:global SafeUpdateOnCap; +: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 package find where name="wireless" disabled=no ] ] > 0) do={ + :if ([ / interface wireless cap get enabled ] = true && \ + [ / caps-man manager get enabled ] = false && \ + $SafeUpdateOnCap != true) do={ + $LogPrintExit2 error $0 ("System is managed by CAPsMAN, not checking for RouterOS version.") true; + } +} + +: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 ([ :len ($Update->"latest-version") ] = 0) do={ + $LogPrintExit2 info $0 ("An empty string is not a valid version.") true; +} + +: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 ($NumInstalled < $NumLatest) do={ + :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/check-routeros-update.rsc b/check-routeros-update.rsc deleted file mode 100644 index 78161e4..0000000 --- a/check-routeros-update.rsc +++ /dev/null @@ -1,239 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: check-routeros-update -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch, scheduler -# -# check for RouterOS update, send notification and/or install -# https://rsc.eworm.de/doc/check-routeros-update.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - :global SafeUpdateAll; - :global SafeUpdateNeighbor; - :global SafeUpdateNeighborIdentity; - :global SafeUpdatePatch; - :global SafeUpdateUrl; - :global SentRouterosUpdateNotification; - - :global DeviceInfo; - :global EscapeForRegEx; - :global FetchUserAgentStr; - :global LogPrint; - :global ScriptFromTerminal; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global VersionToNum; - :global WaitFullyConnected; - - :local DoUpdate do={ - :local ScriptName [ :tostr $1 ]; - - :global LogPrint; - - :if ([ :len [ /system/script/find where name="packages-update" ] ] > 0) do={ - /system/script/run packages-update; - } else={ - /system/package/update/install without-paging; - } - $LogPrint info $ScriptName ("Waiting for system to reboot."); - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={ - $LogPrint warning $ScriptName ("Running from backup partition, refusing to act."); - :set ExitOK true; - :error false; - } - - $WaitFullyConnected; - - :if ([ :len [ /system/scheduler/find where name="_RebootForUpdate" ] ] > 0) do={ - :set ExitOK true; - :error "A reboot for update is already scheduled."; - } - - :local License [ /system/license/get ]; - :if ([ :typeof ($License->"deadline-at") ] = "str") do={ - :if ([ :len ($License->"next-renewal-at") ] = 0 && ($License->"limited-upgrades") = true) do={ - $LogPrint warning $ScriptName ("Your license expired on " . ($License->"deadline-at") . "!"); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "warning-sign" ] . "License expired!"); \ - message=("Your license expired on " . ($License->"deadline-at") . \ - ", can no longer update RouterOS on " . $Identity . "...") }); - :set ExitOK true; - :error false; - } - - :if ([ :totime ($License->"deadline-at") ] - 3w < [ :timestamp ]) do={ - $LogPrint warning $ScriptName ("Your license will expire on " . ($License->"deadline-at") . "!"); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "warning-sign" ] . "License about to expire!"); \ - message=("Your license failed to renew and is about to expire on " . \ - ($License->"deadline-at") . " on " . $Identity . "...") }); - } - } - - $LogPrint debug $ScriptName ("Checking for updates..."); - /system/package/update/check-for-updates without-paging as-value; - :local Update [ /system/package/update/get ]; - - :if (($Update->"installed-version") = ($Update->"latest-version")) do={ - :if ([ $ScriptFromTerminal $ScriptName ] = true) do={ - $LogPrint info $ScriptName ("System is already up to date."); - } - :set ExitOK true; - :error true; - } - - :if ([ :len ($Update->"latest-version") ] = 0) do={ - $LogPrint info $ScriptName ("Received an empty version string from server."); - :set ExitOK true; - :error false; - } - - :local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; - :local NumLatest [ $VersionToNum ($Update->"latest-version") ]; - :local BitMask [ $VersionToNum "255.255zero0" ]; - :local NumInstalledFeature ($NumInstalled & $BitMask); - :local NumLatestFeature ($NumLatest & $BitMask); - :local Link ("https://mikrotik.com/download/changelogs/" . $Update->"channel" . "-release-tree"); - - :if ($NumLatest < [ $VersionToNum "7.0" ]) do={ - $LogPrint warning $ScriptName ("The version '" . ($Update->"latest-version") . "' is not a valid version."); - :set ExitOK true; - :error false; - } - - :if ($NumInstalled < $NumLatest) do={ - :if ($SafeUpdateAll ~ "^YES,? ?PLEASE!?\$") do={ - $LogPrint info $ScriptName ("Installing ALL versions automatically, including " . \ - $Update->"latest-version" . "..."); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \ - message=("Installing ALL versions automatically, including " . $Update->"latest-version" . \ - "... Updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate $ScriptName; - :set ExitOK true; - :error true; - } - - :if ($SafeUpdatePatch = true && $NumInstalledFeature = $NumLatestFeature) do={ - $LogPrint info $ScriptName ("Version " . $Update->"latest-version" . " is a patch release, updating..."); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \ - message=("Version " . $Update->"latest-version" . " is a patch update for " . $Update->"channel" . \ - ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate $ScriptName; - :set ExitOK true; - :error true; - } - - :if ($SafeUpdateNeighbor = true) do={ - :local Neighbors [ /ip/neighbor/find where platform="MikroTik" identity~$SafeUpdateNeighborIdentity \ - version~("^" . [ $EscapeForRegEx ($Update->"latest-version") ] . "\\b") ]; - :if ([ :len $Neighbors ] > 0) do={ - :local Neighbor [ /ip/neighbor/get ($Neighbors->0) identity ]; - $LogPrint info $ScriptName ("Seen a neighbor (" . $Neighbor . ") running version " . \ - $Update->"latest-version" . " from " . $Update->"channel" . ", updating..."); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \ - message=("Seen a neighbor (" . $Neighbor . ") running version " . $Update->"latest-version" . \ - " from " . $Update->"channel" . ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate $ScriptName; - :set ExitOK true; - :error true; - } - } - - :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") http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) \ - output=user as-value ]; - } on-error={ - $LogPrint warning $ScriptName ("Failed receiving safe version for " . $Update->"channel" . "."); - } - :if ($Result->"status" = "finished" && $Result->"data" = $Update->"latest-version") do={ - $LogPrint info $ScriptName ("Version " . $Update->"latest-version" . " is considered safe, updating..."); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \ - message=("Version " . $Update->"latest-version" . " is considered safe for " . $Update->"channel" . \ - ", updating on " . $Identity . "..."); link=$Link; silent=true }); - $DoUpdate $ScriptName; - :set ExitOK true; - :error true; - } - } - - :if ([ $ScriptFromTerminal $ScriptName ] = true) do={ - :if (($Update->"channel") = "testing" && $NumInstalledFeature < $NumLatestFeature) do={ - :put ("This is a feature update in testing channel. Switch to channel 'stable'? [y/N]"); - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - /system/package/update/set channel=stable; - $LogPrint info $ScriptName ("Switched to channel 'stable', please re-run!"); - :set ExitOK true; - :error true; - } - } - - :put ("Do you want to install RouterOS version " . $Update->"latest-version" . "? [y/N]"); - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - $DoUpdate $ScriptName; - :set ExitOK true; - :error true; - } else={ - :put "Canceled..."; - } - } - - :if ($SentRouterosUpdateNotification = $Update->"latest-version") do={ - $LogPrint info $ScriptName ("Already sent the RouterOS update notification for version " . \ - $Update->"latest-version" . "."); - :set ExitOK true; - :error true; - } - - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "sparkles" ] . "RouterOS update: " . $Update->"latest-version"); \ - 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={ - $LogPrint info $ScriptName ("Already sent the RouterOS downgrade notification for version " . \ - $Update->"latest-version" . "."); - :set ExitOK true; - :error true; - } - - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "warning-sign" ] . "RouterOS version: " . $Update->"latest-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 }); - $LogPrint info $ScriptName ("A different RouterOS version " . ($Update->"latest-version") . \ - " is available for downgrade."); - :set SentRouterosUpdateNotification ($Update->"latest-version"); - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/collect-wireless-mac.capsman b/collect-wireless-mac.capsman new file mode 100644 index 0000000..326aa14 --- /dev/null +++ b/collect-wireless-mac.capsman @@ -0,0 +1,85 @@ +#!rsc by RouterOS +# RouterOS script: collect-wireless-mac.capsman +# Copyright (c) 2013-2022 Christian Hesse +# 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.capsman.rsc b/collect-wireless-mac.capsman.rsc deleted file mode 100644 index 17e09e3..0000000 --- a/collect-wireless-mac.capsman.rsc +++ /dev/null @@ -1,100 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: collect-wireless-mac.capsman -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=40 -# requires RouterOS, version=7.15 -# -# collect wireless mac adresses in access list -# https://rsc.eworm.de/doc/collect-wireless-mac.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - - :global EitherOr; - :global FormatLine; - :global FormatMultiLines; - :global GetMacVendor; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :len [ /caps-man/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - /caps-man/access-list/add comment="--- collected above ---" disabled=yes; - $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- collected above ---'."); - } - :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={ - $LogPrint debug $ScriptName ("Device already gone... Ignoring."); - } - - :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={ - $LogPrint debug $ScriptName ("MAC address " . $RegVal->"mac-address" . " already known: " . \ - [ /caps-man/access-list/get $AccessList comment ]); - } - - :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 active-mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); - :if ([ :len $Lease ] > 0) do={ - :set Address [ /ip/dhcp-server/lease/get $Lease active-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 ] }); - :foreach CName in=[ /ip/dns/static/find where type=CNAME cname=($DnsName->0) ] do={ - :set DnsName ($DnsName, [ /ip/dns/static/get $CName 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"); - $LogPrint info $ScriptName $Message; - /caps-man/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - $SendNotification2 ({ origin=$ScriptName; \ - 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" . \ - [ $FormatLine "Controller" $Identity ] . "\n" . \ - [ $FormatLine "Interface" ($RegVal->"interface") ] . "\n" . \ - [ $FormatLine "SSID" ($RegVal->"ssid") ] . "\n" . \ - [ $FormatLine "MAC" ($RegVal->"mac-address") ] . "\n" . \ - [ $FormatLine "Vendor" $Vendor ] . "\n" . \ - [ $FormatLine "Hostname" $HostName ] . "\n" . \ - [ $FormatLine "Address" $Address ] . "\n" . \ - [ $FormatMultiLines "DNS name" $DnsName ] . "\n" . \ - [ $FormatLine "Date" $DateTime ]) }); - } - } else={ - $LogPrint debug $ScriptName ("No mac address available... Ignoring."); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/collect-wireless-mac.local b/collect-wireless-mac.local new file mode 100644 index 0000000..3f9fddb --- /dev/null +++ b/collect-wireless-mac.local @@ -0,0 +1,86 @@ +#!rsc by RouterOS +# RouterOS script: collect-wireless-mac.local +# Copyright (c) 2013-2022 Christian Hesse +# 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.local.rsc b/collect-wireless-mac.local.rsc deleted file mode 100644 index 4a38bfa..0000000 --- a/collect-wireless-mac.local.rsc +++ /dev/null @@ -1,101 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: collect-wireless-mac.local -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=40 -# requires RouterOS, version=7.15 -# -# collect wireless mac adresses in access list -# https://rsc.eworm.de/doc/collect-wireless-mac.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - - :global EitherOr; - :global FormatLine; - :global FormatMultiLines; - :global GetMacVendor; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :len [ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - /interface/wireless/access-list/add comment="--- collected above ---" disabled=yes; - $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- collected above ---'."); - } - :local PlaceBefore ([ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ]->0); - - :foreach Reg in=[ /interface/wireless/registration-table/find where ap=no ] do={ - :local RegVal; - :do { - :set RegVal [ /interface/wireless/registration-table/get $Reg ]; - } on-error={ - $LogPrint debug $ScriptName ("Device already gone... Ignoring."); - } - - :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={ - $LogPrint debug $ScriptName ("MAC address " . $RegVal->"mac-address" . " already known: " . \ - [ /interface/wireless/access-list/get $AccessList comment ]); - } - - :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 active-mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); - :if ([ :len $Lease ] > 0) do={ - :set Address [ /ip/dhcp-server/lease/get $Lease active-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 ] }); - :foreach CName in=[ /ip/dns/static/find where type=CNAME cname=($DnsName->0) ] do={ - :set DnsName ($DnsName, [ /ip/dns/static/get $CName 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"); - $LogPrint info $ScriptName $Message; - /interface/wireless/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - $SendNotification2 ({ origin=$ScriptName; \ - 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" . \ - [ $FormatLine "Controller" $Identity ] . "\n" . \ - [ $FormatLine "Interface" ($RegVal->"interface") ] . "\n" . \ - [ $FormatLine "SSID" ($RegVal->"ssid") ] . "\n" . \ - [ $FormatLine "MAC" ($RegVal->"mac-address") ] . "\n" . \ - [ $FormatLine "Vendor" $Vendor ] . "\n" . \ - [ $FormatLine "Hostname" $HostName ] . "\n" . \ - [ $FormatLine "Address" $Address ] . "\n" . \ - [ $FormatMultiLines "DNS name" $DnsName ] . "\n" . \ - [ $FormatLine "Date" $DateTime ]) }); - } - } else={ - $LogPrint debug $ScriptName ("No mac address available... Ignoring."); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/collect-wireless-mac.template b/collect-wireless-mac.template new file mode 100644 index 0000000..b3c1c04 --- /dev/null +++ b/collect-wireless-mac.template @@ -0,0 +1,87 @@ +#!rsc by RouterOS +# RouterOS script: collect-wireless-mac%TEMPL% +# Copyright (c) 2013-2022 Christian Hesse +# 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 +# +# !! This is just a template! Replace '%PATH%' with 'caps-man' +# !! or 'interface wireless'! + +:local 0 "collect-wireless-mac%TEMPL%"; +: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 [ / %PATH% access-list find where comment="--- collected above ---" disabled ] ] = 0) do={ + / %PATH% access-list add comment="--- collected above ---" disabled=yes; + $LogPrintExit2 warning $0 ("Added disabled access-list entry with comment '--- collected above ---'.") false; +} +:local PlaceBefore ([ / %PATH% access-list find where comment="--- collected above ---" disabled ]->0); + +:foreach Reg in=[ / %PATH% registration-table find ] do={ + :local RegVal; + :do { + :set RegVal [ / %PATH% registration-table get $Reg ]; + } on-error={ + $LogPrintExit2 debug $0 ("Device already gone... Ignoring.") false; + } + + :if ([ :len ($RegVal->"mac-address") ] > 0) do={ + :local AccessList ([ / %PATH% 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: " . \ + [ / %PATH% 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; + / %PATH% 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.rsc b/collect-wireless-mac.template.rsc deleted file mode 100644 index da901be..0000000 --- a/collect-wireless-mac.template.rsc +++ /dev/null @@ -1,118 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: collect-wireless-mac%TEMPL% -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=40 -# requires RouterOS, version=7.15 -# -# collect wireless mac adresses in access list -# https://rsc.eworm.de/doc/collect-wireless-mac.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - - :global EitherOr; - :global FormatLine; - :global FormatMultiLines; - :global GetMacVendor; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :len [ /caps-man/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - :if ([ :len [ /interface/wifi/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - :if ([ :len [ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - /caps-man/access-list/add comment="--- collected above ---" disabled=yes; - /interface/wifi/access-list/add comment="--- collected above ---" disabled=yes; - /interface/wireless/access-list/add comment="--- collected above ---" disabled=yes; - $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- collected above ---'."); - } - :local PlaceBefore ([ /caps-man/access-list/find where comment="--- collected above ---" disabled ]->0); - :local PlaceBefore ([ /interface/wifi/access-list/find where comment="--- collected above ---" disabled ]->0); - :local PlaceBefore ([ /interface/wireless/access-list/find where comment="--- collected above ---" disabled ]->0); - - :foreach Reg in=[ /caps-man/registration-table/find ] do={ - :foreach Reg in=[ /interface/wifi/registration-table/find ] do={ - :foreach Reg in=[ /interface/wireless/registration-table/find where ap=no ] do={ - :local RegVal; - :do { - :set RegVal [ /caps-man/registration-table/get $Reg ]; - :set RegVal [ /interface/wifi/registration-table/get $Reg ]; - :set RegVal [ /interface/wireless/registration-table/get $Reg ]; - } on-error={ - $LogPrint debug $ScriptName ("Device already gone... Ignoring."); - } - - :if ([ :len ($RegVal->"mac-address") ] > 0) do={ - :local AccessList ([ /caps-man/access-list/find where mac-address=($RegVal->"mac-address") ]->0); - :local AccessList ([ /interface/wifi/access-list/find where mac-address=($RegVal->"mac-address") ]->0); - :local AccessList ([ /interface/wireless/access-list/find where mac-address=($RegVal->"mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - $LogPrint debug $ScriptName ("MAC address " . $RegVal->"mac-address" . " already known: " . \ - [ /caps-man/access-list/get $AccessList comment ]); - [ /interface/wifi/access-list/get $AccessList comment ]); - [ /interface/wireless/access-list/get $AccessList comment ]); - } - - :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 active-mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); - :if ([ :len $Lease ] > 0) do={ - :set Address [ /ip/dhcp-server/lease/get $Lease active-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 ] }); - :foreach CName in=[ /ip/dns/static/find where type=CNAME cname=($DnsName->0) ] do={ - :set DnsName ($DnsName, [ /ip/dns/static/get $CName 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"); - $LogPrint info $ScriptName $Message; - /caps-man/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - /interface/wifi/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - /interface/wireless/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - $SendNotification2 ({ origin=$ScriptName; \ - 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" . \ - [ $FormatLine "Controller" $Identity ] . "\n" . \ - [ $FormatLine "Interface" ($RegVal->"interface") ] . "\n" . \ - [ $FormatLine "SSID" ($RegVal->"ssid") ] . "\n" . \ - [ $FormatLine "MAC" ($RegVal->"mac-address") ] . "\n" . \ - [ $FormatLine "Vendor" $Vendor ] . "\n" . \ - [ $FormatLine "Hostname" $HostName ] . "\n" . \ - [ $FormatLine "Address" $Address ] . "\n" . \ - [ $FormatMultiLines "DNS name" $DnsName ] . "\n" . \ - [ $FormatLine "Date" $DateTime ]) }); - } - } else={ - $LogPrint debug $ScriptName ("No mac address available... Ignoring."); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/collect-wireless-mac.wifi.rsc b/collect-wireless-mac.wifi.rsc deleted file mode 100644 index cb217ce..0000000 --- a/collect-wireless-mac.wifi.rsc +++ /dev/null @@ -1,100 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: collect-wireless-mac.wifi -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=40 -# requires RouterOS, version=7.15 -# -# collect wireless mac adresses in access list -# https://rsc.eworm.de/doc/collect-wireless-mac.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - - :global EitherOr; - :global FormatLine; - :global FormatMultiLines; - :global GetMacVendor; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :len [ /interface/wifi/access-list/find where comment="--- collected above ---" disabled ] ] = 0) do={ - /interface/wifi/access-list/add comment="--- collected above ---" disabled=yes; - $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- collected above ---'."); - } - :local PlaceBefore ([ /interface/wifi/access-list/find where comment="--- collected above ---" disabled ]->0); - - :foreach Reg in=[ /interface/wifi/registration-table/find ] do={ - :local RegVal; - :do { - :set RegVal [ /interface/wifi/registration-table/get $Reg ]; - } on-error={ - $LogPrint debug $ScriptName ("Device already gone... Ignoring."); - } - - :if ([ :len ($RegVal->"mac-address") ] > 0) do={ - :local AccessList ([ /interface/wifi/access-list/find where mac-address=($RegVal->"mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - $LogPrint debug $ScriptName ("MAC address " . $RegVal->"mac-address" . " already known: " . \ - [ /interface/wifi/access-list/get $AccessList comment ]); - } - - :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 active-mac-address=($RegVal->"mac-address") dynamic=yes status=bound ]->0); - :if ([ :len $Lease ] > 0) do={ - :set Address [ /ip/dhcp-server/lease/get $Lease active-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 ] }); - :foreach CName in=[ /ip/dns/static/find where type=CNAME cname=($DnsName->0) ] do={ - :set DnsName ($DnsName, [ /ip/dns/static/get $CName 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"); - $LogPrint info $ScriptName $Message; - /interface/wifi/access-list/add place-before=$PlaceBefore comment=$Message mac-address=($RegVal->"mac-address") disabled=yes; - $SendNotification2 ({ origin=$ScriptName; \ - 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" . \ - [ $FormatLine "Controller" $Identity ] . "\n" . \ - [ $FormatLine "Interface" ($RegVal->"interface") ] . "\n" . \ - [ $FormatLine "SSID" ($RegVal->"ssid") ] . "\n" . \ - [ $FormatLine "MAC" ($RegVal->"mac-address") ] . "\n" . \ - [ $FormatLine "Vendor" $Vendor ] . "\n" . \ - [ $FormatLine "Hostname" $HostName ] . "\n" . \ - [ $FormatLine "Address" $Address ] . "\n" . \ - [ $FormatMultiLines "DNS name" $DnsName ] . "\n" . \ - [ $FormatLine "Date" $DateTime ]) }); - } - } else={ - $LogPrint debug $ScriptName ("No mac address available... Ignoring."); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/contrib/logo-color.d/browser-01.avif b/contrib/logo-color.d/browser-01.avif deleted file mode 100644 index 3dc0a1f..0000000 Binary files a/contrib/logo-color.d/browser-01.avif and /dev/null differ diff --git a/contrib/logo-color.d/browser-02.avif b/contrib/logo-color.d/browser-02.avif deleted file mode 100644 index 1867fbe..0000000 Binary files a/contrib/logo-color.d/browser-02.avif and /dev/null differ diff --git a/contrib/logo-color.d/browser-03.avif b/contrib/logo-color.d/browser-03.avif deleted file mode 100644 index dc24bbb..0000000 Binary files a/contrib/logo-color.d/browser-03.avif and /dev/null differ diff --git a/contrib/logo-color.d/script.js b/contrib/logo-color.d/script.js deleted file mode 100644 index 82cc204..0000000 --- a/contrib/logo-color.d/script.js +++ /dev/null @@ -1,12 +0,0 @@ -function invertHex(hex) { - return (Number("0x1" + hex) ^ 0xffffff).toString(16).substr(1); -} - -function color() { - var svg = document.querySelector(".logo").getSVGDocument(); - svg.getElementById("dark-1").setAttribute("stop-color", document.getElementById("color1").value); - svg.getElementById("dark-2").setAttribute("stop-color", document.getElementById("color2").value); - var background = document.getElementById("color3").value; - svg.getElementById("background").setAttribute("fill", background); - svg.getElementById("hexagon").setAttribute("stroke", "#" + invertHex(background.substring(1))); -} diff --git a/contrib/logo-color.d/style.css b/contrib/logo-color.d/style.css deleted file mode 100644 index eb2ec6a..0000000 --- a/contrib/logo-color.d/style.css +++ /dev/null @@ -1,5 +0,0 @@ -body { - font-family: fira-sans, sans-serif; - font-size: 10pt; - background-color: transparent; -} diff --git a/contrib/logo-color.html b/contrib/logo-color.html deleted file mode 100644 index 17942ce..0000000 --- a/contrib/logo-color.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - -RouterOS-Scripts Logo Color Changer - - - - - -

RouterOS-Scripts Logo Color Changer

- -

You want the logo for your own notifications? But you joined the -Telegram Group and want -something that differentiates? Color it!

- - - -

Select the colors here: - - -

- -

Then right-click, click "Take Screenshot" and finally select the -logo and download it.

- -

Screenshot Browser 01 -Screenshot Browser 02 -Screenshot Browser 03

- -

(This example is with -Firefox. The workflow -for other browsers may differ.)

- -

See how to -Set -a profile photo for your Telegram bot.

- - - diff --git a/contrib/notification.d/script.js b/contrib/notification.d/script.js deleted file mode 100644 index 91741fd..0000000 --- a/contrib/notification.d/script.js +++ /dev/null @@ -1,6 +0,0 @@ -function visible(cb, element) { - document.getElementById(element).style.display = cb.checked ? "block" : "none"; -} -function update(cb, element) { - document.getElementById(element).innerHTML = cb.value; -} diff --git a/contrib/notification.d/style.css b/contrib/notification.d/style.css deleted file mode 100644 index 648ea23..0000000 --- a/contrib/notification.d/style.css +++ /dev/null @@ -1,36 +0,0 @@ -body { - font-family: fira-sans, sans-serif; - font-size: 10pt; - background-color: transparent; -} -div.notification { - position: relative; - float: right; - width: 600px; - border: 3px outset #6c5d53; - /* border-radius: 5px; */ - padding: 10px; - background-color: #e6e6e6; -} -div.content { - padding-left: 60px; -} -img.logo { - float: left; - border-radius: 50%; -} -p.heading { - margin: 0px; - font-weight: bold; - text-decoration: underline; -} -p.hint { - display: none; -} -pre { - font-family: fira-mono, monospace; - white-space: pre-wrap; -} -span.link { - color: #863600; -} diff --git a/contrib/notification.html b/contrib/notification.html deleted file mode 100644 index 7875036..0000000 --- a/contrib/notification.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - -RouterOS-Scripts Notification Generator - - - - - -

RouterOS-Scripts Notification Generator

- -
- -
-

[MikroTik] ℹ️ Subject

-
Message
- -

⏰ This message was queued since oct/18/2022 18:30:48 and may be obsolete.

-

✂️ The message was too long and has been truncated, cut off 13%!

-
-
- -

Hostname:

-

Subject:

-

Message:

-

Show link:

-

Queued since

-

Cut-off with percent

- -

Then right-click, click "Take Screenshot" and finally select the -notification and download it.

- - - diff --git a/daily-psk.capsman b/daily-psk.capsman new file mode 100644 index 0000000..944d52c --- /dev/null +++ b/daily-psk.capsman @@ -0,0 +1,94 @@ +#!rsc by RouterOS +# RouterOS script: daily-psk.capsman +# Copyright (c) 2013-2022 Christian Hesse +# Michael Gisbers +# 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 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 [ :toarray "" ]; +:local Date [ / system clock get date ]; +:local NewPsk [ $GeneratePSK $Date ]; + +:foreach AccList in=[ / caps-man access-list find where comment~$DailyPskMatchComment ] do={ + :local Ssid [ / caps-man access-list get $AccList ssid-regexp ]; + :local Configuration [ / caps-man configuration get ([ find where ssid=$Ssid ]->0) name ]; + :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 interface find where configuration=$Configuration ] ] > 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 ("https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi" . \ + "?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.capsman.rsc b/daily-psk.capsman.rsc deleted file mode 100644 index 5672931..0000000 --- a/daily-psk.capsman.rsc +++ /dev/null @@ -1,96 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: daily-psk.capsman -# Copyright (c) 2013-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# update daily PSK (pre shared key) -# https://rsc.eworm.de/doc/daily-psk.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global DailyPskMatchComment; - :global DailyPskQrCodeUrl; - :global Identity; - - :global FormatLine; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global UrlEncode; - :global WaitForFile; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - # return pseudo-random string for PSK - :local GeneratePSK do={ - :local Date [ :tostr $1 ]; - - :global DailyPskSecrets; - - :global ParseDate; - - :set Date [ $ParseDate $Date ]; - - :local A ((14 - ($Date->"month")) / 12); - :local B (($Date->"year") - $A); - :local C (($Date->"month") + 12 * $A - 2); - :local WeekDay (7000 + ($Date->"day") + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); - :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); - - :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ - ($DailyPskSecrets->1->(($Date->"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={ - $LogPrint info $ScriptName ("Updating daily PSK for '" . $Ssid . "' to '" . $NewPsk . "' (was '" . $OldPsk . "')"); - /caps-man/access-list/set $AccList private-passphrase=$NewPsk; - - :if ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ - :if ($Seen->$Ssid = 1) do={ - $LogPrint debug $ScriptName ("Already sent a mail for SSID " . $Ssid . ", skipping."); - } else={ - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid 8 ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk 8 ] . "\n" . \ - [ $FormatLine "Date" $Date 8 ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); - :set ($Seen->$Ssid) 1; - } - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/daily-psk.local b/daily-psk.local new file mode 100644 index 0000000..022b4a3 --- /dev/null +++ b/daily-psk.local @@ -0,0 +1,94 @@ +#!rsc by RouterOS +# RouterOS script: daily-psk.local +# Copyright (c) 2013-2022 Christian Hesse +# Michael Gisbers +# 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 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 [ :toarray "" ]; +: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=no ] ] = 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 ("https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi" . \ + "?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.rsc b/daily-psk.local.rsc deleted file mode 100644 index 9dea469..0000000 --- a/daily-psk.local.rsc +++ /dev/null @@ -1,95 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: daily-psk.local -# Copyright (c) 2013-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# update daily PSK (pre shared key) -# https://rsc.eworm.de/doc/daily-psk.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global DailyPskMatchComment; - :global DailyPskQrCodeUrl; - :global Identity; - - :global FormatLine; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global UrlEncode; - :global WaitForFile; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - # return pseudo-random string for PSK - :local GeneratePSK do={ - :local Date [ :tostr $1 ]; - - :global DailyPskSecrets; - - :global ParseDate; - - :set Date [ $ParseDate $Date ]; - - :local A ((14 - ($Date->"month")) / 12); - :local B (($Date->"year") - $A); - :local C (($Date->"month") + 12 * $A - 2); - :local WeekDay (7000 + ($Date->"day") + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); - :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); - - :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ - ($DailyPskSecrets->1->(($Date->"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={ - $LogPrint info $ScriptName ("Updating daily PSK for '" . $Ssid . "' to '" . $NewPsk . "' (was '" . $OldPsk . "')"); - /interface/wireless/access-list/set $AccList private-pre-shared-key=$NewPsk; - - :if ([ :len [ /interface/wireless/find where name=$IntName !disabled ] ] = 1) do={ - :if ($Seen->$Ssid = 1) do={ - $LogPrint debug $ScriptName ("Already sent a mail for SSID " . $Ssid . ", skipping."); - } else={ - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid 8 ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk 8 ] . "\n" . \ - [ $FormatLine "Date" $Date 8 ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); - :set ($Seen->$Ssid) 1; - } - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/daily-psk.template b/daily-psk.template new file mode 100644 index 0000000..5f7a832 --- /dev/null +++ b/daily-psk.template @@ -0,0 +1,100 @@ +#!rsc by RouterOS +# RouterOS script: daily-psk%TEMPL% +# Copyright (c) 2013-2022 Christian Hesse +# Michael Gisbers +# 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 +# +# !! This is just a template! Replace '%PATH%' with 'caps-man' +# !! or 'interface wireless'! + +:local 0 "daily-psk%TEMPL%"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global DailyPskMatchComment; +: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 [ :toarray "" ]; +:local Date [ / system clock get date ]; +:local NewPsk [ $GeneratePSK $Date ]; + +:foreach AccList in=[ / %PATH% access-list find where comment~$DailyPskMatchComment ] do={ + :local IntName [ / interface wireless access-list get $AccList interface ]; + :local Ssid [ / interface wireless get $IntName ssid ]; + :local Ssid [ / caps-man access-list get $AccList ssid-regexp ]; + :local Configuration [ / caps-man configuration get ([ find where ssid=$Ssid ]->0) name ]; + :local OldPsk [ / interface wireless access-list get $AccList private-pre-shared-key ]; + :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; + / interface wireless access-list set $AccList private-pre-shared-key=$NewPsk; + / caps-man access-list set $AccList private-passphrase=$NewPsk; + + :if ([ :len [ / interface wireless find where name=$IntName disabled=no ] ] = 1) do={ + :if ([ :len [ / caps-man interface find where configuration=$Configuration ] ] > 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 ("https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi" . \ + "?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.rsc b/daily-psk.template.rsc deleted file mode 100644 index 8202eeb..0000000 --- a/daily-psk.template.rsc +++ /dev/null @@ -1,111 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: daily-psk%TEMPL% -# Copyright (c) 2013-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# update daily PSK (pre shared key) -# https://rsc.eworm.de/doc/daily-psk.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global DailyPskMatchComment; - :global DailyPskQrCodeUrl; - :global Identity; - - :global FormatLine; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global UrlEncode; - :global WaitForFile; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - # return pseudo-random string for PSK - :local GeneratePSK do={ - :local Date [ :tostr $1 ]; - - :global DailyPskSecrets; - - :global ParseDate; - - :set Date [ $ParseDate $Date ]; - - :local A ((14 - ($Date->"month")) / 12); - :local B (($Date->"year") - $A); - :local C (($Date->"month") + 12 * $A - 2); - :local WeekDay (7000 + ($Date->"day") + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); - :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); - - :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ - ($DailyPskSecrets->1->(($Date->"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={ - :foreach AccList in=[ /interface/wifi/access-list/find where comment~$DailyPskMatchComment ] do={ - :foreach AccList in=[ /interface/wireless/access-list/find where comment~$DailyPskMatchComment ] do={ - :local SsidRegExp [ /caps-man/access-list/get $AccList ssid-regexp ]; - :local SsidRegExp [ /interface/wifi/access-list/get $AccList ssid-regexp ]; - :local Configuration ([ /caps-man/configuration/find where ssid~$SsidRegExp ]->0); - :local Configuration ([ /interface/wifi/configuration/find where ssid~$SsidRegExp ]->0); - :local Ssid [ /caps-man/configuration/get $Configuration ssid ]; - :local Ssid [ /interface/wifi/configuration/get $Configuration ssid ]; - :local OldPsk [ /caps-man/access-list/get $AccList private-passphrase ]; - :local OldPsk [ /interface/wifi/access-list/get $AccList passphrase ]; - # /caps-man/ /interface/wifi/ above - /interface/wireless/ below - :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={ - $LogPrint info $ScriptName ("Updating daily PSK for '" . $Ssid . "' to '" . $NewPsk . "' (was '" . $OldPsk . "')"); - /caps-man/access-list/set $AccList private-passphrase=$NewPsk; - /interface/wifi/access-list/set $AccList passphrase=$NewPsk; - /interface/wireless/access-list/set $AccList private-pre-shared-key=$NewPsk; - - :if ([ :len [ /caps-man/actual-interface-configuration/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ - :if ([ :len [ /interface/wifi/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ - :if ([ :len [ /interface/wireless/find where name=$IntName !disabled ] ] = 1) do={ - :if ($Seen->$Ssid = 1) do={ - $LogPrint debug $ScriptName ("Already sent a mail for SSID " . $Ssid . ", skipping."); - } else={ - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid 8 ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk 8 ] . "\n" . \ - [ $FormatLine "Date" $Date 8 ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); - :set ($Seen->$Ssid) 1; - } - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/daily-psk.wifi.rsc b/daily-psk.wifi.rsc deleted file mode 100644 index 3de3c5b..0000000 --- a/daily-psk.wifi.rsc +++ /dev/null @@ -1,96 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: daily-psk.wifi -# Copyright (c) 2013-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# update daily PSK (pre shared key) -# https://rsc.eworm.de/doc/daily-psk.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global DailyPskMatchComment; - :global DailyPskQrCodeUrl; - :global Identity; - - :global FormatLine; - :global LogPrint; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global UrlEncode; - :global WaitForFile; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - # return pseudo-random string for PSK - :local GeneratePSK do={ - :local Date [ :tostr $1 ]; - - :global DailyPskSecrets; - - :global ParseDate; - - :set Date [ $ParseDate $Date ]; - - :local A ((14 - ($Date->"month")) / 12); - :local B (($Date->"year") - $A); - :local C (($Date->"month") + 12 * $A - 2); - :local WeekDay (7000 + ($Date->"day") + $B + ($B / 4) - ($B / 100) + ($B / 400) + ((31 * $C) / 12)); - :set WeekDay ($WeekDay - (($WeekDay / 7) * 7)); - - :return (($DailyPskSecrets->0->(($Date->"day") - 1)) . \ - ($DailyPskSecrets->1->(($Date->"month") - 1)) . \ - ($DailyPskSecrets->2->$WeekDay)); - } - - :local Seen ({}); - :local Date [ /system/clock/get date ]; - :local NewPsk [ $GeneratePSK $Date ]; - - :foreach AccList in=[ /interface/wifi/access-list/find where comment~$DailyPskMatchComment ] do={ - :local SsidRegExp [ /interface/wifi/access-list/get $AccList ssid-regexp ]; - :local Configuration ([ /interface/wifi/configuration/find where ssid~$SsidRegExp ]->0); - :local Ssid [ /interface/wifi/configuration/get $Configuration ssid ]; - :local OldPsk [ /interface/wifi/access-list/get $AccList passphrase ]; - :local Skip 0; - - :if ($NewPsk != $OldPsk) do={ - $LogPrint info $ScriptName ("Updating daily PSK for '" . $Ssid . "' to '" . $NewPsk . "' (was '" . $OldPsk . "')"); - /interface/wifi/access-list/set $AccList passphrase=$NewPsk; - - :if ([ :len [ /interface/wifi/find where configuration.ssid=$Ssid !disabled ] ] > 0) do={ - :if ($Seen->$Ssid = 1) do={ - $LogPrint debug $ScriptName ("Already sent a mail for SSID " . $Ssid . ", skipping."); - } else={ - :local Link ($DailyPskQrCodeUrl . \ - "?scale=8&level=1&ssid=" . [ $UrlEncode $Ssid ] . "&pass=" . [ $UrlEncode $NewPsk ]); - $SendNotification2 ({ origin=$ScriptName; \ - subject=([ $SymbolForNotification "calendar" ] . "daily PSK " . $Ssid); \ - message=("This is the daily PSK on " . $Identity . ":\n\n" . \ - [ $FormatLine "SSID" $Ssid 8 ] . "\n" . \ - [ $FormatLine "PSK" $NewPsk 8 ] . "\n" . \ - [ $FormatLine "Date" $Date 8 ] . "\n\n" . \ - "A client device specific rule must not exist!"); link=$Link }); - :set ($Seen->$Ssid) 1; - } - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/dhcp-lease-comment.capsman b/dhcp-lease-comment.capsman new file mode 100644 index 0000000..c4844bf --- /dev/null +++ b/dhcp-lease-comment.capsman @@ -0,0 +1,30 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-lease-comment.capsman +# Copyright (c) 2013-2022 Christian Hesse +# 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.capsman.rsc b/dhcp-lease-comment.capsman.rsc deleted file mode 100644 index 36b31c8..0000000 --- a/dhcp-lease-comment.capsman.rsc +++ /dev/null @@ -1,43 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: dhcp-lease-comment.capsman -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=60 -# requires RouterOS, version=7.15 -# -# update dhcp-server lease comment with infos from access-list -# https://rsc.eworm.de/doc/dhcp-lease-comment.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :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->"active-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={ - $LogPrint info $ScriptName ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment); - /ip/dhcp-server/lease/set comment=$NewComment $Lease; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/dhcp-lease-comment.local b/dhcp-lease-comment.local new file mode 100644 index 0000000..55a2e3c --- /dev/null +++ b/dhcp-lease-comment.local @@ -0,0 +1,30 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-lease-comment.local +# Copyright (c) 2013-2022 Christian Hesse +# 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.local.rsc b/dhcp-lease-comment.local.rsc deleted file mode 100644 index 35dc6f6..0000000 --- a/dhcp-lease-comment.local.rsc +++ /dev/null @@ -1,43 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: dhcp-lease-comment.local -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=60 -# requires RouterOS, version=7.15 -# -# update dhcp-server lease comment with infos from access-list -# https://rsc.eworm.de/doc/dhcp-lease-comment.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :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->"active-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={ - $LogPrint info $ScriptName ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment); - /ip/dhcp-server/lease/set comment=$NewComment $Lease; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/dhcp-lease-comment.template b/dhcp-lease-comment.template new file mode 100644 index 0000000..fea0c14 --- /dev/null +++ b/dhcp-lease-comment.template @@ -0,0 +1,31 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-lease-comment%TEMPL% +# Copyright (c) 2013-2022 Christian Hesse +# 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 +# +# !! This is just a template! Replace '%PATH%' with 'caps-man' +# !! or 'interface wireless'! + +:local 0 "dhcp-lease-comment%TEMPL%"; +: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 ([ / %PATH% access-list find where mac-address=($LeaseVal->"mac-address") ]->0); + :if ([ :len $AccessList ] > 0) do={ + :set NewComment [ / %PATH% 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.rsc b/dhcp-lease-comment.template.rsc deleted file mode 100644 index 47a8554..0000000 --- a/dhcp-lease-comment.template.rsc +++ /dev/null @@ -1,48 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: dhcp-lease-comment%TEMPL% -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=60 -# requires RouterOS, version=7.15 -# -# update dhcp-server lease comment with infos from access-list -# https://rsc.eworm.de/doc/dhcp-lease-comment.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :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->"active-mac-address") ]->0); - :local AccessList ([ /interface/wifi/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0); - :local AccessList ([ /interface/wireless/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - :set NewComment [ /caps-man/access-list/get $AccessList comment ]; - :set NewComment [ /interface/wifi/access-list/get $AccessList comment ]; - :set NewComment [ /interface/wireless/access-list/get $AccessList comment ]; - } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrint info $ScriptName ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment); - /ip/dhcp-server/lease/set comment=$NewComment $Lease; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/dhcp-lease-comment.wifi.rsc b/dhcp-lease-comment.wifi.rsc deleted file mode 100644 index e0f9785..0000000 --- a/dhcp-lease-comment.wifi.rsc +++ /dev/null @@ -1,43 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: dhcp-lease-comment.wifi -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=60 -# requires RouterOS, version=7.15 -# -# update dhcp-server lease comment with infos from access-list -# https://rsc.eworm.de/doc/dhcp-lease-comment.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :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/wifi/access-list/find where mac-address=($LeaseVal->"active-mac-address") ]->0); - :if ([ :len $AccessList ] > 0) do={ - :set NewComment [ /interface/wifi/access-list/get $AccessList comment ]; - } - :if ([ :len $NewComment ] != 0 && $LeaseVal->"comment" != $NewComment) do={ - $LogPrint info $ScriptName ("Updating comment for DHCP lease " . $LeaseVal->"active-mac-address" . ": " . $NewComment); - /ip/dhcp-server/lease/set comment=$NewComment $Lease; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/dhcp-to-dns b/dhcp-to-dns new file mode 100644 index 0000000..eb73ca3 --- /dev/null +++ b/dhcp-to-dns @@ -0,0 +1,97 @@ +#!rsc by RouterOS +# RouterOS script: dhcp-to-dns +# Copyright (c) 2013-2022 Christian Hesse +# 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/dhcp-to-dns.rsc b/dhcp-to-dns.rsc deleted file mode 100644 index 9b94098..0000000 --- a/dhcp-to-dns.rsc +++ /dev/null @@ -1,130 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: dhcp-to-dns -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=20 -# requires RouterOS, version=7.16 -# -# check DHCP leases and add/remove/update DNS entries -# https://rsc.eworm.de/doc/dhcp-to-dns.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Domain; - :global Identity; - - :global CleanName; - :global EitherOr; - :global IfThenElse; - :global LogPrint; - :global LogPrintOnce; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :local Ttl 5m; - :local CommentPrefix ("managed by " . $ScriptName); - :local CommentString ("--- " . $ScriptName . " above ---"); - - :if ([ :len [ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ] ] = 0) do={ - /ip/dns/static/add name=$CommentString type=NXDOMAIN disabled=yes; - $LogPrint warning $ScriptName ("Added disabled static dns record with name '" . $CommentString . "'."); - } - :local PlaceBefore ([ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ]->0); - - :foreach DnsRecord in=[ /ip/dns/static/find where comment~("^" . $CommentPrefix . "\\b") type=A ] do={ - :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; - :local DnsRecordInfo [ $ParseKeyValueStore ($DnsRecordVal->"comment") ]; - :local MacInServer ($DnsRecordInfo->"macaddress" . " in " . $DnsRecordInfo->"server"); - - :if ([ :len [ /ip/dhcp-server/lease/find where active-mac-address=($DnsRecordInfo->"macaddress") \ - active-address=($DnsRecordVal->"address") server=($DnsRecordInfo->"server") status=bound ] ] > 0) do={ - $LogPrint debug $ScriptName ("Lease for " . $MacInServer . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting record."); - } else={ - :local Found false; - $LogPrint info $ScriptName ("Lease expired for " . $MacInServer . ", deleting record (" . $DnsRecordVal->"name" . ")."); - /ip/dns/static/remove $DnsRecord; - /ip/dns/static/remove [ find where type=CNAME comment=($DnsRecordVal->"comment") ]; - } - } - - :foreach Lease in=[ /ip/dhcp-server/lease/find where status=bound ] do={ - :local LeaseVal; - :do { - :set LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - :if ([ :len [ /ip/dhcp-server/lease/find where active-mac-address=($LeaseVal->"active-mac-address") status=bound ] ] > 1) do={ - $LogPrintOnce info $ScriptName ("Multiple bound leases found for mac-address " . ($LeaseVal->"active-mac-address") . "!"); - } - } on-error={ - $LogPrint debug $ScriptName ("A lease just vanished, ignoring."); - } - - :if ([ :len ($LeaseVal->"active-address") ] > 0) do={ - :local Comment ($CommentPrefix . ", macaddress=" . $LeaseVal->"active-mac-address" . ", server=" . $LeaseVal->"server"); - :local MacDash [ $CleanName ($LeaseVal->"active-mac-address") ]; - :local HostName [ $CleanName [ $EitherOr ([ $ParseKeyValueStore ($LeaseVal->"comment") ]->"hostname") ($LeaseVal->"host-name") ] ]; - :local Network [ /ip/dhcp-server/network/find where ($LeaseVal->"active-address") in address ]; - :local NetworkVal; - :if ([ :len $Network ] > 0) do={ - :set NetworkVal [ /ip/dhcp-server/network/get ($Network->0) ]; - } - :local NetworkInfo [ $ParseKeyValueStore ($NetworkVal->"comment") ]; - :local NetDomain ([ $IfThenElse ([ :len ($NetworkInfo->"name-extra") ] > 0) ($NetworkInfo->"name-extra" . ".") ] . \ - [ $EitherOr [ $EitherOr ($NetworkInfo->"domain") ($NetworkVal->"domain") ] $Domain ]); - :local FullA ($MacDash . "." . $NetDomain); - :local FullCN ($HostName . "." . $NetDomain); - :local MacInServer ($LeaseVal->"active-mac-address" . " in " . $LeaseVal->"server"); - - :local DnsRecord [ /ip/dns/static/find where comment=$Comment type=A ]; - :if ([ :len $DnsRecord ] > 0) do={ - :local DnsRecordVal [ /ip/dns/static/get $DnsRecord ]; - - :if ($DnsRecordVal->"address" = $LeaseVal->"active-address" && $DnsRecordVal->"name" = $FullA) do={ - $LogPrint debug $ScriptName ("The A record for " . $MacInServer . " (" . $FullA . ") does not need updating."); - } else={ - $LogPrint info $ScriptName ("Updating A record for " . $MacInServer . " (" . $FullA . " -> " . $LeaseVal->"active-address" . ")."); - /ip/dns/static/set address=($LeaseVal->"active-address") name=$FullA $DnsRecord; - } - - :local CName [ /ip/dns/static/find where comment=$Comment type=CNAME ]; - :if ([ :len $CName ] > 0) do={ - :local CNameVal [ /ip/dns/static/get $CName ]; - :if ($CNameVal->"name" != $FullCN || $CNameVal->"cname" != $FullA) do={ - $LogPrint info $ScriptName ("Deleting CNAME record with wrong data for " . $MacInServer . "."); - /ip/dns/static/remove $CName; - } - } - :if ([ :len $HostName ] > 0 && [ :len [ /ip/dns/static/find where name=$FullCN type=CNAME ] ] = 0) do={ - $LogPrint info $ScriptName ("Adding CNAME record for " . $MacInServer . " (" . $FullCN . " -> " . $FullA . ")."); - /ip/dns/static/add name=$FullCN type=CNAME cname=$FullA ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - } - - } else={ - $LogPrint info $ScriptName ("Adding A record for " . $MacInServer . " (" . $FullA . " -> " . $LeaseVal->"active-address" . ")."); - /ip/dns/static/add name=$FullA type=A address=($LeaseVal->"active-address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - :if ([ :len $HostName ] > 0 && [ :len [ /ip/dns/static/find where name=$FullCN type=CNAME ] ] = 0) do={ - $LogPrint info $ScriptName ("Adding CNAME record for " . $MacInServer . " (" . $FullCN . " -> " . $FullA . ")."); - /ip/dns/static/add name=$FullCN type=CNAME cname=$FullA ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - } - } - - :if ([ :len [ /ip/dns/static/find where name=$FullA type=A ] ] > 1) do={ - $LogPrintOnce warning $ScriptName ("The name '" . $FullA . "' appeared in more than one A record!"); - } - } else={ - $LogPrint debug $ScriptName ("No address available... Ignoring."); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/doc/accesslist-duplicates.d/01-example.avif b/doc/accesslist-duplicates.d/01-example.avif index 11b3fc5..9b26451 100644 Binary files a/doc/accesslist-duplicates.d/01-example.avif and b/doc/accesslist-duplicates.d/01-example.avif differ diff --git a/doc/accesslist-duplicates.md b/doc/accesslist-duplicates.md index e4d0c7f..db1e00a 100644 --- a/doc/accesslist-duplicates.md +++ b/doc/accesslist-duplicates.md @@ -1,14 +1,7 @@ Find and remove access list duplicates ====================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -22,19 +15,14 @@ entries in wireless access list. Requirements and installation ----------------------------- -Depending on whether you use `wifi` package (`/interface/wifi`), legacy -wifi with CAPsMAN (`/caps-man`) or local wireless interface -(`/interface/wireless`) you need to install a different script. +Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless +interface (`/ interface wireless`) you need to install a different script. -For `wifi`: - - $ScriptInstallUpdate accesslist-duplicates.wifi; - -For legacy CAPsMAN: +For CAPsMAN: $ScriptInstallUpdate accesslist-duplicates.capsman; -For legacy local interface: +For local interface: $ScriptInstallUpdate accesslist-duplicates.local; @@ -43,7 +31,7 @@ Usage and invocation Run this script from a terminal: - /system/script/run accesslist-duplicates.wifi; + / system script run accesslist-duplicates.local; ![screenshot: example](accesslist-duplicates.d/01-example.avif) @@ -53,5 +41,5 @@ See also * [Collect MAC addresses in wireless access list](collect-wireless-mac.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/backup-cloud.d/notification.avif b/doc/backup-cloud.d/notification.avif deleted file mode 100644 index e533908..0000000 Binary files a/doc/backup-cloud.d/notification.avif and /dev/null differ diff --git a/doc/backup-cloud.d/notification.svg b/doc/backup-cloud.d/notification.svg new file mode 100644 index 0000000..b023e50 --- /dev/null +++ b/doc/backup-cloud.d/notification.svg @@ -0,0 +1,216 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] 💾☁ Cloud backup + +Uploaded backup for MikroTik to cloud. + +Hostname: MikroTik +Board name: CHR +Architecture: x86_64 +RouterOS: + Channel: stable + Installed: 6.48.3 +RouterOS-Scripts: + Current: 55 + +Name: cloud-20210614-092419 +Size: 370767 B (362 KiB) +Download key: LLDBfPcWXxmSetWilqeJX5V + + diff --git a/doc/backup-cloud.md b/doc/backup-cloud.md index 7286960..6a15688 100644 --- a/doc/backup-cloud.md +++ b/doc/backup-cloud.md @@ -1,14 +1,7 @@ Upload backup to Mikrotik cloud =============================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -19,14 +12,14 @@ Description This script uploads [binary backup to Mikrotik cloud](https://wiki.mikrotik.com/wiki/Manual:IP/Cloud#Backup). -> ⚠️ **Warning**: The used command can hit errors that a script can with -> workaround only. A notification *should* be sent anyway. But it can result -> in malfunction of fetch command (where all up- and downloads break) for -> some time. Failed notifications are queued then. +> ⚠️ **Warning**: The used command can hit errors that a script can not handle. +> This may result in script termination (where no notification is sent) or +> malfunction of fetch command (where all up- and downloads break) for some +> time. Failed notifications are queued then. ### Sample notification -![backup-cloud notification](backup-cloud.d/notification.avif) +![backup-cloud notification](backup-cloud.d/notification.svg) Requirements and installation ----------------------------- @@ -43,14 +36,8 @@ The configuration goes to `global-config-overlay`, these are the parameters: * `BackupPassword`: password to encrypt the backup with * `BackupRandomDelay`: delay up to amount of seconds when run from scheduler -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). Usage and invocation @@ -58,19 +45,19 @@ Usage and invocation Just run the script: - /system/script/run backup-cloud; + / system script run backup-cloud; Creating a scheduler may be an option: - /system/scheduler/add interval=1w name=backup-cloud on-event="/system/script/run backup-cloud;" start-time=09:20:00; + / system scheduler add interval=1w name=backup-cloud on-event="/ system script run backup-cloud;" start-time=09:20:00; See also -------- * [Send backup via e-mail](backup-email.md) -* [Save configuration to fallback partition](backup-partition.md) +* [Save configuration to fallback partition](doc/backup-partition.md) * [Upload backup to server](backup-upload.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/backup-email.md b/doc/backup-email.md index 7b8bcfe..9c2edb5 100644 --- a/doc/backup-email.md +++ b/doc/backup-email.md @@ -1,14 +1,7 @@ Send backup via e-mail ====================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -16,18 +9,15 @@ Send backup via e-mail Description ----------- -This script sends binary backup (`/system/backup/save`) and complete -configuration export (`/export terse show-sensitive`) via e-mail. +This script sends binary backup (`/ system backup save`) and complete +configuration export (`/ export terse`) via e-mail. Requirements and installation ----------------------------- -Just install the script and the required module: +Just install the script: - $ScriptInstallUpdate mod/notification-email,backup-email; - -Also make sure you configure -[sending notifications via e-mail](mod/notification-email.md). + $ScriptInstallUpdate backup-email; Configuration ------------- @@ -36,33 +26,29 @@ The configuration goes to `global-config-overlay`, these are the parameters: * `BackupSendBinary`: whether to send binary backup * `BackupSendExport`: whether to send configuration export -* `BackupSendGlobalConfig`: whether to send `global-config-overlay` * `BackupPassword`: password to encrypt the backup with * `BackupRandomDelay`: delay up to amount of seconds when run from scheduler -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. +Also valid e-mail settings are required to send mails. Usage and invocation -------------------- Just run the script: - /system/script/run backup-email; + / system script run backup-email; Creating a scheduler may be an option: - /system/scheduler/add interval=1w name=backup-email on-event="/system/script/run backup-email;" start-time=09:15:00; + / system scheduler add interval=1w name=backup-email on-event="/ system script run backup-email;" start-time=09:15:00; See also -------- * [Upload backup to Mikrotik cloud](backup-cloud.md) -* [Save configuration to fallback partition](backup-partition.md) -* [Send notifications via e-mail](mod/notification-email.md) +* [Save configuration to fallback partition](doc/backup-partition.md) * [Upload backup to server](backup-upload.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/backup-partition.md b/doc/backup-partition.md index 9d615a5..c31c780 100644 --- a/doc/backup-partition.md +++ b/doc/backup-partition.md @@ -1,14 +1,7 @@ Save configuration to fallback partition ======================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -18,20 +11,10 @@ Description This script saves the current configuration to fallback [partition](https://wiki.mikrotik.com/wiki/Manual:Partitions). -It can also copy-over the RouterOS installation when run interactively -or just before a feature update. For this to work you need a device with sufficient flash storage that is properly partitioned. -To make you aware of a possible issue a scheduler logging a warning is -added in the backup partition's configuration. You may want to use -[log-forward](log-forward.md) to be notified. - -> ⚠️ **Warning**: By default only the configuration is saved to backup -> partition. Every now and then you should copy your installation over -> for a recent RouterOS version! See below for options. - Requirements and installation ----------------------------- @@ -39,31 +22,16 @@ Just install the script: $ScriptInstallUpdate backup-partition; -Configuration -------------- - -The configuration goes to `global-config-overlay`, the only parameter is: - -* `BackupPartitionCopyBeforeFeatureUpdate`: copy-over the RouterOS - installation when a feature update is pending - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - Usage and invocation -------------------- Just run the script: - /system/script/run backup-partition; - -When run interactively from terminal it supports to copy-over the RouterOS -installation when versions differ. + / system script run backup-partition; Creating a scheduler may be an option: - /system/scheduler/add interval=1w name=backup-partition on-event="/system/script/run backup-partition;" start-time=09:30:00; + / system scheduler add interval=1w name=backup-partition on-event="/ system script run backup-partition;" start-time=09:30:00; See also -------- @@ -71,8 +39,7 @@ See also * [Upload backup to Mikrotik cloud](backup-cloud.md) * [Send backup via e-mail](backup-email.md) * [Upload backup to server](backup-upload.md) -* [Forward log messages via notification](log-forward.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/backup-upload.d/notification.avif b/doc/backup-upload.d/notification.avif deleted file mode 100644 index 83cfb18..0000000 Binary files a/doc/backup-upload.d/notification.avif and /dev/null differ diff --git a/doc/backup-upload.d/notification.svg b/doc/backup-upload.d/notification.svg new file mode 100644 index 0000000..90573ab --- /dev/null +++ b/doc/backup-upload.d/notification.svg @@ -0,0 +1,212 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] 💾⬆ Backup & Config upload + +Backup and config export upload for MikroTik. + +Hostname: MikroTik +Board name: CHR +Architecture: x86_64 +RouterOS: + Channel: stable + Installed: 6.48.3 +RouterOS-Scripts: + Current: 55 + +Backup file: MikroTik_example_com.backup +Config file: MikroTik_example_com.rsc + + diff --git a/doc/backup-upload.md b/doc/backup-upload.md index 6a5b0e4..08d64b0 100644 --- a/doc/backup-upload.md +++ b/doc/backup-upload.md @@ -1,14 +1,7 @@ Upload backup to server ======================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -16,8 +9,8 @@ Upload backup to server Description ----------- -This script uploads binary backup (`/system/backup/save`) and complete -configuration export (`/export terse show-sensitive`) to external server. +This script uploads binary backup (`/ system backup save`) and complete +configuration export (`/ export terse`) to external server. > ⚠️ **Warning**: The used command can hit errors that a script can not handle. > This may result in script termination (where no notification is sent) or @@ -26,7 +19,7 @@ configuration export (`/export terse show-sensitive`) to external server. ### Sample notification -![backup-upload notification](backup-upload.d/notification.avif) +![backup-upload notification](backup-upload.d/notification.svg) Requirements and installation ----------------------------- @@ -42,21 +35,14 @@ The configuration goes to `global-config-overlay`, these are the parameters: * `BackupSendBinary`: whether to send binary backup * `BackupSendExport`: whether to send configuration export -* `BackupSendGlobalConfig`: whether to send `global-config-overlay` * `BackupPassword`: password to encrypt the backup with * `BackupRandomDelay`: delay up to amount of seconds when run from scheduler * `BackupUploadUrl`: url to upload to * `BackupUploadUser`: username for server authentication * `BackupUploadPass`: password for server authentication -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). ### Issues with SFTP client @@ -74,19 +60,19 @@ Usage and invocation Just run the script: - /system/script/run backup-upload; + / system script run backup-upload; Creating a scheduler may be an option: - /system/scheduler/add interval=1w name=backup-upload on-event="/system/script/run backup-upload;" start-time=09:25:00; + / system scheduler add interval=1w name=backup-upload on-event="/ system script run backup-upload;" start-time=09:25:00; See also -------- * [Upload backup to Mikrotik cloud](backup-cloud.md) * [Send backup via e-mail](backup-email.md) -* [Save configuration to fallback partition](backup-partition.md) +* [Save configuration to fallback partition](doc/backup-partition.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/capsman-download-packages.md b/doc/capsman-download-packages.md index 5722227..bac8a3c 100644 --- a/doc/capsman-download-packages.md +++ b/doc/capsman-download-packages.md @@ -1,14 +1,7 @@ Download packages for CAP upgrade from CAPsMAN ============================================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -25,47 +18,24 @@ This script automatically downloads these packages. Requirements and installation ----------------------------- -Make sure you have the `package-path` set in your CAPsMAN configuration, -as that is where packages are downloaded to and where the system expects -them. +Just install the script on CAPsMAN device: -Then just install the script on CAPsMAN device. -Depending on whether you use `wifi` package (`/interface/wifi`) or legacy -wifi with CAPsMAN (`/caps-man`) you need to install a different script. + $ScriptInstallUpdate capsman-download-packages; -For `wifi`: +Optionally add a scheduler to run after startup: - $ScriptInstallUpdate capsman-download-packages.wifi; - -For legacy CAPsMAN: - - $ScriptInstallUpdate capsman-download-packages.capsman; - -Optionally add a scheduler to run after startup. For `wifi`: - - /system/scheduler/add name=capsman-download-packages on-event="/system/script/run capsman-download-packages.wifi;" start-time=startup; - -For legacy CAPsMAN: - - /system/scheduler/add name=capsman-download-packages on-event="/system/script/run capsman-download-packages.capsman;" start-time=startup; + / system scheduler add name=capsman-download-packages on-event="/ system script run capsman-download-packages;" start-time=startup; Packages available in local storage in older version are downloaded -unconditionally. - -If no packages are found the script downloads a default set of packages: - - * `wifi`: `routeros` and `wifi-qcom` for *arm* and *arm64*, `wifi-qcom-ac` for *arm* - * legacy CAPsMAN: `routeros` and `wireless` for *arm* and *mipsbe* - -> ℹ️ **Info**: If you have packages in the directory and things go wrong for -> what ever unknown reason: Remove **all** packages and start over. +unconditionally. The script tries to download missing packages by guessing +from system log. Usage and invocation -------------------- Run the script manually: - /system/script/run capsman-download-packages.wifi; + / system script run capsman-download-packages; ... or from scheduler. @@ -79,5 +49,5 @@ See also * [Run rolling CAP upgrades from CAPsMAN](capsman-rolling-upgrade.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/capsman-rolling-upgrade.md b/doc/capsman-rolling-upgrade.md index d277db6..34e3c91 100644 --- a/doc/capsman-rolling-upgrade.md +++ b/doc/capsman-rolling-upgrade.md @@ -1,14 +1,7 @@ Run rolling CAP upgrades from CAPsMAN ===================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -27,17 +20,9 @@ parallel. Requirements and installation ----------------------------- -Just install the script on CAPsMAN device. -Depending on whether you use `wifi` package (`/interface/wifi`) or legacy -wifi with CAPsMAN (`/caps-man`) you need to install a different script. +Just install the script: -For `wifi`: - - $ScriptInstallUpdate capsman-rolling-upgrade.wifi; - -For legacy CAPsMAN: - - $ScriptInstallUpdate capsman-rolling-upgrade.capsman; + $ScriptInstallUpdate capsman-rolling-upgrade; Usage and invocation -------------------- @@ -48,7 +33,7 @@ that script when required. Alternatively run it manually: - /system/script/run capsman-rolling-upgrade.wifi; + / system script run capsman-rolling-upgrade; See also -------- @@ -56,5 +41,5 @@ See also * [Download packages for CAP upgrade from CAPsMAN](capsman-download-packages.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/certificate-renew-issued.md b/doc/certificate-renew-issued.md index c4615b5..d7c4676 100644 --- a/doc/certificate-renew-issued.md +++ b/doc/certificate-renew-issued.md @@ -1,14 +1,7 @@ Renew locally issued certificates ================================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -35,16 +28,12 @@ parameter: * `CertRenewPass`: an array holding individual passphrases for certificates -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - Usage and invocation -------------------- Run the script to renew certificates issued from a local CA. - /system/script/run certificate-renew-issued; + / system script run certificate-renew-issued; Only scripts with a remaining lifetime of three weeks or less are renewed. The old certificate is revoked automatically. If a passphrase for a specific @@ -57,5 +46,5 @@ See also * [Renew certificates and notify on expiration](check-certificates.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/check-certificates.d/notification.avif b/doc/check-certificates.d/notification.avif deleted file mode 100644 index 7c250da..0000000 Binary files a/doc/check-certificates.d/notification.avif and /dev/null differ diff --git a/doc/check-certificates.d/notification.svg b/doc/check-certificates.d/notification.svg new file mode 100644 index 0000000..a299963 --- /dev/null +++ b/doc/check-certificates.d/notification.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] 🔏 Certificate renewed + +A certificate on MikroTik has been renewed. + +Name: example.com +CommonName: example.com +Private key: available +Fingerprint: cc54cdd01fcd7698ecb71213874be776906eb33d26cd57754d168632f14c4c8b +Issuer: R3 +Validity: may/22/2021 22:29:34 to aug/20/2021 22:29:34 +Expires in: 11w 5d 08:18:06 + + diff --git a/doc/check-certificates.md b/doc/check-certificates.md index 4c144ba..a553e6a 100644 --- a/doc/check-certificates.md +++ b/doc/check-certificates.md @@ -1,14 +1,7 @@ Renew certificates and notify on expiration =========================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -21,7 +14,7 @@ certificates that are still about to expire. ### Sample notification -![check-certificates notification](check-certificates.d/notification.avif) +![check-certificates notification](check-certificates.d/notification.svg) Requirements and installation ----------------------------- @@ -33,59 +26,33 @@ Just install the script: Configuration ------------- +The expiry notifications just require notification settings for e-mail, +[matrix](mod/notification-matrix.md) and/or +[telegram](mod/notification-telegram.md). + For automatic download and renewal of certificates you need configuration in `global-config-overlay`, these are the parameters: * `CertRenewPass`: an array of passphrases to try -* `CertRenewTime`: on what remaining time to try a renew * `CertRenewUrl`: the url to download certificates from -* `CertWarnTime`: on what remaining time to warn via notification -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Certificates on the web server should be named by their common name, like -`CN.pem` (`PEM` format) or`CN.p12` (`PKCS#12` format). Alternatively any -subject alternative name (aka *Subject Alt Name* or *SAN*) can be used. - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or -[telegram](mod/notification-telegram.md). +Certificates on the web server should be named `CN.pem` (`PEM` format) or +`CN.p12` (`PKCS#12` format). Usage and invocation -------------------- Just run the script: - /system/script/run check-certificates; + / system script run check-certificates; ... or create a scheduler for periodic execution: - /system/scheduler/add interval=1d name=check-certificates on-event="/system/script/run check-certificates;" start-time=startup; + / system scheduler add interval=1d name=check-certificates on-event="/ system script run check-certificates;" start-time=startup; +Alternatively running on startup may be desired: -Tips & Tricks -------------- - -### Schedule at startup - -The script checks for full connectivity before acting, so scheduling at -startup is perfectly valid: - - /system/scheduler/add name=check-certificates@startup on-event="/system/script/run check-certificates;" start-time=startup; - -### Initial import - -Given you have a certificate on you server, you can use `check-certificates` -for the initial import. Just create a *dummy* certificate with short lifetime -that matches criteria to be renewed: - - /certificate/add name=example.com common-name=example.com days-valid=1; - /certificate/sign example.com; - /system/script/run check-certificates; + / system scheduler add name=check-certificates-startup on-event="/ system script run check-certificates;" start-time=startup; See also -------- @@ -93,5 +60,5 @@ See also * [Renew locally issued certificates](certificate-renew-issued.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/check-health.d/notification-01-cpu-utilization-high.avif b/doc/check-health.d/notification-01-cpu-utilization-high.avif deleted file mode 100644 index 326e7fe..0000000 Binary files a/doc/check-health.d/notification-01-cpu-utilization-high.avif and /dev/null differ diff --git a/doc/check-health.d/notification-01-voltage.svg b/doc/check-health.d/notification-01-voltage.svg new file mode 100644 index 0000000..b762f61 --- /dev/null +++ b/doc/check-health.d/notification-01-voltage.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ⚡📉 Health warning: voltage + +The voltage on MikroTik jumped more than 10%. + +old value: 16.2V +new value: 12.4V + + diff --git a/doc/check-health.d/notification-02-cpu-utilization-ok.avif b/doc/check-health.d/notification-02-cpu-utilization-ok.avif deleted file mode 100644 index 811ccd7..0000000 Binary files a/doc/check-health.d/notification-02-cpu-utilization-ok.avif and /dev/null differ diff --git a/doc/check-health.d/notification-02-temperature-high.svg b/doc/check-health.d/notification-02-temperature-high.svg new file mode 100644 index 0000000..15250f8 --- /dev/null +++ b/doc/check-health.d/notification-02-temperature-high.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] 🔥 Health warning: temperature + +The temperature on MikroTik is above threshold: 51°C + + diff --git a/doc/check-health.d/notification-03-ram-utilization-high.avif b/doc/check-health.d/notification-03-ram-utilization-high.avif deleted file mode 100644 index 59155c5..0000000 Binary files a/doc/check-health.d/notification-03-ram-utilization-high.avif and /dev/null differ diff --git a/doc/check-health.d/notification-03-temperature-ok.svg b/doc/check-health.d/notification-03-temperature-ok.svg new file mode 100644 index 0000000..d517e57 --- /dev/null +++ b/doc/check-health.d/notification-03-temperature-ok.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ✅ Health recovery: temperature + +The temperature on MikroTik dropped below threshold: 48°C + + diff --git a/doc/check-health.d/notification-04-psu-fail.svg b/doc/check-health.d/notification-04-psu-fail.svg new file mode 100644 index 0000000..5d5d573 --- /dev/null +++ b/doc/check-health.d/notification-04-psu-fail.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ❌ Health warning: psu1-state + +The power supply unit 'psu1-state' on MikroTik failed! + + diff --git a/doc/check-health.d/notification-04-ram-utilization-ok.avif b/doc/check-health.d/notification-04-ram-utilization-ok.avif deleted file mode 100644 index d995b9a..0000000 Binary files a/doc/check-health.d/notification-04-ram-utilization-ok.avif and /dev/null differ diff --git a/doc/check-health.d/notification-05-psu-ok.svg b/doc/check-health.d/notification-05-psu-ok.svg new file mode 100644 index 0000000..aae56c9 --- /dev/null +++ b/doc/check-health.d/notification-05-psu-ok.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ✅ Health recovery: psu1-state + +The power supply unit 'psu1-state' on MikroTik recovered! + + diff --git a/doc/check-health.d/notification-05-voltage.avif b/doc/check-health.d/notification-05-voltage.avif deleted file mode 100644 index 17a385b..0000000 Binary files a/doc/check-health.d/notification-05-voltage.avif and /dev/null differ diff --git a/doc/check-health.d/notification-06-temperature-high.avif b/doc/check-health.d/notification-06-temperature-high.avif deleted file mode 100644 index 60d3802..0000000 Binary files a/doc/check-health.d/notification-06-temperature-high.avif and /dev/null differ diff --git a/doc/check-health.d/notification-07-temperature-ok.avif b/doc/check-health.d/notification-07-temperature-ok.avif deleted file mode 100644 index 4afed02..0000000 Binary files a/doc/check-health.d/notification-07-temperature-ok.avif and /dev/null differ diff --git a/doc/check-health.d/notification-08-state-fail.avif b/doc/check-health.d/notification-08-state-fail.avif deleted file mode 100644 index ad049ac..0000000 Binary files a/doc/check-health.d/notification-08-state-fail.avif and /dev/null differ diff --git a/doc/check-health.d/notification-09-state-ok.avif b/doc/check-health.d/notification-09-state-ok.avif deleted file mode 100644 index 26f5a74..0000000 Binary files a/doc/check-health.d/notification-09-state-ok.avif and /dev/null differ diff --git a/doc/check-health.md b/doc/check-health.md index 7cf0c33..f6900ce 100644 --- a/doc/check-health.md +++ b/doc/check-health.md @@ -1,14 +1,7 @@ Notify about health state ========================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -17,50 +10,34 @@ Description ----------- This script is run from scheduler periodically, sending notification on -health related events. Monitoring CPU and RAM utilization (available -processing and memory resources) works on all devices: +health related events: -* high CPU utilization -* high RAM utilization (low available RAM) - -With additional plugins functionality can be extended, depending on -sensors available in hardware: - -* voltage jumps up or down more than configured threshold -* voltage drops below hard lower limit -* fan failed or recovered +* voltage jumps up or down more than configured threshold or drops below limit * power supply failed or recovered * temperature is above or below threshold -> ⚠️ **Warning**: Note that bad initial state will not trigger an event! For -> example rebooting a device that is already too hot will not trigger an -> alert on high temperature. +Note that bad initial state will not trigger an event. + +Only sensors available in hardware can be checked. See what your +hardware supports: + + / system health print; ### Sample notifications -#### CPU utilization - -![check-health notification cpu utilization high](check-health.d/notification-01-cpu-utilization-high.avif) -![check-health notification cpu utilization ok](check-health.d/notification-02-cpu-utilization-ok.avif) - -#### RAM utilization (low available RAM) - -![check-health notification ram utilization high](check-health.d/notification-03-ram-utilization-high.avif) -![check-health notification ram utilization ok](check-health.d/notification-04-ram-utilization-ok.avif) - #### Voltage -![check-health notification voltage](check-health.d/notification-05-voltage.avif) +![check-health notification voltage](check-health.d/notification-01-voltage.svg) #### Temperature -![check-health notification temperature high](check-health.d/notification-06-temperature-high.avif) -![check-health notification temperature ok](check-health.d/notification-07-temperature-ok.avif) +![check-health notification](check-health.d/notification-02-temperature-high.svg) +![check-health notification](check-health.d/notification-03-temperature-ok.svg) #### PSU state -![check-health notification state fail](check-health.d/notification-08-state-fail.avif) -![check-health notification state ok](check-health.d/notification-09-state-ok.avif) +![check-health notification](check-health.d/notification-04-psu-fail.svg) +![check-health notification](check-health.d/notification-05-psu-ok.svg) Requirements and installation ----------------------------- @@ -68,35 +45,7 @@ Requirements and installation Just install the script and create a scheduler: $ScriptInstallUpdate check-health; - /system/scheduler/add interval=53s name=check-health on-event="/system/script/run check-health;" start-time=startup; - -> ℹ️ **Info**: Running lots of scripts simultaneously can tamper the -> precision of cpu utilization, escpecially on devices with limited -> resources. Thus an unusual interval is used here. - -### Plugins - -Additional plugins are available for sensors available in hardware. First -check what your hardware supports: - - /system/health/print; - -Then install the plugin for *fan* and *power supply unit* *state*: - - $ScriptInstallUpdate check-health,check-health.d/state; - -... or *temperature*: - - $ScriptInstallUpdate check-health,check-health.d/temperature; - -... or *voltage*: - - $ScriptInstallUpdate check-health,check-health.d/voltage; - -You can also combine the commands and install all or a subset of plugins -in one go: - - $ScriptInstallUpdate check-health,check-health.d/state,check-health.d/temperature,check-health.d/voltage; + / system scheduler add interval=1m name=check-health on-event="/ system script run check-health;" start-time=startup; Configuration ------------- @@ -107,16 +56,10 @@ The configuration goes to `global-config-overlay`, these are the parameters: * `CheckHealthVoltageLow`: value (in volt*10) giving a hard lower limit * `CheckHealthVoltagePercent`: percentage value to trigger voltage jumps -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/check-lte-firmware-upgrade.d/notification.avif b/doc/check-lte-firmware-upgrade.d/notification.avif deleted file mode 100644 index c440da5..0000000 Binary files a/doc/check-lte-firmware-upgrade.d/notification.avif and /dev/null differ diff --git a/doc/check-lte-firmware-upgrade.d/notification.svg b/doc/check-lte-firmware-upgrade.d/notification.svg new file mode 100644 index 0000000..70aad88 --- /dev/null +++ b/doc/check-lte-firmware-upgrade.d/notification.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ✨ LTE firmware upgrade + +A new firmware version R11e-LTE6_V027 is available +for LTE interface lte on MikroTik. + +Interface: MikroTik R11e-LTE6 +Installed: R11e-LTE6_V025 +Available: R11e-LTE6_V027 + + diff --git a/doc/check-lte-firmware-upgrade.md b/doc/check-lte-firmware-upgrade.md index 3693b71..704a86b 100644 --- a/doc/check-lte-firmware-upgrade.md +++ b/doc/check-lte-firmware-upgrade.md @@ -1,14 +1,7 @@ Notify on LTE firmware upgrade ============================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -26,7 +19,7 @@ upgrades. Currently supported LTE hardware: ### Sample notification -![check-lte-firmware-upgrade notification](check-lte-firmware-upgrade.d/notification.avif) +![check-lte-firmware-upgrade notification](check-lte-firmware-upgrade.d/notification.svg) Requirements and installation ----------------------------- @@ -37,13 +30,12 @@ Just install the script: ... and create a scheduler: - /system/scheduler/add interval=1d name=check-lte-firmware-upgrade on-event="/system/script/run check-lte-firmware-upgrade;" start-time=startup; + / system scheduler add interval=1d name=check-lte-firmware-upgrade on-event="/ system script run check-lte-firmware-upgrade;" start-time=startup; Configuration ------------- -Also notification settings are required for -[e-mail](mod/notification-email.md), +Notification setting are required for e-mail, [matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). @@ -54,5 +46,5 @@ See also * [Install LTE firmware upgrade](unattended-lte-firmware-upgrade.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/check-routeros-update.d/notification.avif b/doc/check-routeros-update.d/notification.avif deleted file mode 100644 index 50317cf..0000000 Binary files a/doc/check-routeros-update.d/notification.avif and /dev/null differ diff --git a/doc/check-routeros-update.d/notification.svg b/doc/check-routeros-update.d/notification.svg new file mode 100644 index 0000000..1eabdbb --- /dev/null +++ b/doc/check-routeros-update.d/notification.svg @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ✨ RouterOS update + +A new RouterOS version 6.48.3 is available for MikroTik. + +Hostname: MikroTik +Board name: CHR +Architecture: x86_64 +RouterOS: + Channel: stable + Installed: 6.48.2 + Available: 6.48.3 +RouterOS-Scripts: + Current: 55 + +🔗 https://mikrotik.com/download/changelogs/stable-release-tree + + diff --git a/doc/check-routeros-update.md b/doc/check-routeros-update.md index 926b4aa..0710b76 100644 --- a/doc/check-routeros-update.md +++ b/doc/check-routeros-update.md @@ -1,14 +1,7 @@ Notify on RouterOS update ========================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -37,7 +30,7 @@ automatically is supported. ### Sample notification -![check-routeros-update notification](check-routeros-update.d/notification.avif) +![check-routeros-update notification](check-routeros-update.d/notification.svg) Requirements and installation ----------------------------- @@ -48,33 +41,23 @@ Just install the script: And add a scheduler for automatic update notification: - /system/scheduler/add interval=1d name=check-routeros-update on-event="/system/script/run check-routeros-update;" start-time=startup; + / system scheduler add interval=1d name=check-routeros-update on-event="/ system script run check-routeros-update;" start-time=startup; Configuration ------------- -No extra configuration is required to receive notifications. Several -mechanisms are availalbe to enable automatic installation of updates. -The configuration goes to `global-config-overlay`, these are the parameters: +Configuration is required only if you want to control update process with +safe versions from a web server. The configuration goes to +`global-config-overlay`, this is the parameter: -* `SafeUpdateNeighbor`: install updates automatically if at least one other - device is seen in neighbor list with new version -* `SafeUpdateNeighborIdentity`: regular expression to match identity for - trusted devices, leave empty to match all -* `SafeUpdatePatch`: install patch updates (where just last digit changes) - automatically -* `SafeUpdateUrl`: url on webserver to check for safe update, the channel - (`long-term`, `stable` or `testing`) is appended -* `SafeUpdateAll`: install **all** updates automatically +* `SafeUpdateNeighbor`: install updates automatically if seen in neighbor list +* `SafeUpdateOnCap`: check for updates even if device is managed by CAPsMAN +* `SafeUpdatePatch`: install patch updates automatically +* `SafeUpdateUrl`: url to check for safe update, the channel (`long-term`, +`stable` or `testing`) is appended -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). Usage and invocation @@ -82,20 +65,12 @@ Usage and invocation Be notified when run from scheduler or run it manually: - /system/script/run check-routeros-update; + / system script run check-routeros-update; If an update is found you can install it right away. Installing script [packages-update](packages-update.md) gives extra options. -Tips & Tricks -------------- - -The script checks for full connectivity before acting, so scheduling at -startup is perfectly valid: - - /system/scheduler/add name=check-routeros-update@startup on-event="/system/script/run check-routeros-update;" start-time=startup; - See also -------- @@ -103,5 +78,5 @@ See also * [Manage system update](packages-update.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/collect-wireless-mac.d/notification.avif b/doc/collect-wireless-mac.d/notification.avif deleted file mode 100644 index a2833f0..0000000 Binary files a/doc/collect-wireless-mac.d/notification.avif and /dev/null differ diff --git a/doc/collect-wireless-mac.d/notification.svg b/doc/collect-wireless-mac.d/notification.svg new file mode 100644 index 0000000..fe1d20f --- /dev/null +++ b/doc/collect-wireless-mac.d/notification.svg @@ -0,0 +1,208 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] 📱 48:F1:7F:D0:E5:4E connected to Wifi + +A device with unknown MAC address connected to Wifi +on MikroTik. + +Controller: MikroTik +Interface: wl5-wifi +SSID: Wifi +MAC: 48:F1:7F:D0:E5:4E +Vendor: Intel Corporate +Hostname: host-523c8e0e +Address: 192.168.20.254 +DNS name: host-523c8e0e.dhcp.MikroTik.example.com +Date: jun/15/2021 09:21:56 + + diff --git a/doc/collect-wireless-mac.md b/doc/collect-wireless-mac.md index 0197522..5425f76 100644 --- a/doc/collect-wireless-mac.md +++ b/doc/collect-wireless-mac.md @@ -1,14 +1,7 @@ Collect MAC addresses in wireless access list ============================================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -24,24 +17,19 @@ and modify it to your needs. ### Sample notification -![collect-wireless-mac notification](collect-wireless-mac.d/notification.avif) +![collect-wireless-mac notification](collect-wireless-mac.d/notification.svg) Requirements and installation ----------------------------- -Depending on whether you use `wifi` package (`/interface/wifi`), legacy -wifi with CAPsMAN (`/caps-man`) or local wireless interface -(`/interface/wireless`) you need to install a different script. +Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless +interface (`/ interface wireless`) you need to install a different script. -For `wifi`: - - $ScriptInstallUpdate collect-wireless-mac.wifi; - -For legacy CAPsMAN: +For CAPsMAN: $ScriptInstallUpdate collect-wireless-mac.capsman; -For legacy local interface: +For local interface: $ScriptInstallUpdate collect-wireless-mac.local; @@ -52,10 +40,8 @@ On first run a disabled access list entry acting as marker (with comment "`--- collected above ---`") is added. Move this entry to define where new entries are to be added. -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). Usage and invocation @@ -73,5 +59,5 @@ See also * [Run other scripts on DHCP lease](lease-script.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/daily-psk.d/notification.avif b/doc/daily-psk.d/notification.avif deleted file mode 100644 index dd0b1b6..0000000 Binary files a/doc/daily-psk.d/notification.avif and /dev/null differ diff --git a/doc/daily-psk.d/notification.svg b/doc/daily-psk.d/notification.svg new file mode 100644 index 0000000..6c7d13a --- /dev/null +++ b/doc/daily-psk.d/notification.svg @@ -0,0 +1,204 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] 📅 daily PSK Guest-Wifi + +This is the daily PSK on MikroTik: + +SSID: Guest-Wifi +PSK: S3cr3tStr1ng +Date: jun/17/2021 + +A client device specific rule must not exist! + 🔗 https://www.eworm.de/cgi-bin/cqrlogo-wifi.cgi?scale=8&level=1&ssid=Guest-Wifi&pass=S3cr3tStr1ng + + diff --git a/doc/daily-psk.md b/doc/daily-psk.md index 4a3de64..d204691 100644 --- a/doc/daily-psk.md +++ b/doc/daily-psk.md @@ -1,14 +1,7 @@ Use wireless network with daily psk =================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -21,35 +14,28 @@ passphrase to a pseudo-random string daily. ### Sample notification -![daily-psk notification](daily-psk.d/notification.avif) +![daily-psk notification](daily-psk.d/notification.svg) Requirements and installation ----------------------------- Just install this script. -Depending on whether you use `wifi` package (`/interface/wifi`), legacy -wifi with CAPsMAN (`/caps-man`) or local wireless interface -(`/interface/wireless`) you need to install a different script and add -schedulers to run the script: +Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless +interface (`/ interface wireless`) you need to install a different script. -For `wifi`: - - $ScriptInstallUpdate daily-psk.wifi; - /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.wifi;" start-time=03:00:00; - /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.wifi;" start-time=startup; - -For legacy CAPsMAN: +For CAPsMAN: $ScriptInstallUpdate daily-psk.capsman; - /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.capsman;" start-time=03:00:00; - /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.capsman;" start-time=startup; -For legacy local interface: +For local interface: $ScriptInstallUpdate daily-psk.local; - /system/scheduler/add interval=1d name=daily-psk on-event="/system/script/run daily-psk.local;" start-time=03:00:00; - /system/scheduler/add name=daily-psk@startup on-event="/system/script/run daily-psk.local;" start-time=startup; + +And add schedulers to run the script: + + / system scheduler add interval=1d name=daily-psk-nightly on-event="/ system script run daily-psk.local;" start-date=may/23/2018 start-time=03:00:00; + / system scheduler add name=daily-psk-startup on-event="/ system script run daily-psk.local;" start-time=startup; These will update the passphrase on boot and nightly at 3:00. @@ -61,28 +47,14 @@ The configuration goes to `global-config-overlay`, these are the parameters: * `DailyPskMatchComment`: pattern to match the wireless access list comment * `DailyPskSecrets`: an array with pseudo random strings -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. +Then add an access list entry: -Then add an access list entry. For `wifi`: + / interface wireless access-list add comment="Daily PSK" interface=wl-daily private-pre-shared-key="ToBeChangedDaily"; - /interface/wifi/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" passphrase="ToBeChangedDaily"; - -For legacy CAPsMAN: - - /caps-man/access-list/add comment="Daily PSK" ssid-regexp="-guest\$" private-passphrase="ToBeChangedDaily"; - -For legacy local interface: - - /interface/wireless/access-list/add comment="Daily PSK" interface=wl-daily private-pre-shared-key="ToBeChangedDaily"; - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[trix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/dhcp-lease-comment.md b/doc/dhcp-lease-comment.md index b02f199..8679bfa 100644 --- a/doc/dhcp-lease-comment.md +++ b/doc/dhcp-lease-comment.md @@ -1,14 +1,7 @@ Comment DHCP leases with info from access list ============================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -22,19 +15,14 @@ from wireless access list. Requirements and installation ----------------------------- -Depending on whether you use `wifi` package (`/interface/wifi`), legacy -wifi with CAPsMAN (`/caps-man`) or local wireless interface -(`/interface/wireless`) you need to install a different script. +Depending on whether you use CAPsMAN (`/ caps-man`) or local wireless +interface (`/ interface wireless`) you need to install a different script. -For `wifi`: - - $ScriptInstallUpdate dhcp-lease-comment.wifi; - -For legacy CAPsMAN: +For CAPsMAN: $ScriptInstallUpdate dhcp-lease-comment.capsman; -For legacy local interface: +For local interface: $ScriptInstallUpdate dhcp-lease-comment.local; @@ -60,5 +48,5 @@ See also * [Run other scripts on DHCP lease](lease-script.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/dhcp-to-dns.md b/doc/dhcp-to-dns.md index 4211d85..245b457 100644 --- a/doc/dhcp-to-dns.md +++ b/doc/dhcp-to-dns.md @@ -1,14 +1,7 @@ Create DNS records for DHCP leases ================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.16-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -16,9 +9,7 @@ Create DNS records for DHCP leases Description ----------- -This script adds (and updates & removes) dns records based on dhcp server -leases. An A record based on mac address is created for all bound lease, -additionally a CNAME record is created from host name if available. +This script adds (and removes) dns records based on dhcp server leases. Requirements and installation ----------------------------- @@ -32,7 +23,7 @@ Then run it from dhcp server as lease script. You may want to use A scheduler cares about cleanup: - /system/scheduler/add interval=15m name=dhcp-to-dns on-event="/system/script/run dhcp-to-dns;" start-time=startup; + / system scheduler add interval=15m name=dhcp-to-dns on-event="/ system script run dhcp-to-dns;" start-time=startup; Configuration ------------- @@ -41,44 +32,12 @@ On first run a disabled static dns record acting as marker (with comment "`--- dhcp-to-dns above ---`") is added. Move this entry to define where new entries are to be added. -The configuration goes to dhcp server's network definition. The domain is -used to form the dns name: - - /ip/dhcp-server/network/add address=10.0.0.0/24 domain=example.com; - -A bound lease for mac address `00:11:22:33:44:55` with ip address -`10.0.0.50` would result in an A record `00-11-22-33-44-55.example.com` -pointing to the given ip address. - -Additional options can be given from comment, to add an extra level in -dns name or define a different domain. - - /ip/dhcp-server/network/add address=10.0.0.0/24 domain=example.com comment="domain=another-domain.com, name-extra=dhcp"; - -This example would result in name `00-11-22-33-44-55.dhcp.another-domain.com` -for the same lease. - -If no domain is found in dhcp server's network definition a fallback from -`global-config-overlay` is used. This is the parameter: +The configuration goes to `global-config-overlay`, these are the parameters: * `Domain`: the domain used for dns records - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -### Host name from DHCP lease comment - -Overwriting the host name from dhcp lease comment is supported, just add -something like `hostname=new-hostname` in comment, and separate it by comma -from other information if required: - - /ip/dhcp-server/lease/add address=10.0.0.50 comment="my device, hostname=new-hostname" mac-address=00:11:22:33:44:55 server=dhcp; - -Note this information can be configured in wireless access list with -[dhcp-lease-comment](dhcp-lease-comment.md), though it comes with a delay -then due to script execution order. Decrease the scheduler interval to -reduce the effect. +* `HostNameInZone`: whether or not to add the dhcp/dns server's hostname +* `PrefixInZone`: whether or not to add prefix `dhcp` +* `ServerNameInZone`: whether or not to add DHCP server name See also -------- @@ -89,5 +48,5 @@ See also * [Run other scripts on DHCP lease](lease-script.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/firmware-upgrade-reboot.md b/doc/firmware-upgrade-reboot.md index 54f1da0..c913d8e 100644 --- a/doc/firmware-upgrade-reboot.md +++ b/doc/firmware-upgrade-reboot.md @@ -1,14 +1,7 @@ Automatically upgrade firmware and reboot ========================================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -28,7 +21,7 @@ Requirements and installation Just install the script and create a scheduler: $ScriptInstallUpdate firmware-upgrade-reboot; - /system/scheduler/add name=firmware-upgrade-reboot on-event="/system/script/run firmware-upgrade-reboot;" start-time=startup; + / system scheduler add name=firmware-upgrade-reboot on-event="/ system script run firmware-upgrade-reboot;" start-time=startup; Enjoy firmware being up to date and in sync with RouterOS. @@ -39,5 +32,5 @@ See also * [Manage system update](packages-update.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/fw-addr-lists.md b/doc/fw-addr-lists.md deleted file mode 100644 index cb560d7..0000000 --- a/doc/fw-addr-lists.md +++ /dev/null @@ -1,137 +0,0 @@ -Download, import and update firewall address-lists -================================================== - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.16-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) - -> ℹ️ **Info**: This script can not be used on its own but requires the base -> installation. See [main README](../README.md) for details. - -Description ------------ - -This script downloads, imports and updates firewall address-lists. Its main -purpose is to block attacking ip addresses, spam hosts, command-and-control -servers and similar malicious entities. The default configuration contains a -[collective list by GitHub user @stamparm](https://github.com/stamparm/ipsum), -lists from [dshield.org](https://dshield.org/) and -[blocklist.de](https://www.blocklist.de/), and lists from -[spamhaus.org](https://spamhaus.org/) are prepared. - -The address-lists are updated in place, so after initial import you will not -see situation when the lists are not populated. - -To mitigate man-in-the-middle attacks with altered lists the server's -certificate is checked. - -> ⚠️ **Warning**: The script does not limit the size of a list, but keep in -> mind that huge lists can exhaust your device's resources (RAM and CPU), -> and may take a long time to process. - -Requirements and installation ------------------------------ - -Just install the script: - - $ScriptInstallUpdate fw-addr-lists; - -And add two schedulers, first one for initial import after startup, second -one for subsequent updates: - - /system/scheduler/add name="fw-addr-lists@startup" start-time=startup on-event="/system/script/run fw-addr-lists;"; - /system/scheduler/add name="fw-addr-lists" start-time=startup interval=2h on-event="/system/script/run fw-addr-lists;"; - -> ℹ️ **Info**: Modify the interval to your needs, but it is recommended to -> use less than half of the configured timeout for expiration. - -Configuration -------------- - -The configuration goes to `global-config-overlay`, these are the parameters: - -* `FwAddrLists`: a list of firewall address-lists to download and import -* `FwAddrListTimeOut`: the timeout for expiration without renew - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Naming a certificate for a list makes the script verify the server -certificate, so you should add that if possible. You may want to find the -[certificate name from browser](../CERTIFICATES.md). - -Create firewall rules to process the packets that are related to addresses -from address-lists. - -### IPv4 rules - -This rejects the packets from and to IPv4 addresses listed in -address-list `block`. - - /ip/firewall/filter/add chain=input src-address-list=block action=reject reject-with=icmp-admin-prohibited; - /ip/firewall/filter/add chain=forward src-address-list=block action=reject reject-with=icmp-admin-prohibited; - /ip/firewall/filter/add chain=forward dst-address-list=block action=reject reject-with=icmp-admin-prohibited; - /ip/firewall/filter/add chain=output dst-address-list=block action=reject reject-with=icmp-admin-prohibited; - -You may want to have an address-list to allow specific addresses, as prepared -with a list `allow`. In fact you can use any list name, just change the -default ones or add your own - matching in configuration and firewall rules. - - /ip/firewall/filter/add chain=input src-address-list=allow action=accept; - /ip/firewall/filter/add chain=forward src-address-list=allow action=accept; - /ip/firewall/filter/add chain=forward dst-address-list=allow action=accept; - /ip/firewall/filter/add chain=output dst-address-list=allow action=accept; - -Modify these for your needs, but **most important**: Move the rules up in -chains and make sure they actually take effect as expected! - -Alternatively handle the packets in firewall's raw section if you prefer: - - /ip/firewall/raw/add chain=prerouting src-address-list=block action=drop; - /ip/firewall/raw/add chain=prerouting dst-address-list=block action=drop; - /ip/firewall/raw/add chain=output dst-address-list=block action=drop; - -> ⚠️ **Warning**: Just again... The order of firewall rules is important. Make -> sure they actually take effect as expected! - -### IPv6 rules - -These are the same rules, but for IPv6. - -Reject packets in address-list `block`: - - /ipv6/firewall/filter/add chain=input src-address-list=block action=reject reject-with=icmp-admin-prohibited; - /ipv6/firewall/filter/add chain=forward src-address-list=block action=reject reject-with=icmp-admin-prohibited; - /ipv6/firewall/filter/add chain=forward dst-address-list=block action=reject reject-with=icmp-admin-prohibited; - /ipv6/firewall/filter/add chain=output dst-address-list=block action=reject reject-with=icmp-admin-prohibited; - -Allow packets in address-list `allow`: - - /ipv6/firewall/filter/add chain=input src-address-list=allow action=accept; - /ipv6/firewall/filter/add chain=forward src-address-list=allow action=accept; - /ipv6/firewall/filter/add chain=forward dst-address-list=allow action=accept; - /ipv6/firewall/filter/add chain=output dst-address-list=allow action=accept; - -Drop packets in firewall's raw section: - - /ipv6/firewall/raw/add chain=prerouting src-address-list=block action=drop; - /ipv6/firewall/raw/add chain=prerouting dst-address-list=block action=drop; - /ipv6/firewall/raw/add chain=output dst-address-list=block action=drop; - -> ⚠️ **Warning**: Just again... The order of firewall rules is important. Make -> sure they actually take effect as expected! - -See also --------- - -* [Certificate name from browser](../CERTIFICATES.md) - ---- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) diff --git a/doc/global-wait.md b/doc/global-wait.md index 799cae7..2a7fbfa 100644 --- a/doc/global-wait.md +++ b/doc/global-wait.md @@ -1,14 +1,7 @@ Wait for global functions and modules ===================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -34,7 +27,7 @@ Just install the script: ... and add it to your scheduler, for example in combination with the module to [manage VLANs on bridge ports](mod/bridge-port-vlan.md): - /system/scheduler/add name=bridge-port-vlan on-event="/system/script/run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup; + / system scheduler add name=bridge-port-vlan on-event="/ system script run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup; See also -------- @@ -43,5 +36,5 @@ See also * [Manage VLANs on bridge ports](mod/bridge-port-vlan.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/gps-track.md b/doc/gps-track.md index 5e4878f..3b9c94f 100644 --- a/doc/gps-track.md +++ b/doc/gps-track.md @@ -1,14 +1,7 @@ Send GPS position to server =========================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -30,7 +23,7 @@ Just install the script: ... and create a scheduler: - /system/scheduler/add interval=1m name=gps-track on-event="/system/script/run gps-track;" start-time=startup; + / system scheduler add interval=1m name=gps-track on-event="/ system script run gps-track;" start-time=startup; Configuration ------------- @@ -39,13 +32,9 @@ The configuration goes to `global-config-overlay`, the only parameter is: * `GpsTrackUrl`: the url to send json data to -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -The configured coordinate format (see `/system/gps`) defines the format +The configured coordinate format (see `/ system gps`) defines the format sent to the server. --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/hotspot-to-wpa.md b/doc/hotspot-to-wpa.md index a2e9748..8733a7c 100644 --- a/doc/hotspot-to-wpa.md +++ b/doc/hotspot-to-wpa.md @@ -1,14 +1,7 @@ -Use WPA network with hotspot credentials -======================================== +Use WPA2 network with hotspot credentials +========================================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -17,56 +10,37 @@ Description ----------- RouterOS supports an unlimited number of MAC address specific passphrases -for WPA encrypted wifi networks via access list. The idea of this script -is to transfer hotspot credentials to MAC address specific WPA passphrase. +for WPA2 encrypted wifi networks via access list. The idea of this script +is to transfer hotspot credentials to MAC address specific WPA2 passphrase. Requirements and installation ----------------------------- -You need a properly configured hotspot on one (open) SSID and a WPA enabled +You need a properly configured hotspot on one (open) SSID and a WP2 enabled SSID with suffix "`-wpa`". -Then install the script. -Depending on whether you use `wifi` package (`/interface/wifi`)or legacy -wifi with CAPsMAN (`/caps-man`) you need to install a different script and -set it as `on-login` script in hotspot. +Then install the script: -For `wifi`: + $ScriptInstallUpdate hotspot-to-wpa; - $ScriptInstallUpdate hotspot-to-wpa.wifi; - /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.wifi" [ find ]; +Configure your hotspot to use this script as `on-login` script: -For legacy CAPsMAN: - - $ScriptInstallUpdate hotspot-to-wpa.capsman; - /ip/hotspot/user/profile/set on-login="hotspot-to-wpa.capsman" [ find ]; + / ip hotspot user profile set on-login=hotspot-to-wpa [ find ]; ### Automatic cleanup With just `hotspot-to-wpa` installed the mac addresses will last in the -access list forever. Install the optional script for automatic cleanup -and add a scheduler. +access list forever. Install the optional script for automatic cleanup: -For `wifi`: + $ScriptInstallUpdate hotspot-to-wpa-cleanup,lease-script; - $ScriptInstallUpdate hotspot-to-wpa-cleanup.wifi,lease-script; - /system/scheduler/add interval=1d name=hotspot-to-wpa-cleanup on-event="/system/script/run hotspot-to-wpa-cleanup.wifi;" start-time=startup; +Create a scheduler: -For legacy CAPsMAN: + / system scheduler add interval=1d name=hotspot-to-wpa-cleanup on-event="/ system script run hotspot-to-wpa-cleanup;" start-time=startup; - $ScriptInstallUpdate hotspot-to-wpa-cleanup.capsman,lease-script; - /system/scheduler/add interval=1d name=hotspot-to-wpa-cleanup on-event="/system/script/run hotspot-to-wpa-cleanup.capsman;" start-time=startup; +And add the lease script to your wpa interfaces' dhcp server: -And add the lease script and matcher comment to your wpa interfaces' dhcp -server. You can add more information to the comment, separated by comma. In -this example the server is called `hotspot-to-wpa`. - - /ip/dhcp-server/set lease-script=lease-script comment="hotspot-to-wpa=wpa" hotspot-to-wpa; - -You can specify the timeout after which a device is removed from leases and -access-list. The default is four weeks. - - /ip/dhcp-server/set lease-script=lease-script comment="hotspot-to-wpa=wpa, timeout=2w" hotspot-to-wpa; + / ip dhcp-server set lease-script=lease-script [ find where name~"wpa" ]; Configuration ------------- @@ -77,41 +51,33 @@ entries are to be added. Create hotspot login credentials: - /ip/hotspot/user/add comment="Test User 1" name=user1 password=v3ry; - /ip/hotspot/user/add comment="Test User 2" name=user2 password=s3cr3t; - -This also works with authentication via radius, but is limited then: -Additional information is not available, including the password. + / ip hotspot user add add comment="Test User 1" name=user1 password=v3ry; + / ip hotspot user add add comment="Test User 2" name=user2 password=s3cr3t; Additionally templates can be created to give more options for access list: * `action`: set to `reject` to ignore logins on that hotspot -* `passphrase` or `private-passphrase`: do **not** use passphrase from - hotspot's user credentials, but given one - or unset (use default - passphrase) with special word `ignore` +* `private-passphrase`: do **not** use passphrase from hotspot's user + credentials, but given one - or unset (use default passphrase) with + special word `ignore` * `ssid-regexp`: set a different SSID regular expression to match * `vlan-id`: connect device to specific VLAN * `vlan-mode`: set the VLAN mode for device -For a hotspot called `example` the template could look like this. -For `wifi`: +For a hotspot called `example` the template could look like this: - /interface/wifi/access-list/add comment="hotspot-to-wpa template example" disabled=yes passphrase="ignore" ssid-regexp="^example\$" vlan-id=10; - -For legacy CAPsMAN: - - /caps-man/access-list/add comment="hotspot-to-wpa template example" disabled=yes private-passphrase="ignore" ssid-regexp="^example\$" vlan-id=10 vlan-mode=use-tag; + / caps-man access-list add comment="hotspot-to-wpa template example" disabled=yes private-passphrase="ignore" ssid-regexp="^example\$" vlan-id=10 vlan-mode=use-tag; The same settings are available in hotspot user's comment and take precedence over the template settings: - /ip/hotspot/user/add comment="private-passphrase=ignore, ssid-regexp=^example\\\$, vlan-id=10, vlan-mode=use-tag" name=user password=v3ry-s3cr3t; + / ip hotspot user add comment="private-passphrase=ignore, ssid-regexp=^example\\\$, vlan-id=10, vlan-mode=use-tag" name=user password=v3ry-s3cr3t; Usage and invocation -------------------- Now let the users connect and login to the hotspot. After that the devices -(identified by MAC address) can connect to the WPA network, using the +(identified by MAC address) can connect to the WPA2 network, using the passphrase from hotspot credentials. See also @@ -120,5 +86,5 @@ See also * [Run other scripts on DHCP lease](lease-script.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/ip-addr-bridge.md b/doc/ip-addr-bridge.md index f9f98e3..44dac6a 100644 --- a/doc/ip-addr-bridge.md +++ b/doc/ip-addr-bridge.md @@ -1,14 +1,7 @@ Manage IP addresses with bridge status ====================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) Description ----------- @@ -26,14 +19,14 @@ Just install the script: ... and make it run from scheduler: - /system/scheduler/add name=ip-addr-bridge on-event="/system/script/run ip-addr-bridge;" start-time=startup; + / system scheduler add name=ip-addr-bridge on-event="/ system script run ip-addr-bridge;" start-time=startup; -This will disable IP addresses on bridges without at least one running port. +This will disable IP addresses on bridges without at lease one running port. The IP address is enabled if at least one port is running. Note that IP addresses on bridges without a single port (acting as loopback interface) are ignored. --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/ipsec-to-dns.md b/doc/ipsec-to-dns.md index 123656c..349ae63 100644 --- a/doc/ipsec-to-dns.md +++ b/doc/ipsec-to-dns.md @@ -1,14 +1,7 @@ Create DNS records for IPSec peers ================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -28,7 +21,7 @@ Just install the script: This script is run from scheduler: - /system/scheduler/add interval=1m name=ipsec-to-dns on-event="/system/script/run ipsec-to-dns;" start-time=startup; + / system scheduler add interval=1m name=ipsec-to-dns on-event="/ system script run ipsec-to-dns;" start-time=startup; Configuration ------------- @@ -43,15 +36,11 @@ The configuration goes to `global-config-overlay`, these are the parameters: * `HostNameInZone`: whether or not to add the ipsec/dns server's hostname * `PrefixInZone`: whether or not to add prefix `ipsec` -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - See also -------- * [Create DNS records for DHCP leases](dns-to-dhcp.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/ipv6-update.md b/doc/ipv6-update.md index 1f009b1..ae4eb6a 100644 --- a/doc/ipv6-update.md +++ b/doc/ipv6-update.md @@ -1,14 +1,7 @@ Update configuration on IPv6 prefix change ========================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -18,7 +11,7 @@ Description With changing IPv6 prefix from ISP this script handles to update... -* ipv6 firewall address-list (prefixes (`/64`) and host addresses (`/128`)) +* ipv6 firewall address-list * dns records Requirements and installation @@ -30,14 +23,14 @@ Just install the script: Your ISP needs to provide an IPv6 prefix, your device receives it via dhcp: - /ipv6/dhcp-client/add add-default-route=yes interface=ppp-isp pool-name=isp request=prefix script=ipv6-update; + / ipv6 dhcp-client add add-default-route=yes interface=ppp-isp pool-name=isp request=prefix script=ipv6-update; Note this already adds this script as `script`. The pool name (here: "`isp`") is important, we need it later. Also this expects there is an address assigned from pool to an interface: - /ipv6/address/add from-pool=isp interface=br-local; + / ipv6 address add from-pool=isp interface=br-local; Sometimes dhcp client is stuck on reconnect and needs to be released. Installing [ppp-on-up](ppp-on-up.md) may solve this. @@ -45,34 +38,25 @@ Installing [ppp-on-up](ppp-on-up.md) may solve this. Configuration ------------- -As an address-list entry is mandatory a dynamic one is created automatically. -It is updated with current prefix and can be used in firewall rules. +An address list entry is updated with current prefix and can be used in +firewall rules, comment has to be "`ipv6-pool-`" and actual pool name: -Alternatively a static address-list entry can be used, where comment has to -be "`ipv6-pool-`" and actual pool name. Use what ever list is desired, and -create it with: + / ipv6 firewall address-list add address=2003:cf:2f0f:de00::/56 comment=ipv6-pool-isp list=extern; - /ipv6/firewall/address-list/add address=2003:cf:2f0f:de00::/56 comment=ipv6-pool-isp list=extern; - -If the dynamic entry exists already you need to remove it before creating -the static one.. +As this entry is mandatory it is created automatically if it does not exist, +with the comment also set for list. Address list entries for specific interfaces can be updated as well. The interface needs to get its address from pool `isp` and the address list entry has to be associated to an interface in comment: - /ipv6/firewall/address-list/add address=2003:cf:2f0f:de01::/64 comment="ipv6-pool-isp, interface=br-local" list=local; - -Updating address list entries with host addresses works as well, the new -prefix is combinded with given suffix then: - - /ipv6/firewall/address-list/add address=2003:cf:2f0f:de01:e3e0:f8fa:8cd6:dbe1/128 comment="ipv6-pool-isp, interface=br-local" list=hosts; + / ipv6 firewall address-list add address=2003:cf:2f0f:de01::/64 comment="ipv6-pool-isp, interface=br-local" list=local; Static DNS records need a special comment to be updated. Again it has to start with "`ipv6-pool-`" and actual pool name, followed by a comma, "`interface=`" and the name of interface this address is connected to: - /ip/dns/static/add address=2003:cf:2f0f:de00:1122:3344:5566:7788 comment="ipv6-pool-isp, interface=br-local" name=test.example.com ttl=15m; + / ip dns static add address=2003:cf:2f0f:de00:1122:3344:5566:7788 comment="ipv6-pool-isp, interface=br-local" name=test.example.com ttl=15m; See also -------- @@ -80,5 +64,5 @@ See also * [Run scripts on ppp connection](ppp-on-up.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/lease-script.md b/doc/lease-script.md index f83c383..16fc73e 100644 --- a/doc/lease-script.md +++ b/doc/lease-script.md @@ -1,14 +1,7 @@ Run other scripts on DHCP lease =============================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -39,7 +32,7 @@ Just install the script: ... and add it as `lease-script` to your dhcp server: - /ip/dhcp-server/set lease-script=lease-script [ find ]; + / ip dhcp-server set lease-script=lease-script [ find ]; See also -------- @@ -47,8 +40,8 @@ See also * [Collect MAC addresses in wireless access list](collect-wireless-mac.md) * [Comment DHCP leases with info from access list](dhcp-lease-comment.md) * [Create DNS records for DHCP leases](dhcp-to-dns.md) -* [Use WPA network with hotspot credentials](hotspot-to-wpa.md) +* [Use WPA2 network with hotspot credentials](hotspot-to-wpa.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/leds-mode.md b/doc/leds-mode.md index a194396..b525220 100644 --- a/doc/leds-mode.md +++ b/doc/leds-mode.md @@ -1,14 +1,7 @@ Manage LEDs dark mode ===================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) Description ----------- @@ -28,21 +21,21 @@ Usage and invocation To switch the device to dark mode: - /system/script/run leds-night-mode; + / system script run leds-night-mode; ... and back to normal mode: - /system/script/run leds-day-mode; + / system script run leds-day-mode; To toggle between the two modes: - /system/script/run leds-toggle-mode; + / system script run leds-toggle-mode; Add these schedulers to switch to dark mode in the evening and back to normal mode in the morning: - /system/scheduler/add interval=1d name=leds-day-mode on-event="/system/script/run leds-day-mode;" start-time=07:00:00; - /system/scheduler/add interval=1d name=leds-night-mode on-event="/system/script/run leds-night-mode;" start-time=21:00:00; + / system scheduler add interval=1d name=leds-day-mode on-event="/ system script run leds-day-mode;" start-time=07:00:00; + / system scheduler add interval=1d name=leds-night-mode on-event="/ system script run leds-night-mode;" start-time=21:00:00; The script `leds-toggle-mode` can be used from [mode button](mode-button.md) to toggle mode. @@ -53,5 +46,5 @@ See also * [Mode button with multiple presses](mode-button.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/log-forward.d/notification.avif b/doc/log-forward.d/notification.avif deleted file mode 100644 index a0f9ab3..0000000 Binary files a/doc/log-forward.d/notification.avif and /dev/null differ diff --git a/doc/log-forward.d/notification.svg b/doc/log-forward.d/notification.svg new file mode 100644 index 0000000..373144b --- /dev/null +++ b/doc/log-forward.d/notification.svg @@ -0,0 +1,192 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ⚠ Log Forwarding + +The log on MikroTik contains these 3 messages after 6d23:55:18 uptime. + + ● 13:24:02 script;error backup-cloud: Failed uploading backup for MikroTik to cloud! + ● 13:24:17 system;info;account user admin logged in from 192.168.88.177 via ssh + ● 13:24:57 system;info;account user admin logged out from 192.168.88.177 via ssh + + diff --git a/doc/log-forward.md b/doc/log-forward.md index 3c19569..1f3eae5 100644 --- a/doc/log-forward.md +++ b/doc/log-forward.md @@ -1,14 +1,7 @@ Forward log messages via notification ===================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -16,28 +9,20 @@ Forward log messages via notification Description ----------- -RouterOS itself supports sending log messages via e-mail or to a syslog -server (see `/system/logging`). This has some limitation, however: +RouterOS supports sending log messages via e-mail or to a syslog server. +This has some limitation, however: * does not work early after boot if network connectivity is not - yet established, or breaks intermittently + yet established * lots of messages generate a flood of mails -* Matrix, Ntfy and Telegram are not supported +* Matrix and Telegram are not supported -The script works around the limitations, for example it does: - -* read from `/log`, including messages from early boot -* skip multi-repeated messages -* rate-limit itself to mitigate flooding -* forward via notification (which includes *e-mail*, *Matrix*, *Ntfy* and - *Telegram* when installed and configured, see below) - -It is intended to be run periodically from scheduler, then collects new -log messages and forwards them via notification. +The script is intended to be run periodically. It collects log messages +and forwards them via notification. ### Sample notification -![log-forward notification](log-forward.d/notification.avif) +![log-forward notification](log-forward.d/notification.svg) Requirements and installation ----------------------------- @@ -48,17 +33,11 @@ Just install the script: ... and add a scheduler: - /system/scheduler/add interval=1m name=log-forward on-event="/system/script/run log-forward;" start-time=startup; + / system scheduler add interval=1m name=log-forward on-event="/ system script run log-forward;" start-time=startup; Configuration ------------- -The default configuration should provide reasonable presets, filtering -*info*, and effectively forwarding *warning* and *error*. - -> 💡️ **Hint**: Please try with defaults first, especially if you are not -> familiar with regular expressions! - The configuration goes to `global-config-overlay`, these are the parameters: * `LogForwardFilter`: define topics *not* to be forwarded @@ -67,35 +46,10 @@ The configuration goes to `global-config-overlay`, these are the parameters: * `LogForwardIncludeMessage`: define message text to be forwarded (even if filter matches) -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -These patterns are matched as -[regular expressions](https://wiki.mikrotik.com/wiki/Manual:Regular_Expressions). -To forward **all** (ignoring severity) log messages with topics `account` -(which includes user logins) and `dhcp` you need something like: - - :global LogForwardInclude "(account|dhcp)"; - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or [telegram](mod/notification-telegram.md). -Tips & Tricks -------------- - -### Notification on reboot - -You want to receive a notification on every device (re-)boot? Quite easy, -just add: - - :global LogForwardIncludeMessage "(^router rebooted)"; - -This will match on every log message beginning with `router rebooted`. - --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/bridge-port-to.md b/doc/mod/bridge-port-to.md index 629c526..02d1e8d 100644 --- a/doc/mod/bridge-port-to.md +++ b/doc/mod/bridge-port-to.md @@ -1,14 +1,7 @@ Manage ports in bridge ====================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) +[◀ Go back to main README](../../README.md) > ℹ️️ **Info**: This module can not be used on its own but requires the base > installation. See [main README](../../README.md) for details. @@ -29,20 +22,20 @@ Just install the module: Configuration ------------- -The configuration goes to ports' comments (`/interface/bridge/port`). +The configuration goes to ports' comments (`/ interface bridge port`). - /interface/bridge/port/add bridge=br-guest comment="default=dhcp-client, alt=br-guest" disabled=yes interface=en1; - /interface/bridge/port/add bridge=br-intern comment="default=br-intern, alt=br-guest" interface=en2; - /interface/bridge/port/add bridge=br-guest comment="default=br-guest, extra=br-extra" interface=en3; + / interface bridge port add bridge=br-guest comment="default=dhcp-client, alt=br-guest" disabled=yes interface=en1; + / interface bridge port add bridge=br-intern comment="default=br-intern, alt=br-guest" interface=en2; + / interface bridge port add bridge=br-guest comment="default=br-guest, extra=br-extra" interface=en3; Also dhcp client can be handled: - /ip/dhcp-client/add comment="toggle with bridge port" disabled=no interface=en1; + / ip dhcp-client add comment="toggle with bridge port" disabled=no interface=en1; Add a scheduler to start with default setup on system startup: $ScriptInstallUpdate global-wait; - /system/scheduler/add name=bridge-port-to on-event="/system/script/run global-wait; :global BridgePortTo; \$BridgePortTo default;" start-time=startup; + / system scheduler add name=bridge-port-vlan on-event="/ system script run global-wait; :global BridgePortTo; \$BridgePortTo default;" start-time=startup; Usage and invocation -------------------- @@ -84,5 +77,5 @@ See also * [Manage VLANs on bridge ports](bridge-port-vlan.md) --- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/bridge-port-vlan.md b/doc/mod/bridge-port-vlan.md index cf29199..290826e 100644 --- a/doc/mod/bridge-port-vlan.md +++ b/doc/mod/bridge-port-vlan.md @@ -1,14 +1,7 @@ Manage VLANs on bridge ports ============================ -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) +[◀ Go back to main README](../../README.md) > ℹ️️ **Info**: This module can not be used on its own but requires the base > installation. See [main README](../../README.md) for details. @@ -30,24 +23,24 @@ Configuration Using named VLANs you have to add comments in bridge vlan menu: - /interface/bridge/vlan/add bridge=bridge comment=intern tagged=br-local vlan-ids=10; - /interface/bridge/vlan/add bridge=bridge comment=geust tagged=br-local vlan-ids=20; - /interface/bridge/vlan/add bridge=bridge comment=extra tagged=br-local vlan-ids=30; + / interface bridge vlan add bridge=bridge comment=intern tagged=br-local vlan-ids=10; + / interface bridge vlan add bridge=bridge comment=geust tagged=br-local vlan-ids=20; + / interface bridge vlan add bridge=bridge comment=extra tagged=br-local vlan-ids=30; -The configuration goes to ports' comments (`/interface/bridge/port`). +The configuration goes to ports' comments (`/ interface bridge port`). - /interface/bridge/port/add bridge=bridge comment="default=dhcp-client, alt=guest" disabled=yes interface=en1; - /interface/bridge/port/add bridge=bridge comment="default=intern, alt=guest, extra=30" interface=en2; - /interface/bridge/port/add bridge=bridge comment="default=guest, extra=extra" interface=en3; + / interface bridge port add bridge=bridge comment="default=dhcp-client, alt=guest" disabled=yes interface=en1; + / interface bridge port add bridge=bridge comment="default=intern, alt=guest, extra=30" interface=en2; + / interface bridge port add bridge=bridge comment="default=guest, extra=extra" interface=en3; Also dhcp client can be handled: - /ip/dhcp-client/add comment="toggle with bridge port" disabled=no interface=en1; + / ip dhcp-client add comment="toggle with bridge port" disabled=no interface=en1; Add a scheduler to start with default setup on system startup: $ScriptInstallUpdate global-wait; - /system/scheduler/add name=bridge-port-vlan on-event="/system/script/run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup; + / system scheduler add name=bridge-port-vlan on-event="/ system script run global-wait; :global BridgePortVlan; \$BridgePortVlan default;" start-time=startup; Usage and invocation -------------------- @@ -88,5 +81,5 @@ See also * [Manage ports in bridge](bridge-port-to.md) --- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/inspectvar.d/inspectvar.avif b/doc/mod/inspectvar.d/inspectvar.avif index f1da1d4..d4a745f 100644 Binary files a/doc/mod/inspectvar.d/inspectvar.avif and b/doc/mod/inspectvar.d/inspectvar.avif differ diff --git a/doc/mod/inspectvar.md b/doc/mod/inspectvar.md index 7daba15..d3fb3b2 100644 --- a/doc/mod/inspectvar.md +++ b/doc/mod/inspectvar.md @@ -1,14 +1,7 @@ Inspect variables ================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) +[◀ Go back to main README](../../README.md) > ℹ️️ **Info**: This module can not be used on its own but requires the base > installation. See [main README](../../README.md) for details. @@ -31,10 +24,10 @@ Usage and invocation Call the function `$InspectVar` with a variable as parameter: - $InspectVar $ModeButton; + $InspectVar $ModeButton ![InspectVar](inspectvar.d/inspectvar.avif) --- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/ipcalc.d/ipcalc.avif b/doc/mod/ipcalc.d/ipcalc.avif index fe726e8..022f325 100644 Binary files a/doc/mod/ipcalc.d/ipcalc.avif and b/doc/mod/ipcalc.d/ipcalc.avif differ diff --git a/doc/mod/ipcalc.d/ipcalcreturn.avif b/doc/mod/ipcalc.d/ipcalcreturn.avif index 5e4dd57..d858bb1 100644 Binary files a/doc/mod/ipcalc.d/ipcalcreturn.avif and b/doc/mod/ipcalc.d/ipcalcreturn.avif differ diff --git a/doc/mod/ipcalc.md b/doc/mod/ipcalc.md index c07853e..a3e7fc8 100644 --- a/doc/mod/ipcalc.md +++ b/doc/mod/ipcalc.md @@ -1,14 +1,7 @@ IP address calculation ====================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) +[◀ Go back to main README](../../README.md) > ℹ️️ **Info**: This module can not be used on its own but requires the base > installation. See [main README](../../README.md) for details. @@ -56,5 +49,5 @@ the information in a named array. ![IPCalcReturn](ipcalc.d/ipcalcreturn.avif) --- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/notification-email.md b/doc/mod/notification-email.md deleted file mode 100644 index 34d1c09..0000000 --- a/doc/mod/notification-email.md +++ /dev/null @@ -1,88 +0,0 @@ -Send notifications via e-mail -============================= - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) - -> ℹ️️ **Info**: This module can not be used on its own but requires the base -> installation. See [main README](../../README.md) for details. - -Description ------------ - -This module adds support for sending notifications via e-mail. A queue is -used to make sure notifications are not lost on failure but sent later. - -Requirements and installation ------------------------------ - -Just install the module: - - $ScriptInstallUpdate mod/notification-email; - -Also you need a valid e-mail account with smtp login credentials. - -Configuration -------------- - -Set up your device's -[e-mail settings](https://wiki.mikrotik.com/wiki/Manual:Tools/email). -Also make sure the device has correct time configured, best is to set up -the ntp client. - -Then edit `global-config-overlay`, add `EmailGeneralTo` with a valid -recipient address. Finally reload the configuration. - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -### Sending to several recipients - -Sending notifications to several recipients is possible as well. Add -`EmailGeneralCc` on top, which can have a single mail address or a comma -separated list. - -Usage and invocation --------------------- - -There's nothing special to do. Every script or function sending a notification -will now send it to your e-mail account. - -But of course you can use the function to send notifications directly. Give -it a try: - - $SendEMail "Subject..." "Body..."; - -Alternatively this sends a notification with all available and configured -methods: - - $SendNotification "Subject..." "Body..."; - -To use the functions in your own scripts you have to declare them first. -Place this before you call them: - - :global SendEMail; - :global SendNotification; - -In case there is a situation when the queue needs to be purged there is a -function available: - - $PurgeEMailQueue; - -See also --------- - -* [Send notifications via Matrix](notification-matrix.md) -* [Send notifications via Ntfy](notification-ntfy.md) -* [Send notifications via Telegram](notification-telegram.md) - ---- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) diff --git a/doc/mod/notification-matrix.d/01-authenticate.avif b/doc/mod/notification-matrix.d/01-authenticate.avif deleted file mode 100644 index b897943..0000000 Binary files a/doc/mod/notification-matrix.d/01-authenticate.avif and /dev/null differ diff --git a/doc/mod/notification-matrix.d/01-home-server.avif b/doc/mod/notification-matrix.d/01-home-server.avif new file mode 100644 index 0000000..8a79ae6 Binary files /dev/null and b/doc/mod/notification-matrix.d/01-home-server.avif differ diff --git a/doc/mod/notification-matrix.d/02-access-token.avif b/doc/mod/notification-matrix.d/02-access-token.avif new file mode 100644 index 0000000..8a0b647 Binary files /dev/null and b/doc/mod/notification-matrix.d/02-access-token.avif differ diff --git a/doc/mod/notification-matrix.d/02-join-room.avif b/doc/mod/notification-matrix.d/02-join-room.avif deleted file mode 100644 index ad99ffd..0000000 Binary files a/doc/mod/notification-matrix.d/02-join-room.avif and /dev/null differ diff --git a/doc/mod/notification-matrix.d/03-join-room.avif b/doc/mod/notification-matrix.d/03-join-room.avif new file mode 100644 index 0000000..c7fad4e Binary files /dev/null and b/doc/mod/notification-matrix.d/03-join-room.avif differ diff --git a/doc/mod/notification-matrix.md b/doc/mod/notification-matrix.md index 89c1b01..91a39e5 100644 --- a/doc/mod/notification-matrix.md +++ b/doc/mod/notification-matrix.md @@ -1,14 +1,7 @@ Send notifications via Matrix ============================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) +[◀ Go back to main README](../../README.md) > ℹ️️ **Info**: This module can not be used on its own but requires the base > installation. See [main README](../../README.md) for details. @@ -28,8 +21,8 @@ Just install the module: $ScriptInstallUpdate mod/notification-matrix; Also install a Matrix client on at least one of your mobile and/or desktop -devices. Create and setup an account there, we will reference that as -"*general account*" later. +devices. As there is no privilege separation you should create a dedicated +notification account, in addition to your general user account. Configuration ------------- @@ -38,66 +31,58 @@ Edit `global-config-overlay`, add `MatrixHomeServer`, `MatrixAccessToken` and `MatrixRoom` - see below on hints how to retrieve this information. Then reload the configuration. -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -The Matrix server is connected via encrypted https, and certificate -verification is applied. So make sure you have the certificate chain for -your server in device's certificate store. - -The example below is for `matrix.org`, which uses a trust chain from *Google -Trust Services*. Run this to import the required certificate: - - $CertificateAvailable "GTS Root R4"; - -Replace the CA certificate name with what ever is needed for your server. -You may want to find the -[certificate name from browser](../../CERTIFICATES.md). - -### From other device - -If you have setup your Matrix *notification account* before just reuse that. -Copy the relevant configuration to the device to be configured. - -### Setup new account - -As there is no privilege separation you should create a dedicated account -for use with these scripts, in addition to your *general account*. -We will reference that as "*notification account*" in the following steps. - -#### Authenticate +### Home server Matrix user accounts are identified by a unique user id in the form of -`@localpart:domain`. Use that and your password to generate an access token -and write first part of the configuration: +`@localpart:domain`. The `domain` part is not necessarily your home server +address, you have to resolve it with the procedure described in the +[Matrix specification](https://spec.matrix.org/latest/client-server-api/#server-discovery). - $SetupMatrixAuthenticate "@example:matrix.org" "v3ry-s3cr3t"; +Your best bet is to query the server at `domain` with the +[well-known uri](https://spec.matrix.org/latest/client-server-api/#well-known-uri). +For "*matrix.org*" this query is: -![authenticate](notification-matrix.d/01-authenticate.avif) + / tool fetch "https://matrix.org/.well-known/matrix/client" output=user; -The configuration is written to a new configuration snippet -`global-config-overlay.d/mod/notification-matrix`. +![home server](notification-matrix.d/01-home-server.avif) -#### Join Room +So the home server for "*matrix.org*" is "*matrix-client.matrix.org*". +Please strip the protocol ("*https://*") for `MatrixHomeServer` if given. -Every Matix chat is a room, so we have to create one. Do that with your -*general account*, this makes sure your *general account* is the room owner. -Then join the room and invite the *notification account* by its user id -"*@example:matrix.org*". -Look up the *room id* within the Matrix client, it should read like -"*!WUcxpSjKyxSGelouhA:matrix.org*" (starting with an exclamation mark and -ending with the domain). +### Access token -Finally make the *notification account* join into the room by accepting -the invite. +After discovering the correct home server an access token has to be created. +For this the login credentials (username and password) of the notification +account must be sent to the home server via +[client server api](https://matrix.org/docs/guides/client-server-api#login). - $SetupMatrixJoinRoom "!WUcxpSjKyxSGelouhA:matrix.org"; +We use the home server discovered above, "*matrix-client.matrix.org*". +The user is "*example*" and password is "*v3ry-s3cr3t*". -![join room](notification-matrix.d/02-join-room.avif) + / tool fetch "https://matrix-client.matrix.org/_matrix/client/r0/login" http-method=post http-data="{\"type\":\"m.login.password\", \"user\":\"example\", \"password\":\"v3ry-s3cr3t\"}" output=user; -The configuration is appended to the configuration snippet -`global-config-overlay.d/mod/notification-matrix`. +![access token](notification-matrix.d/02-access-token.avif) + +The server replied with a JSON object containing the `access_token`, use that +for `MatrixAccessToken`. + +### Room + +Every Matix chat is a room, so we have to create one. Do so with your general +user, this makes sure your general user is the room owner. Then join the room +and invite the notification user by its user id "*@example:matrix.org*". Look +up the room id within the Matrix client, it should read like +"*!WUcxpSjKyxSGelouhA:matrix.org*". Use that for `MatrixRoom`. + +Finally join the notification user to the room by accepting the invite. Again, +this can be done with +[client server api](https://matrix.org/docs/guides/client-server-api#joining-a-room-via-an-invite). +Make sure to replace room id ("*!*" is escaped with "*%21*") and access token +with your data. + + / tool fetch "https://matrix-client.matrix.org/_matrix/client/r0/rooms/%21WUcxpSjKyxSGelouhA:matrix.org/join?access_token=yt_ZXdvcm0tdGVzdA_NNqUyvKHRhBLZmnzVVSK_0xu6yN" http-method=post http-data="" output=user; + +![join room](notification-matrix.d/03-join-room.avif) Usage and invocation -------------------- @@ -105,35 +90,11 @@ Usage and invocation There's nothing special to do. Every script or function sending a notification will now send it to your Matrix account. -But of course you can use the function to send notifications directly. Give -it a try: - - $SendMatrix "Subject..." "Body..."; - -Alternatively this sends a notification with all available and configured -methods: - - $SendNotification "Subject..." "Body..."; - -To use the functions in your own scripts you have to declare them first. -Place this before you call them: - - :global SendMatrix; - :global SendNotification; - -In case there is a situation when the queue needs to be purged there is a -function available: - - $PurgeMatrixQueue; - See also -------- -* [Certificate name from browser](../../CERTIFICATES.md) -* [Send notifications via e-mail](notification-email.md) -* [Send notifications via Ntfy](notification-ntfy.md) * [Send notifications via Telegram](notification-telegram.md) --- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/notification-ntfy.md b/doc/mod/notification-ntfy.md deleted file mode 100644 index 51756ac..0000000 --- a/doc/mod/notification-ntfy.md +++ /dev/null @@ -1,98 +0,0 @@ -Send notifications via Ntfy -=========================== - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) - -> ℹ️️ **Info**: This module can not be used on its own but requires the base -> installation. See [main README](../../README.md) for details. - -Description ------------ - -This module adds support for sending notifications via -[Ntfy](https://ntfy.sh/). A queue is used to make sure -notifications are not lost on failure but sent later. - -Requirements and installation ------------------------------ - -Just install the module: - - $ScriptInstallUpdate mod/notification-ntfy; - -Also install the Ntfy app on your mobile device or use the -[web app](https://ntfy.sh/app) in a browser of your choice. - -Configuration -------------- - -Creating an account is not required. Just choose a topic and you are good -to go. - -> ⚠️ **Warning**: If you use ntfy without sign-up, the topic is essentially -> a password, so pick something that's not easily guessable. - -Edit `global-config-overlay`, add `NtfyServer` (leave it unchanged, unless -you are self-hosting the service) and `NtfyTopic` with your choosen topic. -Then reload the configuration. - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Using a paid account or running a server on-premises allows to add additional -basic authentication. Configure `NtfyServerUser` and `NtfyServerPass` for this. -Even authentication via access token is possible, adding it as password with -a blank username. - -Also available is `NtfyServerToken` to add a bearer token for authentication. - -For a custom service installing an additional certificate may be required. -You may want to install that certificate manually, after finding the -[certificate name from browser](../../CERTIFICATES.md). - -Usage and invocation --------------------- - -There's nothing special to do. Every script or function sending a notification -will now send it to your Ntfy topic. - -But of course you can use the function to send notifications directly. Give -it a try: - - $SendNtfy "Subject..." "Body..."; - -Alternatively this sends a notification with all available and configured -methods: - - $SendNotification "Subject..." "Body..."; - -To use the functions in your own scripts you have to declare them first. -Place this before you call them: - - :global SendNtfy; - :global SendNotification; - -In case there is a situation when the queue needs to be purged there is a -function available: - - $PurgeNtfyQueue; - -See also --------- - -* [Certificate name from browser](../../CERTIFICATES.md) -* [Send notifications via e-mail](notification-email.md) -* [Send notifications via Matrix](notification-matrix.md) -* [Send notifications via Telegram](notification-telegram.md) - ---- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) diff --git a/doc/mod/notification-telegram.d/getchatid.avif b/doc/mod/notification-telegram.d/getchatid.avif deleted file mode 100644 index 7792969..0000000 Binary files a/doc/mod/notification-telegram.d/getchatid.avif and /dev/null differ diff --git a/doc/mod/notification-telegram.d/setuserpic.avif b/doc/mod/notification-telegram.d/setuserpic.avif deleted file mode 100644 index 2017d20..0000000 Binary files a/doc/mod/notification-telegram.d/setuserpic.avif and /dev/null differ diff --git a/doc/mod/notification-telegram.md b/doc/mod/notification-telegram.md index 2d00116..435694e 100644 --- a/doc/mod/notification-telegram.md +++ b/doc/mod/notification-telegram.md @@ -1,14 +1,7 @@ Send notifications via Telegram =============================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) +[◀ Go back to main README](../../README.md) > ℹ️️ **Info**: This module can not be used on its own but requires the base > installation. See [main README](../../README.md) for details. @@ -38,36 +31,21 @@ create your own bot: ![create new bot](notification-telegram.d/newbot.avif) -Set that token from *BotFather* (use your own!) to `TelegramTokenId`, for -now just temporarily: +Now open a chat with your bot and start it by clicking the `START` button. - :set TelegramTokenId "5214364459:AAHLwf1o7ybbKDo6pY24Kd2bZ5rjCakDXTc"; - -Now open a chat with your bot and start it by clicking the `START` button, -then send your first message. Any text will do. On your device run -`$GetTelegramChatId` to retrieve the chat id: - - $GetTelegramChatId; - -![get chat id](notification-telegram.d/getchatid.avif) +Open just another chat with [GetIDs Bot](https://t.me/getidsbot), again start +with the `START` button. It will send you some information, including the +`id`, just below `You`. Finally edit `global-config-overlay`, add `TelegramTokenId` with the token -from *BotFather* and `TelegramChatId` with your retrieved chat id. Then +from *BotFather* and `TelegramChatId` with your id from *GetIDs Bot*. Then reload the configuration. -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - ### Notifications to a group -Sending notifications to a group is possible as well. Add your bot to a group -and make it an admin (required for read access!) and send a message and run -`$GetTelegramChatId` again. Then use that chat id (which starts with a dash) -for `TelegramChatId`. - -Groups can enable the `Topics` feature. Use `TelegramThreadId` to send to a -specific topic in a group. +Sending notifications to a group is possible as well. Add your bot and the +*GetIDs Bot* to a group, then use the group's id (which starts with a dash) +for `TelegramChatId`. Then remove *GetIDs Bot* from group. Usage and invocation -------------------- @@ -75,49 +53,11 @@ Usage and invocation There's nothing special to do. Every script or function sending a notification will now send it to your Telegram account. -But of course you can use the function to send notifications directly. Give -it a try: - - $SendTelegram "Subject..." "Body..."; - -Alternatively this sends a notification with all available and configured -methods: - - $SendNotification "Subject..." "Body..."; - -To use the functions in your own scripts you have to declare them first. -Place this before you call them: - - :global SendTelegram; - :global SendNotification; - -In case there is a situation when the queue needs to be purged there is a -function available: - - $PurgeTelegramQueue; - -Tips & Tricks -------------- - -### Set a profile photo - -You can use a profile photo for your bot to make it recognizable. Open the -chat with [BotFather](https://t.me/BotFather) and set it there. - -![set profile photo](notification-telegram.d/setuserpic.avif) - -Have a look at my -[RouterOS-Scripts Logo Color Changer](https://git.eworm.de/cgit/routeros-scripts/plain/contrib/logo-color.html) -to create a colored version of this scripts' logo. - See also -------- -* [Chat with your router and send commands via Telegram bot](../telegram-chat.md) -* [Send notifications via e-mail](notification-email.md) * [Send notifications via Matrix](notification-matrix.md) -* [Send notifications via Ntfy](notification-ntfy.md) --- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/scriptrunonce.d/hello-world.rsc b/doc/mod/scriptrunonce.d/hello-world.rsc index 6404781..17ec575 100644 --- a/doc/mod/scriptrunonce.d/hello-world.rsc +++ b/doc/mod/scriptrunonce.d/hello-world.rsc @@ -1,3 +1,3 @@ #!rsc by RouterOS -:put ("Hello World from " . [ /system/identity/get name ] . "!"); +:put ("Hello World from " . [ / system identity get name ] . "!"); diff --git a/doc/mod/scriptrunonce.d/scriptrunonce.avif b/doc/mod/scriptrunonce.d/scriptrunonce.avif index 27ccd41..614c72c 100644 Binary files a/doc/mod/scriptrunonce.d/scriptrunonce.avif and b/doc/mod/scriptrunonce.d/scriptrunonce.avif differ diff --git a/doc/mod/scriptrunonce.md b/doc/mod/scriptrunonce.md index 955d12e..aaa64a9 100644 --- a/doc/mod/scriptrunonce.md +++ b/doc/mod/scriptrunonce.md @@ -1,14 +1,7 @@ Download script and run it once =============================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) +[◀ Go back to main README](../../README.md) > ℹ️️ **Info**: This module can not be used on its own but requires the base > installation. See [main README](../../README.md) for details. @@ -34,13 +27,9 @@ The optional configuration goes to `global-config-overlay`. * `ScriptRunOnceBaseUrl`: base url, prepended to parameter * `ScriptRunOnceUrlSuffix`: url suffix, appended to parameter -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - If the parameter passed to the function is not a complete URL (starting -with protocol `ftp://`, `http://`, `https://` or `sftp://`) the base-url is -prepended, and file extension `.rsc` and url-suffix are appended. +with protocol `ftp://`, `http://`, `https://` or `sftp://`) the values are +prepended and appended. Usage and invocation -------------------- @@ -48,12 +37,12 @@ Usage and invocation The function `$ScriptRunOnce` expects an URL (or name if `ScriptRunOnceBaseUrl` is given) pointing to a script as parameter. - $ScriptRunOnce https://git.eworm.de/cgit/routeros-scripts/plain/doc/mod/scriptrunonce.d/hello-world.rsc; + $ScriptRunOnce https://git.eworm.de/cgit/routeros-scripts/plain/doc/mod/scriptrunonce.d/hello-world.rsc ![ScriptRunOnce](scriptrunonce.d/scriptrunonce.avif) Giving multiple scripts is possible, separated by comma. --- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../../README.md) +[▲ Go back to top](#top) diff --git a/doc/mod/ssh-keys-import.md b/doc/mod/ssh-keys-import.md deleted file mode 100644 index 344f4bc..0000000 --- a/doc/mod/ssh-keys-import.md +++ /dev/null @@ -1,69 +0,0 @@ -Import ssh keys for public key authentication -============================================= - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.16-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../../README.md) - -> ℹ️️ **Info**: This module can not be used on its own but requires the base -> installation. See [main README](../../README.md) for details. - -Description ------------ - -RouterOS supports ssh login with public key authentication. The functions -in this module help importing the keys. - -Requirements and installation ------------------------------ - -Just install the module: - - $ScriptInstallUpdate mod/ssh-keys-import; - -Usage and invocation --------------------- - -### Import single key from terminal - -Call the function `$SSHKeysImport` with key and user as parameter to -import that key: - - $SSHKeysImport "ssh-ed25519 AAAAC3Nza...ZVugJT user" admin; - $SSHKeysImport "ssh-rsa AAAAB3Nza...QYZk8= user" admin; - -The third part of the key (`user` in this example) is inherited as -`key-owner` in RouterOS. Also the `MD5` fingerprint is recorded, this helps -to audit and verify the available keys. - -> ℹ️️ **Info**: Use `ssh-keygen` to show a fingerprint of an existing public -> key file: `ssh-keygen -l -E md5 -f ~/.ssh/id_ed25519.pub` - -### Import several keys from file - -The functions `$SSHKeysImportFile` can read an `authorized_keys`-style file -and import all the keys. The user given to the function can be overwritting -from comments in the file. Create a file `keys.pub` with this content: - -``` -ssh-ed25519 AAAAC3Nza...3OcN8A user@client -ssh-rsa AAAAB3Nza...ozyts= worker@station -# user=example -ssh-rsa AAAAB3Nza...GXQVk= person@host -``` - -Then import it with: - - $SSHKeysImportFile keys.pub admin; - -This will import the first two keys for user `admin` (as given to function) -and the third one for user `example` (as defined in comment). - ---- -[⬅️ Go back to main README](../../README.md) -[⬆️ Go back to top](#top) diff --git a/doc/mode-button.md b/doc/mode-button.md index be15bc9..c1c059e 100644 --- a/doc/mode-button.md +++ b/doc/mode-button.md @@ -1,14 +1,7 @@ Mode button with multiple presses ================================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -20,17 +13,17 @@ This script extend the functionality of mode button. Instead of just one you can trigger several actions by pressing the mode button several times. The hardware needs to have a mode button, see -`/system/routerboard/mode-button`. Starting with RouterOS 6.47beta60 you +`/ system routerboard mode-button`. Starting with RouterOS 6.47beta60 you can configure the reset button to act the same, see -`/system/routerboard/reset-button`. +`/ system routerboard reset-button`. Copy this code to terminal to check: ``` -:if ([ :len [ /system/routerboard/mode-button/print as-value ] ] > 0) do={ +:if ([ :len [ /system routerboard mode-button print as-value ] ] > 0) do={ :put "Mode button is supported."; } else={ - :if ([ :len [ /system/routerboard/reset-button/print as-value ] ] > 0) do={ + :if ([ :len [ /system routerboard reset-button print as-value ] ] > 0) do={ :put "Mode button is not supported, but reset button is."; } else={ :put "Neither mode button nor reset button is supported."; @@ -47,11 +40,11 @@ Just install the script: Then configure the mode button to run `mode-button`: - /system/routerboard/mode-button/set enabled=yes on-event="/system/script/run mode-button;"; + / system routerboard mode-button set enabled=yes on-event="/ system script run mode-button;"; To use the reset button instead: - /system/routerboard/reset-button/set enabled=yes on-event="/system/script/run mode-button;"; + / system routerboard reset-button set enabled=yes on-event="/ system script run mode-button;"; Configuration ------------- @@ -59,17 +52,13 @@ Configuration The configuration goes to `global-config-overlay`, these are the parameters: * `ModeButton`: an array with defined actions -* `ModeButtonLED`: led to give visual feedback, `type` must be `on` or `off` - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. +* `ModeButtonLED`: led to give visual feedback Usage and invocation -------------------- -Press the mode button. 😜 +Press the mode button. :) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/netwatch-dns.md b/doc/netwatch-dns.md index 0d94918..4fbfc2b 100644 --- a/doc/netwatch-dns.md +++ b/doc/netwatch-dns.md @@ -1,14 +1,7 @@ Manage DNS and DoH servers from netwatch ======================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.16-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -28,7 +21,7 @@ Just install the script: Then add a scheduler to run it periodically: - /system/scheduler/add interval=1m name=netwatch-dns on-event="/system/script/run netwatch-dns;" start-time=startup; + / system scheduler add interval=1m name=netwatch-dns on-event="/ system script run netwatch-dns;" start-time=startup; Configuration ------------- @@ -36,45 +29,25 @@ Configuration The DNS and DoH servers to be checked have to be added to netwatch with specific comment: - /tool/netwatch/add comment="doh" host=1.1.1.1; - /tool/netwatch/add comment="dns" host=8.8.8.8; - /tool/netwatch/add comment="doh, dns" host=9.9.9.9; + / tool netwatch add comment="doh, hostname=cloudflare-dns" host=1.1.1.1; + / tool netwatch add comment="dns, hostname=google-dns" host=8.8.8.8; + / tool netwatch add comment="doh, dns, hostname=quad-nine" host=9.9.9.10; This will configure *cloudflare-dns* for DoH (`https://1.1.1.1/dnsquery`), and -*google-dns* and *quad-nine* for regular DNS (`8.8.8.8,9.9.9.9`) if up. +*google-dns* and *quad-nine* for regular DNS (`8.8.8.8,9.9.9.10`) if up. If *cloudflare-dns* is down the script will fall back to *quad-nine* for DoH. Giving a specific query url for DoH is possible: - /tool/netwatch/add comment="doh, doh-url=https://dns.nextdns.io/dns-query" host=199.247.16.158; + / tool netwatch add comment="doh, hostname=nextdns, doh-url=https://dns.nextdns.io/dns-query" host=199.247.16.158; Note that using a name in DoH url may introduce a chicken-and-egg issue! -Adding a static DNS record has the same result for the url, but always -resolves to the same address. - - /ip/dns/static/add name="cloudflare-dns.com" address=1.1.1.1; - /tool/netwatch/add comment="doh" host=1.1.1.1; - -Be aware that you have to keep the ip address in sync with real world -manually! - -Importing a certificate automatically is possible. You may want to find the -[certificate name from browser](../CERTIFICATES.md). - - /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root G2" host=1.1.1.1; - /tool/netwatch/add comment="doh, doh-cert=DigiCert Global Root G3" host=9.9.9.9; - /tool/netwatch/add comment="doh, doh-cert=GTS Root R1" host=8.8.8.8; - -> ⚠️ **Warning**: Combining these techniques can cause some confusion and -> troubles! Chances are that a service uses different certificates based -> on indicated server name. - Sometimes using just one specific (possibly internal) DNS server may be desired, with fallback in case it fails. This is possible as well: - /tool/netwatch/add comment="dns" host=10.0.0.10; - /tool/netwatch/add comment="dns-fallback" host=1.1.1.1; + / tool netwatch add comment="dns, hostname=pi-hole" host=10.0.0.10; + / tool netwatch add comment="dns-fallback, hostname=cloudflare-dns" host=1.1.1.1; Tips & Tricks ------------- @@ -84,16 +57,15 @@ Tips & Tricks Netwatch entries can be created to work with both - this script and [netwatch-notify](netwatch-notify.md). Just give options for both: - /tool/netwatch/add comment="doh, notify, name=cloudflare-dns" host=1.1.1.1; + / tool netwatch add comment="doh, notify, hostname=cloudflare-dns" host=1.1.1.1; Also this allows to update host address, see option `resolve`. See also -------- -* [Certificate name from browser](../CERTIFICATES.md) * [Notify on host up and down](netwatch-notify.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/netwatch-notify.d/notification-01-down.avif b/doc/netwatch-notify.d/notification-01-down.avif deleted file mode 100644 index 894fb23..0000000 Binary files a/doc/netwatch-notify.d/notification-01-down.avif and /dev/null differ diff --git a/doc/netwatch-notify.d/notification-01-down.svg b/doc/netwatch-notify.d/notification-01-down.svg new file mode 100644 index 0000000..5cab5fe --- /dev/null +++ b/doc/netwatch-notify.d/notification-01-down.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ❌ Netwatch Notify: example.com down + +Host example.com (93.184.216.34) is down since +jun/08/2021 06:55:03. + + diff --git a/doc/netwatch-notify.d/notification-02-up.avif b/doc/netwatch-notify.d/notification-02-up.avif deleted file mode 100644 index 9021a93..0000000 Binary files a/doc/netwatch-notify.d/notification-02-up.avif and /dev/null differ diff --git a/doc/netwatch-notify.d/notification-02-up.svg b/doc/netwatch-notify.d/notification-02-up.svg new file mode 100644 index 0000000..8e03981 --- /dev/null +++ b/doc/netwatch-notify.d/notification-02-up.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] ✅ Netwatch Notify: example.com up + +Host example.com (93.184.216.34) is up since +jun/08/2021 07:01:00. +It was down for 6 checks since jun/08/2021 06:55:03. + + diff --git a/doc/netwatch-notify.md b/doc/netwatch-notify.md index 81adfe9..b2f6dd0 100644 --- a/doc/netwatch-notify.md +++ b/doc/netwatch-notify.md @@ -1,14 +1,7 @@ Notify on host up and down ========================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -17,15 +10,15 @@ Description ----------- This script sends notifications about host UP and DOWN events. In comparison -to just netwatch (`/tool/netwatch`) and its `up-script` and `down-script` +to just netwatch (`/ tool netwatch`) and its `up-script` and `down-script` this script implements a simple state machine and dependency model. Host down events are triggered only if the host is down for several checks and optional parent host is not down to avoid false alerts. ### Sample notifications -![netwatch-notify notification down](netwatch-notify.d/notification-01-down.avif) -![netwatch-notify notification up](netwatch-notify.d/notification-02-up.avif) +![netwatch-notify notification down](netwatch-notify.d/notification-01-down.svg) +![netwatch-notify notification up](netwatch-notify.d/notification-02-up.svg) Requirements and installation ----------------------------- @@ -36,20 +29,14 @@ Just install the script: Then add a scheduler to run it periodically: - /system/scheduler/add interval=1m name=netwatch-notify on-event="/system/script/run netwatch-notify;" start-time=startup; + / system scheduler add interval=1m name=netwatch-notify on-event="/ system script run netwatch-notify;" start-time=startup; Configuration ------------- The hosts to be checked have to be added to netwatch with specific comment: - /tool/netwatch/add comment="notify, name=example.com" host=[ :resolve "example.com" ]; - -Also notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or -[telegram](mod/notification-telegram.md). + / tool netwatch add comment="notify, hostname=example.com" host=[ :resolve "example.com" ]; ### Hooks @@ -57,47 +44,39 @@ It is possible to run an up hook command (`up-hook`) or down hook command (`down-hook`) when a notification is triggered. This has to be added in comment, note that some characters need extra escaping: - /tool/netwatch/add comment=("notify, name=device, down-hook=/interface/ethernet \\{ disable \\\"en2\\\"; enable \\\"en2\\\"; \\}") host=10.0.0.20; + / tool netwatch add comment=("notify, hostname=device, down-hook=/ interface ethernet \\{ disable \\\"en2\\\"; enable \\\"en2\\\"; \\}") host=10.0.0.20; Also there is a `pre-down-hook` that fires at two thirds of failed checks required for the notification. The idea is to fix the issue before a notification is sent. -Getting the escaping right may be troublesome. Please consider adding a -script in `/system/script`, then running that from hook. +### Count threshould -### Count threshold +The count threshould (default is 5 checks) is configurable as well: -The count threshold (default is 5 checks) is configurable as well: - - /tool/netwatch/add comment="notify, name=example.com, count=10" host=104.18.144.11; + / tool netwatch add comment="notify, hostname=example.com, count=10" host=104.18.144.11; ### Parents & dependencies If the host is behind another checked host add a dependency, this will suppress notification if the parent host is down: - /tool/netwatch/add comment="notify, name=gateway" host=93.184.216.1; - /tool/netwatch/add comment="notify, name=example.com, parent=gateway" host=93.184.216.34; + / tool netwatch add comment="notify, hostname=gateway" host=93.184.216.1; + / tool netwatch add comment="notify, hostname=example.com, parent=gateway" host=93.184.216.34; Note that every configured parent in a chain increases the check count -threshold by one. +threshould by one. ### Update from DNS The host address can be updated dynamically. Give extra parameter `resolve` with a resolvable name: - /tool/netwatch/add comment="notify, name=example.com, resolve=example.com" host=0.0; + / tool netwatch add comment="notify, hostname=example.com, resolve=example.com"; -This supports multiple A records for a name just fine, even a CNAME -to those. An update happens only if no more record with the configured host -address is found. - -The address family is preserved, so if you want AAAA records (for IPv6) -use this: - - /tool/netwatch/add comment="notify, name=example.com, resolve=example.com" host=::; +But be warned: Dynamic updates will probably cause issues if the name has +more than one record in dns - a high rate of configuration changes (and flash +writes) at least. ### No notification on host down @@ -105,32 +84,13 @@ Also suppressing the notification on host down is possible with parameter `no-down-notification`. This may be desired for devices that are usually powered off, but accessibility is of interest. - /tool/netwatch/add comment="notify, name=printer, no-down-notification" host=10.0.0.30; + / tool netwatch add comment="notify, hostname=printer, no-down-notification" host=10.0.0.30; Go and get your coffee ☕️ before sending the print job. -### No log on failed resolve - -A message is writting to log after three failed attemts to resolve a host. -However this can cause some noise for hosts that are expected to have -failures, for example when the name is dynamically added by -[`dhcp-to-dns`](dhcp-to-dns.md). This can be suppressed: - - /tool/netwatch/add comment="notify, name=client, resolve=client.dhcp.example.com, no-resolve-fail" host=10.0.0.0; - -### Add a note in notification - -For some extra information it is possible to add a text note. This is -included verbatim into the notification. - - /tool/netwatch/add comment="notify, name=example, note=Do not touch!" host=10.0.0.31; - -### Add a link in notification - -It is possible to add a link in notification, that is added below the -formatted notification text. - - /tool/netwatch/add comment="notify, name=example.com, resolve=example.com, link=https://example.com/" host=0.0; +Also notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or +[telegram](mod/notification-telegram.md). Tips & Tricks ------------- @@ -139,10 +99,10 @@ Tips & Tricks Sometimes it is sufficient if one of a number of hosts is available. You can make `netwatch-notify` check for that by adding several items with same -`name`. Note that `count` has to be multiplied to keep the actual time. +`hostname`. Note that `count` has to be multiplied to keep the actual time. - /tool/netwatch/add comment="notify, name=service, count=10" host=10.0.0.10; - /tool/netwatch/add comment="notify, name=service, count=10" host=10.0.0.20; + / tool netwatch add comment="notify, hostname=service, count=10" host=10.0.0.10; + / tool netwatch add comment="notify, hostname=service, count=10" host=10.0.0.20; ### Checking internet connectivity @@ -152,11 +112,11 @@ check `1.1.1.1` (Cloudflare DNS), `9.9.9.9` (Quad-nine DNS), `8.8.8.8` (Google DNS) or any other reliable address that indicates internet connectivity. - /tool/netwatch/add comment="notify, name=internet" host=1.1.1.1; + / tool netwatch add comment="notify, hostname=internet" host=1.1.1.1; A target like this suits well to be parent for other checks. - /tool/netwatch/add comment="notify, name=example.com, parent=internet" host=93.184.216.34; + / tool netwatch add comment="notify, hostname=example.com, parent=internet" host=93.184.216.34; ### Checking specific ISP @@ -164,13 +124,12 @@ Having several ISPs for redundancy a failed link may go unnoticed without proper monitoring. You can use routing-mark to monitor specific connections. Create a route and firewall mangle rule. - /routing/table/add fib name=via-isp1; - /ip/route/add distance=1 gateway=isp1 routing-table=via-isp1; - /ip/firewall/mangle/add action=mark-routing chain=output new-routing-mark=via-isp1 dst-address=1.0.0.1 passthrough=yes; + / ip route add distance=1 gateway=isp1 routing-mark=via-isp1; + / ip firewall mangle add action=mark-routing chain=output new-routing-mark=via-isp1 dst-address=1.0.0.1 passthrough=yes; Finally monitor the address with `netwatch-notify`. - /tool/netwatch/add comment="notify, name=quad-one via isp1" host=1.0.0.1; + / tool netwatch add comment="notify, hostname=quad-one via isp1" host=1.0.0.1; Note that *all* traffic to the given address is routed that way. In case of link failure this address is not available, so use something reliable but @@ -182,7 +141,7 @@ non-essential. In this example the address `1.0.0.1` is used, the same service Netwatch entries can be created to work with both - this script and [netwatch-dns](netwatch-dns.md). Just give options for both: - /tool/netwatch/add comment="doh, notify, name=cloudflare-dns" host=1.1.1.1; + / tool netwatch add comment="doh, notify, hostname=cloudflare-dns" host=1.1.1.1; See also -------- @@ -190,5 +149,5 @@ See also * [Manage DNS and DoH servers from netwatch](netwatch-dns.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/netwatch-syslog.md b/doc/netwatch-syslog.md index 6a337d4..9a28bb9 100644 --- a/doc/netwatch-syslog.md +++ b/doc/netwatch-syslog.md @@ -1,5 +1,34 @@ -This script has been dropped. Filtering in firewall is advised, which should -look something like this: +Manage remote logging +===================== - /ip/firewall/filter/add action=reject chain=output out-interface-list=WAN port=514 protocol=udp reject-with=icmp-admin-prohibited; - /ip/firewall/filter/add action=reject chain=forward out-interface-list=WAN port=514 protocol=udp reject-with=icmp-admin-prohibited; +[◀ Go back to main README](../README.md) + +Description +----------- + +RouterOS supports sending log messages via network to a remote syslog server. +If the server is not available no log messages (with potentially sensitive +information) should be sent. This script disables remote logging by +availability. + +Requirements and installation +----------------------------- + +Let's assume there is a remote log action and associated logging rule: + + / system logging action set remote=10.0.0.1 [ find where name="remote" ]; + / system logging add action=remote topics=info; + +Just install the script: + + $ScriptInstallUpdate netwatch-syslog; + +... and create a netwatch matching the IP address from logging action above: + + / tool netwatch add down-script=netwatch-syslog host=10.0.0.1 up-script=netwatch-syslog; + +All logging rules are disabled when host is down. + +--- +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/ospf-to-leds.md b/doc/ospf-to-leds.md index 3694d35..2fba33e 100644 --- a/doc/ospf-to-leds.md +++ b/doc/ospf-to-leds.md @@ -1,14 +1,7 @@ Visualize OSPF state via LEDs ============================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -29,7 +22,7 @@ Just install the script: ... and add a scheduler to run the script periodically: - /system/scheduler/add interval=20s name=ospf-to-leds on-event="/system/script/run ospf-to-leds;" start-time=startup; + / system scheduler add interval=20s name=ospf-to-leds on-event="/ system script run ospf-to-leds;" start-time=startup; Configuration ------------- @@ -37,8 +30,8 @@ Configuration The configuration goes to OSPF instance's comment. To visualize state for instance `default` via LED `user-led` set this: - /routing/ospf/instance/set default comment="ospf-to-leds, leds=user-led"; + / routing ospf instance set default comment="ospf-to-leds, leds=user-led"; --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/packages-update.md b/doc/packages-update.md index 75225fe..243e72b 100644 --- a/doc/packages-update.md +++ b/doc/packages-update.md @@ -1,14 +1,7 @@ Manage system update ==================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -17,18 +10,14 @@ Description ----------- In rare cases RouterOS fails to properly downlaod package on update -(`/system/package/update/install`), resulting in borked system with missing +(`/ system package update install`), resulting in borked system with missing packages. This script tries to avoid this situation by doing some basic verification. But it provides some extra functionality: -* upload backup to Mikrotik cloud if [backup-cloud](backup-cloud.md) is - installed * send backup via e-mail if [backup-email](backup-email.md) is installed -* save configuration to fallback partition if - [backup-partition](backup-partition.md) is installed -* upload backup to server if [backup-upload](backup-upload.md) is installed +* upload backup if [backup-upload](backup-upload.md) is installed * schedule reboot at night Requirements and installation @@ -41,38 +30,23 @@ Just install the script: It is automatically run by [check-routeros-update](check-routeros-update.md) if available. -Configuration -------------- - -The configuration goes to `global-config-overlay`, this is the only parameter: - -* `PackagesUpdateDeferReboot`: defer the reboot for night (between 3 AM - and 5 AM) - -By modifying the scheduler's `start-time` you can force the reboot at -different time. - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - Usage and invocation -------------------- Alternatively run it manually: - /system/script/run packages-update; + / system script run packages-update; See also -------- +* [Notify on RouterOS update](check-routeros-update.md) * [Upload backup to Mikrotik cloud](backup-cloud.md) * [Send backup via e-mail](backup-email.md) -* [Save configuration to fallback partition](backup-partition.md) +* [Save configuration to fallback partition](doc/backup-partition.md) * [Upload backup to server](backup-upload.md) -* [Notify on RouterOS update](check-routeros-update.md) * [Automatically upgrade firmware and reboot](firmware-upgrade-reboot.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/ppp-on-up.md b/doc/ppp-on-up.md index 305afc1..ae58da5 100644 --- a/doc/ppp-on-up.md +++ b/doc/ppp-on-up.md @@ -1,14 +1,7 @@ Run scripts on ppp connection ============================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -31,7 +24,7 @@ Just install the script: ... and make it the `on-up` script for ppp profile: - /ppp/profile/set on-up=ppp-on-up [ find ]; + / ppp profile set on-up=ppp-on-up [ find ]; See also -------- @@ -40,5 +33,5 @@ See also * [Update tunnelbroker configuration](update-tunnelbroker.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/rotate-ntp.md b/doc/rotate-ntp.md index 9a016a3..775b977 100644 --- a/doc/rotate-ntp.md +++ b/doc/rotate-ntp.md @@ -1,3 +1,43 @@ -This script has been dropped as the limitation does no longer exist with -RouterOS 7.x, where you can enable a ntp server and use a name for the client -at the same time. +Rotate NTP servers +================== + +[◀ Go back to main README](../README.md) + +> ℹ️ **Info**: This script can not be used on its own but requires the base +> installation. See [main README](../README.md) for details. + +Description +----------- + +RouterOS requires NTP servers to be configured by IP address. Servers from a +pool may appear and disappear, leaving broken NTP configuration. + +This script allows to rotate IP addresses from a given pool. + +Requirements and installation +----------------------------- + +Just install the script: + + $ScriptInstallUpdate rotate-ntp; + +Configuration +------------- + +The configuration goes to `global-config-overlay`, this is the parameter: + +* `NtpPool`: dns name of ntp server pool + +Usage and invocation +-------------------- + +Just run the script to update the NTP configuration with actual IP +addresses from pool if required. + +Alternatively a scheduler can be created: + + / system scheduler add interval=5d name=rotate-ntp on-event="/ system script run rotate-ntp;" start-time=startup; + +--- +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/sms-action.md b/doc/sms-action.md index b696c85..8442774 100644 --- a/doc/sms-action.md +++ b/doc/sms-action.md @@ -1,14 +1,7 @@ Act on received SMS =================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -35,13 +28,9 @@ The configuration goes to `global-config-overlay`, this is the only parameter: * `SmsAction`: an array with pre-defined actions -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - Then enable SMS actions: - /tool/sms/set allowed-number=+491234567890 receive-enabled=yes secret=s3cr3t; + / tool sms set allowed-number=+491234567890 receive-enabled=yes secret=s3cr3t; Usage and invocation -------------------- @@ -59,5 +48,5 @@ See also * [Forward received SMS](sms-forward.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/sms-forward.d/notification.avif b/doc/sms-forward.d/notification.avif deleted file mode 100644 index 01eb7ba..0000000 Binary files a/doc/sms-forward.d/notification.avif and /dev/null differ diff --git a/doc/sms-forward.d/notification.svg b/doc/sms-forward.d/notification.svg new file mode 100644 index 0000000..298b43e --- /dev/null +++ b/doc/sms-forward.d/notification.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + [MikroTik] 📨 SMS Forwarding from 7277 + +Received this message by MikroTik from 7277: + +On Jun/12/2021 13:44:10 GMT -0 type class-0: +Welcome to our network! + + diff --git a/doc/sms-forward.md b/doc/sms-forward.md index ccb6482..9ebae69 100644 --- a/doc/sms-forward.md +++ b/doc/sms-forward.md @@ -1,14 +1,7 @@ Forward received SMS ==================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -22,7 +15,7 @@ A broadband interface with SMS support is required. ### Sample notification -![sms-forward notification](sms-forward.d/notification.avif) +![sms-forward notification](sms-forward.d/notification.svg) Requirements and installation ----------------------------- @@ -33,60 +26,17 @@ Just install the script: ... and add a scheduler to run it periodically: - /system/scheduler/add interval=2m name=sms-forward on-event="/system/script/run sms-forward;" start-time=startup; + / system scheduler add interval=2m name=sms-forward on-event="/ system script run sms-forward;" start-time=startup; Configuration ------------- -You have to enable receiving of SMS: +Notification settings are required for e-mail, +[matrix](mod/notification-matrix.md) and/or +[telegram](mod/notification-telegram.md). Also you have to enable receiving +of SMS: - /tool/sms/set receive-enabled=yes; - -The configuration goes to `global-config-overlay`, this is the only parameter: - -* `SmsForwardHooks`: an array with pre-defined hooks, where each hook consists - of `match` (which is matched against the received message), `allowed-number` - (which is matched against the sending phone number or name) and `command`. - For `match` and `allowed-number` regular expressions are supported. Actual - phone number (`$Phone`) and message (`$Message`) are available for the hook. - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Notification settings are required for -[e-mail](mod/notification-email.md), -[matrix](mod/notification-matrix.md), -[ntfy](mod/notification-ntfy.md) and/or -[telegram](mod/notification-telegram.md). - -Tips & Tricks -------------- - -### Take care of harmful commands! - -It is easy to fake the sending phone number! So make sure you do not rely on -that number for potentially harmful commands. Add a shared secret to match -into the text instead, for example: `reboot-53cr3t-5tr1n9` instead of just -`reboot`. - -### Order new volume - -Most broadband providers include a volume limit for their data plans. The -hook functionality can be used to order new volume automatically. - -Let's assume an imaginary provider **ABC** sends a message when the available -volume is about to deplete. The message is sent from `ABC` and the text -contains the string `80%`. New volume can be ordered by sending a SMS back to -the phone number `1234` with the text `data-plan`. - - :global SmsForwardHooks { - { match="80%"; - allowed-number="ABC"; - command="/tool/sms/send lte1 phone-number=1234 message=\"data-plan\";" }; - }; - -Adjust the values to your own needs. + / tool sms set receive-enabled=yes; See also -------- @@ -94,5 +44,5 @@ See also * [Act on received SMS](sms-action.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/ssh-keys-import.md b/doc/ssh-keys-import.md index d1325aa..3dc8ccf 100644 --- a/doc/ssh-keys-import.md +++ b/doc/ssh-keys-import.md @@ -1,2 +1,33 @@ -This script has been replaced by a module. Please see -[Import ssh keys for public key authentication](mod/ssh-keys-import.md). +Import SSH keys +=============== + +[◀ Go back to main README](../README.md) + +Description +----------- + +This script imports public SSH keys (files with extension "`pub`") into +local store for user authentication. + +Requirements and installation +----------------------------- + +Just install the script: + + $ScriptInstallUpdate ssh-keys-import; + +Usage and invocation +-------------------- + +Copy files with extension "`pub`" containing public SSH keys for your device. +Then run the script: + + / system script run ssh-keys-import; + +Starting with an `authorized_keys` file you can split it on a shell: + + grep -E '^ssh-rsa' authorized_keys | nl -nrz | while read num type key name; do echo $type $key $name > $num-$name.pub; done + +--- +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/super-mario-theme.md b/doc/super-mario-theme.md index c72f220..68484dc 100644 --- a/doc/super-mario-theme.md +++ b/doc/super-mario-theme.md @@ -1,14 +1,7 @@ Play Super Mario theme ====================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) Description ----------- @@ -29,10 +22,10 @@ Usage and invocation Just run the script to play: - /system/script/run super-mario-theme; + / system script run super-mario-theme; For extra fun use it for dhcp lease script. :) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/telegram-chat.d/01-chat-specific.avif b/doc/telegram-chat.d/01-chat-specific.avif deleted file mode 100644 index ab75f78..0000000 Binary files a/doc/telegram-chat.d/01-chat-specific.avif and /dev/null differ diff --git a/doc/telegram-chat.d/02-chat-all.avif b/doc/telegram-chat.d/02-chat-all.avif deleted file mode 100644 index ed1a389..0000000 Binary files a/doc/telegram-chat.d/02-chat-all.avif and /dev/null differ diff --git a/doc/telegram-chat.d/03-reply.avif b/doc/telegram-chat.d/03-reply.avif deleted file mode 100644 index 515853e..0000000 Binary files a/doc/telegram-chat.d/03-reply.avif and /dev/null differ diff --git a/doc/telegram-chat.md b/doc/telegram-chat.md deleted file mode 100644 index 1e6f70f..0000000 --- a/doc/telegram-chat.md +++ /dev/null @@ -1,152 +0,0 @@ -Chat with your router and send commands via Telegram bot -======================================================== - -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) - -> ℹ️ **Info**: This script can not be used on its own but requires the base -> installation. See [main README](../README.md) for details. - -Description ------------ - -This script makes your device poll a Telegram bot for new messages. With -these messages you can send commands to your device and make it run them. -The resulting output is send back to you. - -Requirements and installation ------------------------------ - -Just install the script and the module for notifications via Telegram: - - $ScriptInstallUpdate telegram-chat,mod/notification-telegram; - -Then create a schedule that runs the script periodically: - - /system/scheduler/add start-time=startup interval=30s name=telegram-chat on-event="/system/script/run telegram-chat;"; - -> ⚠️ **Warning**: Make sure to keep the interval in sync when installing -> on several devices. Differing polling intervals will result in missed -> messages. - -Configuration -------------- - -Make sure to configure -[notifications via telegram](mod/notification-telegram.md) first. The -additional configuration goes to `global-config-overlay`, these are the -parameters: - -* `TelegramChatIdsTrusted`: an array with trusted chat ids or user names -* `TelegramChatGroups`: define the groups a device should belong to - -> ℹ️ **Info**: Copy relevant configuration from -> [`global-config`](../global-config.rsc) (the one without `-overlay`) to -> your local `global-config-overlay` and modify it to your specific needs. - -Usage and invocation --------------------- - -### Activating device(s) - -This script is capable of chatting with multiple devices. By default a -device is passive and not acting on messages. To activate it send a message -containing `! identity` (exclamation mark, optional space and system's -identity). To query all dynamic ip addresses form a device named "*MikroTik*" -send `! MikroTik`, followed by `/ip/address/print where dynamic;`. - -![chat to specific device](telegram-chat.d/01-chat-specific.avif) - -Devices can be grouped to chat with them simultaneously. The default group -"*all*" can be activated by sending `! @all`, which will make all devices -act on your commands. - -![chat to all devices](telegram-chat.d/02-chat-all.avif) - -Send a single exclamation mark or non-existent identity to make all -devices passive again. - -### Reply to message - -Let's assume you received a message from a device before, and want to send -a command to that device. No need to activate it, you can just reply to -that message. - -![reply to message](telegram-chat.d/03-reply.avif) - -Associated messages are cleared on device reboot. - -### Ask for devices - -Send a message with a single question mark (`?`) to query for devices -currenty online. The answer can be used for command via reply then. - -Known limitations ------------------ - -### Do not use numeric ids! - -Numeric ids are valid within a session only. Usually you can use something -like this to print all ip addresses and remove the first one: - - /ip/address/print; - /ip/address/remove 0; - -This will fail when sent in separate messages. Instead you should use basic -scripting capabilities. Try to print what you want to act on... - - /ip/address/print where interface=eth; - -... verify and finally remove it. - - /ip/address/remove [ find where interface=eth ]; - -What does work is using the persistent ids: - - /ip/address/print show-ids; - -The output contains an id starting with asterisk that can be used: - - /ip/address/remove *E; - -### Mind command runtime - -The command is run in background while the script waits for it - about -20 seconds at maximum. A command exceeding that time continues to run in -background, but the output in the message is missing or truncated then. - -If you still want a response you can work around this by making your code -send information on its own. Something like this should do the job: - - :global SendTelegram; - :delay 30s; - $SendTelegram "Command finished" "Your command finished..."; - -### Output size - -Telegram messages have a limit of 4096 characters. If output is too large it -is truncated, and a warning is added to the message. - -### Sending commands to a group - -Adding a bot to a group allows it to send messages to that group. To allow -it to receive messages you have to make it an admin of that group! It is -fine to deny all permissions, though. - -Also adding an admin to a group can cause the group id to change, so check -that if notifications break suddenly. - -See also --------- - -* [Send notifications via Telegram](mod/notification-telegram.md) - ---- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) diff --git a/doc/unattended-lte-firmware-upgrade.md b/doc/unattended-lte-firmware-upgrade.md index cb96aa1..65801a6 100644 --- a/doc/unattended-lte-firmware-upgrade.md +++ b/doc/unattended-lte-firmware-upgrade.md @@ -1,14 +1,7 @@ Install LTE firmware upgrade ============================ -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) Description ----------- @@ -19,7 +12,6 @@ This script upgrades LTE firmware on compatible devices: * R11e-LTE-US * R11e-4G * R11e-LTE6 -* ... and more - probably what ever Mikrotik builds into their devices A temporary scheduler is created to be independent from terminal. Thus starting the upgrade process over the broadband connection is supported. @@ -40,7 +32,7 @@ Usage and invocation Run the script if an upgrade for your LTE hardware is available: - /system/script/run unattended-lte-firmware-upgrade; + / system script run unattended-lte-firmware-upgrade; Then be patient, go for a coffee and wait for the upgrade process to finish. @@ -50,5 +42,5 @@ See also * [Notify on LTE firmware upgrade](check-lte-firmware-upgrade.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/update-gre-address.md b/doc/update-gre-address.md index de9f622..c19e138 100644 --- a/doc/update-gre-address.md +++ b/doc/update-gre-address.md @@ -1,14 +1,7 @@ Update GRE configuration with dynamic addresses =============================================== -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -33,7 +26,7 @@ Just install the script: ... and add a scheduler to run the script periodically: - /system/scheduler/add interval=30s name=update-gre-address on-event="/system/script/run update-gre-address;" start-time=startup; + / system scheduler add interval=30s name=update-gre-address on-event="/ system script run update-gre-address;" start-time=startup; Configuration ------------- @@ -41,8 +34,8 @@ Configuration The configuration goes to interface's comment. Add the client's IKEv2 certificate CN into the comment: - /interface/gre/set comment="ikev2-client1" gre-client1; + / interface gre set comment="ikev2-client1" gre-client1; --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/doc/update-tunnelbroker.md b/doc/update-tunnelbroker.md index ee0fe98..9dd7f19 100644 --- a/doc/update-tunnelbroker.md +++ b/doc/update-tunnelbroker.md @@ -1,14 +1,7 @@ Update tunnelbroker configuration ================================= -[![GitHub stars](https://img.shields.io/github/stars/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=red)](https://github.com/eworm-de/routeros-scripts/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=green)](https://github.com/eworm-de/routeros-scripts/network) -[![GitHub watchers](https://img.shields.io/github/watchers/eworm-de/routeros-scripts?logo=GitHub&style=flat&color=blue)](https://github.com/eworm-de/routeros-scripts/watchers) -[![required RouterOS version](https://img.shields.io/badge/RouterOS-7.15-yellow?style=flat)](https://mikrotik.com/download/changelogs/) -[![Telegram group @routeros_scripts](https://img.shields.io/badge/Telegram-%40routeros__scripts-%2326A5E4?logo=telegram&style=flat)](https://t.me/routeros_scripts) -[![donate with PayPal](https://img.shields.io/badge/Like_it%3F-Donate!-orange?logo=githubsponsors&logoColor=orange&style=flat)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A4ZXBD6YS2W8J) - -[⬅️ Go back to main README](../README.md) +[◀ Go back to main README](../README.md) > ℹ️ **Info**: This script can not be used on its own but requires the base > installation. See [main README](../README.md) for details. @@ -35,10 +28,11 @@ Configuration The configuration goes to interface's comment: - /interface/6to4/set comment="tunnelbroker, user=user, id=12345, pass=s3cr3t" tunnelbroker; + / interface 6to4 set comment="tunnelbroker, user=user, pass=s3cr3t, id=12345" tunnelbroker; -You should know you user name from login. The `id` is the tunnel's numeric -id, `pass` is the *update key* found on the tunnel's advanced tab. +Also enabling dynamic DNS in Mikrotik cloud is required: + + / ip cloud set ddns-enabled=yes; See also -------- @@ -46,5 +40,5 @@ See also * [Run scripts on ppp connection](ppp-on-up.md) --- -[⬅️ Go back to main README](../README.md) -[⬆️ Go back to top](#top) +[◀ Go back to main README](../README.md) +[▲ Go back to top](#top) diff --git a/firmware-upgrade-reboot b/firmware-upgrade-reboot new file mode 100644 index 0000000..ac2cb55 --- /dev/null +++ b/firmware-upgrade-reboot @@ -0,0 +1,41 @@ +#!rsc by RouterOS +# RouterOS script: firmware-upgrade-reboot +# Copyright (c) 2022 Christian Hesse +# 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 ("Firmware is already up to date.") 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/firmware-upgrade-reboot.rsc b/firmware-upgrade-reboot.rsc deleted file mode 100644 index 86a9a8c..0000000 --- a/firmware-upgrade-reboot.rsc +++ /dev/null @@ -1,60 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: firmware-upgrade-reboot -# Copyright (c) 2022-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# install firmware upgrade, and reboot -# https://rsc.eworm.de/doc/firmware-upgrade-reboot.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ScriptLock; - :global VersionToNum; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :local RouterBoard [ /system/routerboard/get ]; - :if ($RouterBoard->"current-firmware" = $RouterBoard->"upgrade-firmware") do={ - $LogPrint info $ScriptName ("Current and upgrade firmware match with version " . \ - $RouterBoard->"current-firmware" . "."); - :set ExitOK true; - :error true; - } - :if ([ $VersionToNum ($RouterBoard->"current-firmware") ] > [ $VersionToNum ($RouterBoard->"upgrade-firmware") ]) do={ - $LogPrint info $ScriptName ("Different firmware version is available, but it is a downgrade. Ignoring."); - :set ExitOK true; - :error true; - } - - :if ([ /system/routerboard/settings/get auto-upgrade ] = false) do={ - $LogPrint info $ScriptName ("Firmware version " . $RouterBoard->"upgrade-firmware" . \ - " is available, upgrading."); - /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; - } - - $LogPrint info $ScriptName ("Firmware upgrade successful, rebooting."); - /system/reboot; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/fw-addr-lists.d/allow b/fw-addr-lists.d/allow deleted file mode 100644 index 8b59ed7..0000000 --- a/fw-addr-lists.d/allow +++ /dev/null @@ -1,3 +0,0 @@ -# an ip address list for use with fw-addr-lists script -# https://git.eworm.de/cgit/routeros-scripts/about/doc/fw-addr-lists.md -git.eworm.de diff --git a/fw-addr-lists.d/block b/fw-addr-lists.d/block deleted file mode 100644 index 5e9fef2..0000000 --- a/fw-addr-lists.d/block +++ /dev/null @@ -1,5 +0,0 @@ -# an ip address list for use with fw-addr-lists script -# https://git.eworm.de/cgit/routeros-scripts/about/doc/fw-addr-lists.md - -# example.net -93.184.216.34 diff --git a/fw-addr-lists.d/mikrotik b/fw-addr-lists.d/mikrotik deleted file mode 100644 index 3b31a94..0000000 --- a/fw-addr-lists.d/mikrotik +++ /dev/null @@ -1,5 +0,0 @@ -# AS51894 Mikrotikls SIA -# https://bgp.he.net/AS51894 -159.148.147.0/24 -159.148.172.0/24 -2a02:610:7501::/48 diff --git a/fw-addr-lists.rsc b/fw-addr-lists.rsc deleted file mode 100644 index f0940fe..0000000 --- a/fw-addr-lists.rsc +++ /dev/null @@ -1,214 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: fw-addr-lists -# Copyright (c) 2023-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.16 -# -# download, import and update firewall address-lists -# https://rsc.eworm.de/doc/fw-addr-lists.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global FwAddrLists; - :global FwAddrListTimeOut; - - :global CertificateAvailable; - :global EitherOr; - :global FetchHuge; - :global HumanReadableNum; - :global LogPrint; - :global LogPrintOnce; - :global LogPrintVerbose; - :global ScriptLock; - :global WaitFullyConnected; - - :local FindDelim do={ - :local ValidChars "0123456789.:/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"; - :for I from=0 to=[ :len $1 ] do={ - :if ([ :typeof [ :find $ValidChars [ :pick ($1 . " ") $I ] ] ] != "num") do={ - :return $I; - } - } - } - - :local GetBranch do={ - :global EitherOr; - :return [ :pick [ :convert transform=md5 to=hex [ :pick $1 0 [ $EitherOr [ :find $1 "/" ] [ :len $1 ] ] ] ] 0 2 ]; - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - :local ListComment ("managed by " . $ScriptName); - - :foreach FwListName,FwList in=$FwAddrLists do={ - :local CntAdd 0; - :local CntRenew 0; - :local CntRemove 0; - :local IPv4Addresses ({}); - :local IPv6Addresses ({}); - :local Failure false; - - :foreach List in=$FwList do={ - :local CheckCertificate false; - :local Data false; - :local TimeOut [ $EitherOr [ :totime ($List->"timeout") ] $FwAddrListTimeOut ]; - - :if ([ :len ($List->"cert") ] > 0) do={ - :set CheckCertificate true; - :if ([ $CertificateAvailable ($List->"cert") ] = false) do={ - $LogPrint warning $ScriptName ("Downloading required certificate (" . $FwListName . \ - " / " . $List->"url" . ") failed, trying anyway."); - } - } - - :for I from=1 to=5 do={ - :if ($Data = false) do={ - :set Data [ :tolf [ $FetchHuge $ScriptName ($List->"url") $CheckCertificate ] ]; - :if ($Data = false) do={ - :if ($I < 5) do={ - $LogPrint debug $ScriptName ("Failed downloading for list '" . $FwListName . \ - "', " . $I . ". try from: " . $List->"url"); - :delay (($I * $I) . "s"); - } - } - } - } - - :if ($Data = false) do={ - :set Data ""; - :set Failure true; - $LogPrint warning $ScriptName ("Failed downloading for list '" . $FwListName . \ - "' from: " . $List->"url"); - } else={ - $LogPrint debug $ScriptName ("Downloaded " . [ $HumanReadableNum [ :len $Data ] 1024 ] . \ - "B for list '" . $FwListName . "' from: " . $List->"url"); - } - - :foreach Line in=[ :deserialize $Data delimiter="\n" from=dsv options=dsv.plain ] do={ - :set Line ($Line->0); - :local Address; - :if ([ :pick $Line 0 1 ] = "{") do={ - :do { - :set Address [ :tostr ([ :deserialize from=json $Line ]->"cidr") ]; - } on-error={ } - } else={ - :set Address ([ :pick $Line 0 [ $FindDelim $Line ] ] . ($List->"cidr")); - } - :do { - :local Branch [ $GetBranch $Address ]; - :if ($Address ~ "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}(/[0-9]{1,2})?\$") do={ - :if ($Address ~ "/32\$") do={ - :set Address [ :pick $Address 0 ([ :len $Address ] - 3) ]; - } - :set ($IPv4Addresses->$Branch->$Address) $TimeOut; - :error true; - } - :if ($Address ~ "^[0-9a-zA-Z]*:[0-9a-zA-Z:\\.]+(/[0-9]{1,3})?\$") do={ - :if ($Address ~ "/128\$") do={ - :set Address [ :pick $Address 0 ([ :len $Address ] - 4) ]; - } - :set ($IPv6Addresses->$Branch->$Address) $TimeOut; - :error true; - } - :if ($Address ~ "^[\\.a-zA-Z0-9-]+\\.[a-zA-Z]{2,}\$") do={ - :set ($IPv4Addresses->$Branch->$Address) $TimeOut; - :set ($IPv6Addresses->$Branch->$Address) $TimeOut; - :error true; - } - } on-error={ } - } - } - - :foreach Entry in=[ /ip/firewall/address-list/find where \ - list=$FwListName comment=$ListComment ] do={ - :local Address [ /ip/firewall/address-list/get $Entry address ]; - :local Branch [ $GetBranch $Address ]; - :local TimeOut ($IPv4Addresses->$Branch->$Address); - :if ([ :typeof $TimeOut ] = "time") do={ - $LogPrintVerbose debug $ScriptName ("Renewing IPv4 address in list '" . $FwListName . \ - "' with " . $TimeOut . ": " . $Address); - /ip/firewall/address-list/set $Entry timeout=$TimeOut; - :set ($IPv4Addresses->$Branch->$Address); - :set CntRenew ($CntRenew + 1); - } else={ - :if ($Failure = false) do={ - $LogPrintVerbose debug $ScriptName ("Removing IPv4 address from list '" . $FwListName . \ - "': " . $Address); - /ip/firewall/address-list/remove $Entry; - :set CntRemove ($CntRemove + 1); - } - } - } - - :foreach Entry in=[ /ipv6/firewall/address-list/find where \ - list=$FwListName comment=$ListComment ] do={ - :local Address [ /ipv6/firewall/address-list/get $Entry address ]; - :local Branch [ $GetBranch $Address ]; - :local TimeOut ($IPv6Addresses->$Branch->$Address); - :if ([ :typeof $TimeOut ] = "time") do={ - $LogPrintVerbose debug $ScriptName ("Renewing IPv6 address in list '" . $FwListName . \ - "' with " . $TimeOut . ": " . $Address); - /ipv6/firewall/address-list/set $Entry timeout=$TimeOut; - :set ($IPv6Addresses->$Branch->$Address); - :set CntRenew ($CntRenew + 1); - } else={ - :if ($Failure = false) do={ - $LogPrintVerbose debug $ScriptName ("Removing IPv6 address from list '" . $FwListName . \ - "': " . $Address); - /ipv6/firewall/address-list/remove $Entry; - :set CntRemove ($CntRemove + 1); - } - } - } - - :foreach BranchName,Branch in=$IPv4Addresses do={ - $LogPrintVerbose debug $ScriptName ("Handling branch: " . $BranchName); - :foreach Address,Timeout in=$Branch do={ - $LogPrintVerbose debug $ScriptName ("Adding IPv4 address to list '" . $FwListName . \ - "' with " . $Timeout . ": " . $Address); - :do { - /ip/firewall/address-list/add list=$FwListName comment=$ListComment \ - address=$Address timeout=$Timeout; - :set CntAdd ($CntAdd + 1); - } on-error={ - $LogPrint warning $ScriptName ("Failed to add IPv4 address to list '" . $FwListName . \ - "': " . $Address); - } - } - } - - :foreach BranchName,Branch in=$IPv6Addresses do={ - $LogPrintVerbose debug $ScriptName ("Handling branch: " . $BranchName); - :foreach Address,Timeout in=$Branch do={ - $LogPrintVerbose debug $ScriptName ("Adding IPv6 address to list '" . $FwListName . \ - "' with " . $Timeout . ": " . $Address); - :do { - /ipv6/firewall/address-list/add list=$FwListName comment=$ListComment \ - address=$Address timeout=$Timeout; - :set CntAdd ($CntAdd + 1); - } on-error={ - $LogPrint warning $ScriptName ("Failed to add IPv6 address to list '" . $FwListName . \ - "': " . $Address); - } - } - } - - $LogPrint info $ScriptName ("list: " . $FwListName . \ - " (" . [ $HumanReadableNum ($CntAdd + $CntRenew) 1000 ] . ")" . \ - " -- added: " . [ $HumanReadableNum $CntAdd 1000 ] . \ - " - renewed: " . [ $HumanReadableNum $CntRenew 1000 ] . \ - " - removed: " . [ $HumanReadableNum $CntRemove 1000 ]); - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/global-config.rsc b/global-config similarity index 52% rename from global-config.rsc rename to global-config index fa32b16..d428da5 100644 --- a/global-config.rsc +++ b/global-config @@ -1,26 +1,24 @@ #!rsc by RouterOS # RouterOS script: global-config -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md +# Copyright (c) 2013-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # # global configuration -# https://rsc.eworm.de/ +# https://git.eworm.de/cgit/routeros-scripts/about/ -# Set this to 'true' to disable news and change notifications. -:global NoNewsAndChangesNotification false; +# Make sure all configuration properties are up to date and this +# value is in sync with value in script 'global-functions'! +:global GlobalConfigVersion 80; -# Add extra text (or emojis) in notification tags. -:global IdentityExtra ""; - -# This is used in DNS scripts ('ipsec-to-dns' and fallback in 'dhcp-to-dns') -# and backup scripts for file names. +# 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. +# These addresses are used to send e-mails to. 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"; @@ -33,16 +31,8 @@ :global TelegramChatId ""; #:global TelegramTokenId "123456:ABCDEF-GHI"; #:global TelegramChatId "12345678"; -# Use this to send notifications to a specific topic in group. -:global TelegramThreadId ""; -# 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: @@ -54,18 +44,9 @@ #:global MatrixAccessToken "123456ABCDEFGHI..."; #:global MatrixRoom "!example:matrix.org"; -# You can send Ntfy notifications. Configure these settings and -# install the module: -# $ScriptInstallUpdate mod/notification-ntfy -:global NtfyServer "ntfy.sh"; -:global NtfyServerUser ""; -:global NtfyServerPass ""; -:global NtfyServerToken ""; -:global NtfyTopic ""; - -# It is possible to override e-mail, Telegram, Matrix and Ntfy setting -# for every script. This is done in arrays, where 'Override' is appended -# to the variable name, like this: +# 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"; @@ -79,7 +60,6 @@ # 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. @@ -88,50 +68,19 @@ :global BackupUploadUrl "sftp://example.com/backup/"; :global BackupUploadUser "mikrotik"; :global BackupUploadPass "v3ry-s3cr3t"; -# Copy the RouterOS installation to backup partition before feature update. -:global BackupPartitionCopyBeforeFeatureUpdate false; - -# This defines the settings for firewall address-lists (fw-addr-lists). -# Warning: Mind your device's resources - memory and processing! -:global FwAddrLists { -# "allow"={ -# { url="https://rsc.eworm.de/main/fw-addr-lists.d/allow"; -# cert="ISRG Root X2"; timeout=1w }; -# }; - "block"={ -# { url="https://rsc.eworm.de/main/fw-addr-lists.d/block"; -# cert="ISRG Root X2" }; - { url="https://raw.githubusercontent.com/stamparm/ipsum/refs/heads/master/levels/4.txt"; -# # higher level (decrease the numerical value) for more addresses, and vice versa - cert="USERTrust RSA Certification Authority" }; - { url="https://www.dshield.org/block.txt"; cidr="/24"; - cert="ISRG Root X1" }; - { url="https://lists.blocklist.de/lists/strongips.txt"; - cert="Certum Trusted Network CA" }; -# { url="https://www.spamhaus.org/drop/drop_v4.json"; -# cert="GTS Root R4" }; -# { url="https://www.spamhaus.org/drop/drop_v6.json"; -# cert="GTS Root R4" }; - }; -# "mikrotik"={ -# { url="https://rsc.eworm.de/main/fw-addr-lists.d/mikrotik"; -# cert="ISRG Root X2"; timeout=1w }; -# }; -}; -:global FwAddrListTimeOut 1d; # This defines what log messages to filter or include by topic or message -# text. Regular expressions are supported. An empty string has a special -# meaning not to filter or include anything. +# 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|packet|raw)"; -:global LogForwardFilterMessage ""; +: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 []; +:global LogForwardIncludeMessage []; #:global LogForwardInclude "account"; #:global LogForwardIncludeMessage "message text"; @@ -143,13 +92,8 @@ :global SafeUpdatePatch false; # Allow to install updates automatically if seen in neighbor list. :global SafeUpdateNeighbor false; -:global SafeUpdateNeighborIdentity ""; -# Install *ALL* updates automatically! -# Set to all upper-case "Yes, please!" to enable. -:global SafeUpdateAll "no"; - -# Defer the reboot for night on automatic (non-interactive) update -:global PackagesUpdateDeferReboot false; +# Allow to install updates even if device is managed by CAPsMAN. +:global SafeUpdateOnCap false; # These thresholds control when to send health notification # on temperature and voltage. @@ -158,16 +102,15 @@ cpu-temperature=70; board-temperature1=50; board-temperature2=50; -}; +} # This is deviation on recovery threshold against notification flooding. -:global CheckHealthTemperatureDeviation 3; +:global CheckHealthTemperatureDeviation 2; :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"; @@ -181,18 +124,14 @@ "Staking"; "Thundering"; "Ultra"; "Unreal" }; { "Belief"; "Button"; "Curtain"; "Edge"; "Jewel"; "String"; "Whistle" } -}; - -# Specify how to assemble DNS names in ipsec-to-dns. -:global HostNameInZone true; -:global PrefixInZone true; +} # Run different commands with multiple mode-button presses. :global ModeButton { - 1="/system/leds/settings/set all-leds-off=(({ \"never\"=\"immediate\"; \"immediate\"=\"never\" })->[ get all-leds-off ]);"; - 2=":global Identity; :global SendNotification; :global SymbolForNotification; \$SendNotification ([ \$SymbolForNotification \"earth\" ] . \"Hello...\") (\"Hello world, \" . \$Identity . \" calling!\");"; - 3="/system/shutdown;"; - 4="/system/reboot;"; + 1="/ system script run leds-toggle-mode;"; + 2=":global SendNotification; :global Identity; \$SendNotification (\"Hello...\") (\"Hello world, \" . \$Identity . \" calling!\");"; + 3="/ system shutdown;"; + 4="/ system reboot;"; 5=":global BridgePortVlan; \$BridgePortVlan alt;"; # add more here... }; @@ -202,34 +141,29 @@ # Run commands on SMS action. :global SmsAction { bridge-port-vlan-alt=":global BridgePortVlan; \$BridgePortVlan alt;"; - reboot="/system/reboot;"; - shutdown="/system/shutdown;"; + 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 address should resolve ntp servers and is used to update +# ntp settings. A pool can rotate servers. +:global NtpPool "pool.ntp.org"; # This is the address used to send gps data to. :global GpsTrackUrl "https://example.com/index.php"; -# This is the base url to fetch scripts from. -:global ScriptUpdatesBaseUrl "https://rsc.eworm.de/main/"; +# 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://rsc.eworm.de/next/"; -#:global ScriptUpdatesBaseUrl "https://git.eworm.de/cgit/routeros-scripts/plain/"; #: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 my git url (git.eworm.de) -#:global ScriptUpdatesUrlSuffix "?h=next"; +# use next branch with default url (git.eworm.de) +#:global ScriptUpdatesUrlSuffix "\?h=next"; # Use this for defaults with $ScriptRunOnce # Install module with: @@ -240,7 +174,7 @@ # 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://rsc.eworm.de/#donate +# https://git.eworm.de/cgit/routeros-scripts/about/#donate # Enable this to silence donation hint. :global IDonate false; @@ -251,21 +185,16 @@ :global CertRenewPass { "v3ry-s3cr3t"; "4n0th3r-s3cr3t"; -}; -:global CertWarnTime 2w; +} :global CertIssuedExportPass { "cert1-cn"="v3ry-s3cr3t"; "cert2-cn"="4n0th3r-s3cr3t"; -}; - -# load custom settings from overlay and snippets -# Warning: Do *NOT* copy this code to overlay! -:foreach Script in=([ /system/script/find where name="global-config-overlay" ], \ - [ /system/script/find where name~"^global-config-overlay.d/" ]) do={ - :do { - /system/script/run $Script; - } on-error={ - :log error ("Loading configuration from overlay or snippet " . \ - [ /system/script/get $Script name ] . " failed!"); - } +} + +# 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-config-overlay b/global-config-overlay new file mode 100644 index 0000000..b5be658 --- /dev/null +++ b/global-config-overlay @@ -0,0 +1,19 @@ +# Overlay for global configuration by RouterOS Scripts +# Copyright (c) 2013-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# global configuration, custom overlay +# https://git.eworm.de/cgit/routeros-scripts/about/ + +# Make sure all configuration properties are up to date and this +# value is in sync with value in script 'global-functions'! +# Comment or remove to disable news and change notifications. +:global GlobalConfigVersion 79; + +# Use branch routeros-v6 with RouterOS v6: +:global ScriptUpdatesUrlSuffix "\?h=routeros-v6"; + +# Copy configuration from global-config here and modify it. + + +# End of global-config-overlay diff --git a/global-config-overlay.rsc b/global-config-overlay.rsc deleted file mode 100644 index 9afaceb..0000000 --- a/global-config-overlay.rsc +++ /dev/null @@ -1,12 +0,0 @@ -# Overlay for global configuration by RouterOS Scripts -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# global configuration, custom overlay -# https://rsc.eworm.de/#editing-configuration - -# Copy relevant configuration from global-config, paste and modify it here. -# https://rsc.eworm.de/global-config.rsc - - -# End of global-config-overlay diff --git a/global-config.changes b/global-config.changes new file mode 100644 index 0000000..19630e9 --- /dev/null +++ b/global-config.changes @@ -0,0 +1,99 @@ +# News, changes and migration by RouterOS Scripts +# Copyright (c) 2019-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md + +# Changes for global-config to be added to notification on script updates +:global GlobalConfigChanges { + 1="Moved variables from 'global-config' to 'global-functions' for independence"; + 2="Variable names became CamelCase to work around scripting issues"; + 3="Variable for certificate renew passphrase became an array to support multiple passphrases"; + 4="Added option to ignore global-config changes"; + 5="Split off new script 'cloud-backup' from 'email-backup'"; + 6="Introduced script 'upload-backup' with new configuration parameters"; + 7="Introduced script 'check-health' with new configuration parameters"; + 8="Added donation hint and option to silence it"; + 9="Introduced configuration overlay 'global-config-overlay'"; + 10="Made health threshold for voltage configurable"; + 11="Introduced function '\$ScriptInstallUpdate' to install new and update existing scripts"; + 12="Removed '\$ScriptUpdatesConfigChangesIgnore', comment '\$GlobalConfigVersion' in 'global-config-overlay' to disable change notifications"; + 13="Configuration for script 'bridge-port-to-default' changed with new syntax in comment"; + 14="Dropped script 'script-updates', use '\$ScriptInstallUpdate' exclusively!"; + 15="New documentation is online! https://git.eworm.de/cgit/routeros-scripts/about/#available-scripts"; + 16="Happy with RouterOS Scripts and have a GitHub and/or GitLab account? Please star!"; + 17="Introduced script 'early-errors'"; + 18=("Added a simple IP calculation function, try: \$IPCalc " . [ / ip address get ([ find ]->0) address ]); + 19="Commenting scripts with 'ignore', 'base-url=...' and 'url-suffix=...' is honored on update"; + 20="Added support for hooks to 'netwatch-notify'"; + 21="Added support for installing patch updates automatically by 'check-routeros-update'"; + 22="Dropped '\$ScriptUpdatesIgnore' from global configuration, auto-migrating to ignore flag in comment" + 23="Added 'log-forward' with configurable filter, which replaces 'early-errors'"; + 24="Made symbols in notifications configurable."; + 25="Added support for DHCP server name in DNS FQDN via '\$ServerNameInZone'"; + 26="Made check count threshold in 'netwatch-notify' configurable."; + 27="Added queue for Telegram notifications to resend later on error."; + 28="Made 'dhcp-to-dns' act on all bound leases, not just dynamic ones."; + 29="Added filter on log message text for 'log-forward'."; + 30="Implemented simple rate limit for 'log-forward' to prevent flooding."; + 31="Switched Telegram notifications to fixed-width font, with opt-out."; + 32="Merged mode (& reset) button scripts in single new script 'mode-button'."; + 33="Added configurable deviation on health temperature recovery threshold against notification flooding."; + 34="Introduced script 'ospf-to-leds' to visualize OSPF instance state via LEDs."; + 35="Implemented visual feedback for 'mode-button' with configurable LED."; + 36="Added support for installing updates automatically if seen in neighbor list."; + 37="Implemented simple dependency model in 'netwatch-notify'."; + 38="Imported new Let's Encrypt intermediate certificate 'R3'."; + 39="Added support for interface specific address list entries in 'ipv6-update'."; + 40="Made the certificate renewal time configurable."; + 41="Implemented migration mechanism for script updates."; + 42="Made severity in terminal output colorful, with opt-out."; + 43="Added queue for e-mail notifications to resend later on error."; + 44="Dropped script 'global-wait', all scripts wait on their own now."; + 45="We have a Telegram Group! Come along and say hello: https://t.me/routeros_scripts"; + 46="Added configurable random delay in backup scripts to stretch execution and prevent resource congestion."; + 47="Removed obsolete intermediate certificate 'Let's Encrypt Authority X3' from store."; + 48="Added support for overriding e-mail and Telegram settings for every script."; + 49="Dropped '\$EmailBackupTo' & '\$EmailBackupCc' from configuration, use settings override if required."; + 50="Added support for dynamic address update in 'netwatch-notify'."; + 51="Added 'ipsec-to-dns' to add DNS records for IPSec peers from mode-config."; + 52="Updated Let's Encrypt trust chain to use root certificate 'ISRG Root X1'. Do not re-import the old chain!"; + 53="Added support to send notifications via Matrix."; + 54="Support for Telegram notifications moved to a module. It is installed automatically if required."; + 55="Added reverse logic in 'log-forward', so messages can be included even if filtered before."; + 56="Added tags in all backup, lease and ppp-on-up scripts. These are used by 'packages-update', 'lease-script' and 'ppp-on-up' to find the scripts."; + 57="Celebrating the 1.000th commit - Hooray!"; + 58="Added a cleanup script for 'hotspot-to-wpa' to purge old access list entries."; + 59="Updating CAP with 'check-routeros-update' is now possible with opt-in."; + 60="Implemented a pre-down hook in 'netwatch-notify' that fires at two thirds of failed checks."; + 61="Finally removed old scripts."; + 62="Added '\$ScriptRunOnce' to run a script from URL once without installation, intended to aid configuration management and the like."; + 63="Moved optional functions '\$IPCalc' and '\$ScriptRunOnce' to modules."; + 64="Implemented '\$InspectVar' in module to inspect variables."; + 65="Added module to manage VLANs on bridge ports."; + 66="Moved script 'bridge-port-to-default' to new module."; + 67="Moved modules to directory with shorter name."; + 68="Reintroduced 'global-wait' for functions in scheduler."; + 69="Support hard lower limit for voltage in 'check-health'."; + 70="MikroTik started pushing RouterOS v7. Changes are required if you run it, see https://git.eworm.de/cgit/routeros-scripts/about/#requirements"; + 71="MikroTik is pushing RouterOS v7 even more, in parallel branches. If you want to keep RouterOS v6 for some time see https://git.eworm.de/cgit/routeros-scripts/about/#requirements"; + 72="Introduced new script 'netwatch-dns' to manage DNS and DoH servers from netwatch."; + 73="Renamed backup scripts ('cloud-backup' -> 'backup-cloud', 'email-backup' -> 'backup-email', 'upload-backup' -> 'backup-upload')."; + 74="Extended 'hotspot-to-wpa', it can now read additional configuration from templates and hotspot users."; + 75="You are using the branch 'routeros-v6', well done."; + 76="Added an option to suppress notifications on host down with 'netwatch-notify'."; + 77="Introduced new script 'firmware-upgrade-reboot'. Handle with care!"; + 78="New documentation is online for notifications via Telegram & Matrix, variable inspection, ip address calculation and running scripts once."; + 79="Introduced new script 'backup-partition' to save configuration to fallback partition."; + 80="The 'routeros-v6' branch entered soft freeze state and will receive important updates only. Happy to welcome you in 'main' branch once moving to RouterOS v7!"; +}; + +# Migration steps to be applied on script updates +:global GlobalConfigMigration { + 41=":global SendNotification; \$SendNotification (\"Migration mechanism\") (\"Congratulations!\nSuccessfully tested the new migration mechanism.\");"; + 47="/ certificate remove [ find where fingerprint=\"731d3d9cfaa061487a1d71445a42f67df0afca2a6c2d2f98ff7b3ce112b1f568\" or fingerprint=\"25847d668eb4f04fdd40b12b6b0740c567da7d024308eb6c2c96fe41d9de218d\" ];"; + 52=":global CertificateDownload; :if ([ :len [ / certificate find where fingerprint=\"67add1166b020ae61b8f5fc96813c04c2aa589960796865572a3c7e737613dfd\" or fingerprint=\"96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6\" ] ] < 2) do={ \$CertificateDownload \"R3\"; }; / certificate remove [ find where fingerprint=\"0687260331a72403d909f105e69bcf0d32e1bd2493ffc6d9206d11bcd6770739\" ];"; + 54=":global ScriptInstallUpdate; :global TelegramTokenId; :global TelegramChatId; :if ([ :len \$TelegramTokenId ] > 0 && [ :len \$TelegramChatId ] > 0) do={ \$ScriptInstallUpdate mod/notification-telegram; }"; + 61="/ system script remove [ find where name~\"^(early-errors|mode-button-(event|scheduler)|script-updates)\\\$\" source~\"^#!rsc by RouterOS\\n\" ];"; + 66=":global ScriptInstallUpdate; :if ([ :len [ / system script find where name=\"bridge-port-to-default\" ] ] > 0) do={ / system script remove [ find where name~\"^bridge-port-to(-default|ggle)\\\$\" ]; \$ScriptInstallUpdate mod/bridge-port-to; }"; + 67=":global ScriptInstallUpdate; :global CharacterReplace; :foreach Script in=[ / system script find where name~\"^global-functions.d/\" ] do={ / system script set name=[ \$CharacterReplace [ / system script get \$Script name ] \"global-functions.d/\" \"mod/\" ] \$Script; }; \$ScriptInstallUpdate;"; + 73=":global ScriptInstallUpdate; :global CharacterReplace; :foreach Old,New in={ \"cloud-backup\"=\"backup-cloud\"; \"email-backup\"=\"backup-email\"; \"upload-backup\"=\"backup-upload\" } do={ / system script set name=\$New [ find where name=\$Old ]; :foreach Scheduler in=[ / system scheduler find where on-event~\$Old ] do={ / system scheduler set \$Scheduler name=[ \$CharacterReplace [ get \$Scheduler name ] \$Old \$New ] on-event=[ \$CharacterReplace [ get \$Scheduler on-event ] \$Old \$New ]; }; }; \$ScriptInstallUpdate;"; +}; diff --git a/global-functions b/global-functions new file mode 100644 index 0000000..172173e --- /dev/null +++ b/global-functions @@ -0,0 +1,1295 @@ +#!rsc by RouterOS +# RouterOS script: global-functions +# Copyright (c) 2013-2022 Christian Hesse +# Michael Gisbers +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# global functions +# https://git.eworm.de/cgit/routeros-scripts/about/ + +:local 0 "global-functions"; + +# expected configuration version +:global ExpectedConfigVersion 80; + +# 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 DefaultRouteIsReachable; +:global DeviceInfo; +:global DNSIsResolving; +:global DownloadPackage; +:global EitherOr; +:global EscapeForRegEx; +:global FlushEmailQueue; +:global GetMacVendor; +:global GetRandom20CharHex; +:global GetRandomNumber; +:global HexToNum; +:global IfThenElse; +:global LogPrintExit2; +:global MkDir; +:global NotificationFunctions; +:global ParseKeyValueStore; +:global QuotedPrintable; +:global RandomDelay; +:global Read; +:global RequiredRouterOS; +:global ScriptFromTerminal; +:global ScriptInstallUpdate; +:global ScriptLock; +:global SendEMail; +:global SendEMail2; +:global SendNotification; +:global SendNotification2; +:global SymbolByUnicodeName; +:global SymbolForNotification; +:global TimeIsSync; +: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; +} + +# default route is reachable +:set DefaultRouteIsReachable do={ + :if ([ :len [ / ip route find where dst-address=0.0.0.0/0 active !blackhole !routing-mark !unreachable ] ] > 0) do={ + :return true; + } + :return false; +} + +# get readable device info +:set DeviceInfo do={ + :global ExpectedConfigVersion; + :global GlobalConfigVersion; + :global Identity; + + :global IfThenElse; + + :local Resource [ / system resource get ]; + :local RouterBoard; + :do { + :set RouterBoard [ / 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 Current: " . $GlobalConfigVersion . \ + [ $IfThenElse ($GlobalConfigVersion != $ExpectedConfigVersion) \ + ("\n Expected: " . $ExpectedConfigVersion) ]); +} + +# check if DNS is resolving +:set DNSIsResolving do={ + :global CharacterReplace; + + :do { + :resolve "low-ttl.eworm.de"; + } on-error={ + :return false; + } + :return true; +} + +# 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 VersionToNum; + :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 ]; } + + :local PkgFile ($PkgName . "-" . $PkgVer . "-" . $PkgArch . ".npk"); + :if ([ $VersionToNum $PkgVer ] < [ $VersionToNum "7.0" ] && $PkgName = "routeros") do={ + :set PkgFile ($PkgName . "-" . $PkgArch . "-" . $PkgVer . ".npk"); + } + :if ($PkgArch = "x86_64" || $PkgName ~ "^routeros-") 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 "ISRG Root X1" ] = 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; +} + +# flush e-mail queue +:set FlushEmailQueue do={ + :global EmailQueue; + + :global EitherOr; + :global LogPrintExit2; + + :local AllDone true; + :local QueueLen [ :len $EmailQueue ]; + + :if ([ :len [ / system scheduler find where name="FlushEmailQueue" ] ] > 0 && $QueueLen = 0) do={ + $LogPrintExit2 warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.") false; + } + + / system scheduler set interval=($QueueLen . "m") [ find where name="FlushEmailQueue" ]; + + :foreach Id,Message in=$EmailQueue do={ + :if ([ :typeof $Message ] = "array" ) do={ + :local Attach [ $EitherOr ($Message->"attach") "" ]; + :while ([ / tool e-mail get last-status ] = "in-progress") do={ :delay 1s; } + / 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=[ :toarray $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 [ find where name="FlushEmailQueue" ]; + :set EmailQueue; + } else={ + / system scheduler set interval=1m [ find where name="FlushEmailQueue" ]; + } +} + +# get MAC vendor +:set GetMacVendor do={ + :local Mac [ :tostr $1 ]; + + :global CertificateAvailable; + :global LogPrintExit2; + + :do { + :if ([ $CertificateAvailable "GTS Root R4" ] = 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 hex (0-9 and a-f) +:set GetRandom20CharHex do={ + :return ([ / certificate scep-server otp generate minutes-valid=0 as-value ]->"password"); +} + +# generate random number +:set GetRandomNumber do={ + :local Max 4294967295; + :if ([ :typeof $1 ] != "nothing" ) do={ + :set Max ([ :tonum $1 ] + 1); + } + + :global GetRandom20CharHex; + :global HexToNum; + + :return ([ $HexToNum [ :pick [ $GetRandom20CharHex ] 0 15 ] ] % $Max); +} + +# 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; +} + +# 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 "" ] . ": " . $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={ + :if ($Exit = "true") do={ + :error ([ $PrintSeverity $Severity ] . ": " . $Message); + } else={ + :put ([ $PrintSeverity $Severity ] . ": " . $Message); + } + } +} + +# create directory +:set MkDir do={ + :local Dir [ :tostr $1 ]; + + :global CleanFilePath; + :global GetRandom20CharHex; + :global LogPrintExit2; + :global WaitForFile; + + :set Dir [ $CleanFilePath $Dir ]; + + :if ($Dir = "") do={ + :return true; + } + + :if ([ :len [ / file find where name=$Dir type="directory" ] ] = 1) do={ + :return true; + } + + :local Return true; + :local Name ($Dir . "-" . [ $GetRandom20CharHex ]); + :do { + / ip smb share add disabled=yes directory=$Dir name=$Name; + $WaitForFile $Dir; + } on-error={ + $LogPrintExit2 warning $0 ("Making directory '" . $Dir . "' failed!") false; + :set Return false; + } + / ip smb share remove [ find where name=$Name ]; + :return $Return; +} + +# prepare NotificationFunctions array +:if ([ :typeof $NotificationFunctions ] != "array") do={ + :set NotificationFunctions [ :toarray "" ]; +} + +# send notification via e-mail - expects one array argument +:set ($NotificationFunctions->"email") do={ + :local Notification $1; + + :global Identity; + :global EmailGeneralTo; + :global EmailGeneralToOverride; + :global EmailGeneralCc; + :global EmailGeneralCcOverride; + :global EmailQueue; + + :global EitherOr; + :global IfThenElse; + :global QuotedPrintable; + + :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 [ :toarray "" ]; + } + :local Signature [ / system note get note ]; + :set ($EmailQueue->[ :len $EmailQueue ]) { + to=$To; cc=$Cc; + subject=[ $QuotedPrintable ("[" . $Identity . "] " . ($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 \ + on-event=(":global FlushEmailQueue; \$FlushEmailQueue;"); + } +} + +# parse key value store +:set ParseKeyValueStore do={ + :local Source $1; + :if ([ :typeof $Source ] != "array") do={ + :set Source [ :tostr $1 ]; + } + :local Result [ :toarray "" ]; + :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; +} + +# 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 . "\?="); +} + +# 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 ([ $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 GlobalConfigVersion; + :global Identity; + :global IDonate; + :global NotificationsWithSymbols; + :global ScriptUpdatesBaseUrl; + :global ScriptUpdatesFetch; + :global ScriptUpdatesUrlSuffix; + :global SentConfigChangesNotification; + + :global CertificateAvailable; + :global IfThenElse; + :global LogPrintExit2; + :global ParseKeyValueStore; + :global SendNotification2; + :global SymbolForNotification; + :global ValidateSyntax; + + :if ([ $CertificateAvailable "ISRG Root X2" ] = 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") ]; + :local SourceNew; + :if ([ :len $ScriptFile ] > 0) do={ + :set SourceNew [ / file get $ScriptFile content ]; + / 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" . $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={ + $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={ + :if ([ $ValidateSyntax $SourceNew ] = true) do={ + :local DontRequirePermissions \ + ($SourceNew~"\n# requires: dont-require-permissions=yes\n"); + $LogPrintExit2 info $0 ("Updating script: " . $ScriptVal->"name") false; + / system script set owner=($ScriptVal->"name") source=$SourceNew \ + dont-require-permissions=$DontRequirePermissions $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 ("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={ + :global GlobalConfigChanges; + :global GlobalConfigMigration; + :local ChangeLogCode; + + :do { + :local Url ($ScriptUpdatesBaseUrl . "global-config.changes" . $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; + } + } + } + } + + :if ($SentConfigChangesNotification != $ExpectedConfigVersion && \ + $GlobalConfigVersion < $ExpectedConfigVersion) do={ + :local NotificationMessage ("Current configuration on " . $Identity . \ + " is out of date. Please update global-config-overlay, then increase " . \ + "\$GlobalConfigVersion (currently " . $GlobalConfigVersion . \ + ") to " . $ExpectedConfigVersion . " and re-run global-config."); + $LogPrintExit2 info $0 ($NotificationMessage) false; + + :if ([ :len $GlobalConfigChanges ] > 0) do={ + :set NotificationMessage ($NotificationMessage . "\n\nChanges:"); + :for I from=($GlobalConfigVersion + 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."); + } + + :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 SentConfigChangesNotification $ExpectedConfigVersion; + } + + :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 GetRandom20CharHex; + :global IfThenElse; + :global LogPrintExit2; + + :global ScriptLockOrder; + :if ([ :typeof $ScriptLockOrder ] = "nothing") do={ + :set ScriptLockOrder [ :toarray "" ]; + } + :if ([ :typeof ($ScriptLockOrder->$Script) ] = "nothing") do={ + :set ($ScriptLockOrder->$Script) [ :toarray "" ]; + } + + :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) [ :toarray "" ]; + } + + :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) [ :toarray "" ]; + / system script job remove [ find where script=$Script ]; + } + + :local MyTicket [ $GetRandom20CharHex ]; + $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 e-mail - expects at lease 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; +} + +# send notification via NotificationFunctions - expects at lease 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 { + "alarm-clock"="\E2\8F\B0"; + "calendar"="\F0\9F\93\85"; + "chart-decreasing"="\F0\9F\93\89"; + "chart-increasing"="\F0\9F\93\88"; + "cloud"="\E2\98\81"; + "cross-mark"="\E2\9D\8C"; + "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"; + "mobile-phone"="\F0\9F\93\B1"; + "pushpin"="\F0\9F\93\8C"; + "scissors"="\E2\9C\82"; + "sparkles"="\E2\9C\A8"; + "up-arrow"="\E2\AC\86"; + "warning-sign"="\E2\9A\A0"; + "white-heavy-check-mark"="\E2\9C\85" + } + + :return ($Symbols->$1); +} + +# 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 . " "); +} + +# check if system time is sync +:set TimeIsSync do={ + :global LogPrintExit2; + + :if ([ / system ntp client get enabled ] = true) do={ + :do { + :if ([ / system ntp client get status ] = "synchronized") do={ + :return true; + } + } on-error={ + :if ([ :typeof [ / system ntp client get last-adjustment ] ] = "time") do={ + :return true; + } + } + :return false; + } + + :if ([ / ip cloud get ddns-enabled ] = true && [ / ip cloud get update-time ] = true) do={ + :if ([ :typeof [ / ip cloud get public-address ] ] = "ip") do={ + :return true; + } + :return false; + } + + $LogPrintExit2 debug $0 ("No time source configured! Returning gracefully...") false; + :return true; +} + +# 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 DefaultRouteIsReachable; + + :while ([ $DefaultRouteIsReachable ] = false) do={ + :delay 1s; + } +} + +# wait for DNS to resolve +:set WaitDNSResolving do={ + :global DNSIsResolving; + + :while ([ $DNSIsResolving ] = false) do={ + :delay 1s; + } +} + +# wait for file to be available +:set WaitForFile do={ + :local FileName [ :tostr $1 ]; + + :global CleanFilePath; + + :set FileName [ $CleanFilePath $FileName ]; + :local I 0; + + :while ([ :len [ / file find where name=$FileName ] ] = 0) do={ + :if ($I > 20) do={ + :return false; + } + :delay 100ms; + :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 LogPrintExit2; + :global TimeIsSync; + + :while ([ $TimeIsSync ] = false) do={ + :if ([ :len [ / system script find where name="rotate-ntp" ] ] > 0 && \ + ([ / system resource get uptime ] % (180 * 1000000000)) = 0s) do={ + :do { + / system script run rotate-ntp; + } on-error={ + $LogPrintExit2 debug $0 ("Running rotate-ntp failed.") false; + } + } + :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; + } +} + +# check for required RouterOS version +$RequiredRouterOS $0 "6.47" true; + +# ... and give a hint on RouterOS v7. +:if ([ $RequiredRouterOS $0 "7.0" false ] = true) do={ + $LogPrintExit2 warning $0 ("RouterOS v7 brings some incompatible changes. Please switch to main branch!") false; +} + +# signal we are ready +:set GlobalFunctionsReady true; diff --git a/global-functions.rsc b/global-functions.rsc deleted file mode 100644 index 8ae7bb8..0000000 --- a/global-functions.rsc +++ /dev/null @@ -1,1777 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: global-functions -# Copyright (c) 2013-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch, scheduler -# -# global functions -# https://rsc.eworm.de/ - -:local ScriptName [ :jobname ]; - -# Git commit id & info, expected configuration version -:global CommitId "unknown"; -:global CommitInfo "unknown"; -:global ExpectedConfigVersion 135; - -# global variables not to be changed by user -:global GlobalFunctionsReady false; -:global Identity [ /system/identity/get name ]; - -# global functions -:global AlignRight; -:global CertificateAvailable; -:global CertificateDownload; -:global CertificateNameByCN; -:global CharacterMultiply; -:global CharacterReplace; -:global CleanFilePath; -:global CleanName; -:global DeviceInfo; -:global Dos2Unix; -:global DownloadPackage; -:global EitherOr; -:global EscapeForRegEx; -:global ExitError; -:global FetchHuge; -:global FetchUserAgentStr; -:global FormatLine; -:global FormatMultiLines; -:global GetMacVendor; -:global GetRandom20CharAlNum; -:global GetRandom20CharHex; -:global GetRandomNumber; -:global Grep; -:global HexToNum; -:global HumanReadableNum; -:global IfThenElse; -:global IsDefaultRouteReachable; -:global IsDNSResolving; -:global IsFullyConnected; -:global IsMacLocallyAdministered; -:global IsTimeSync; -:global LogPrint; -:global LogPrintOnce; -:global LogPrintVerbose; -:global MAX; -:global MIN; -:global MkDir; -:global NotificationFunctions; -:global ParseDate; -:global ParseKeyValueStore; -:global PrettyPrint; -:global ProtocolStrip; -:global RandomDelay; -:global RequiredRouterOS; -:global RmDir; -:global RmFile; -: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; - -# align string to the right -:set AlignRight do={ - :local Input [ :tostr $1 ]; - :local Len [ :tonum $2 ]; - - :global CharacterMultiply; - :global EitherOr; - - :set Len [ $EitherOr $Len 8 ]; - :local Spaces [ $CharacterMultiply " " $Len ]; - - :return ([ :pick $Spaces 0 ($Len - [ :len $Input ]) ] . $Input); -} - -# check and download required certificate -:set CertificateAvailable do={ - :local CommonName [ :tostr $1 ]; - - :global CertificateDownload; - :global LogPrint; - :global ParseKeyValueStore; - - :if ([ /system/resource/get free-hdd-space ] < 8388608 && \ - [ /certificate/settings/get crl-download ] = true && \ - [ /certificate/settings/get crl-store ] = "system") do={ - $LogPrint warning $0 ("This system has low free flash space but " . \ - "is configured to download certificate CRLs to system!"); - } - - :if ([ :len $CommonName ] = 0) do={ - $LogPrint warning $0 ("No CommonName given!"); - :return false; - } - - :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={ - $LogPrint info $0 ("Certificate with CommonName '" . $CommonName . "' not available."); - :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={ - $LogPrint info $0 ("Certificate chain for '" . $CommonName . \ - "' is incomplete, missing '" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "'."); - :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 CertificateAvailable; - :global CertificateNameByCN; - :global CleanName; - :global FetchUserAgentStr; - :global LogPrint; - :global RmFile; - :global WaitForFile; - - $LogPrint info $0 ("Downloading and importing certificate with " . \ - "CommonName '" . $CommonName . "'."); - :local FileName ([ $CleanName $CommonName ] . ".pem"); - :do { - /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgentStr $0 ] }) \ - ($ScriptUpdatesBaseUrl . "certs/" . $FileName . $ScriptUpdatesUrlSuffix) \ - dst-path=$FileName as-value; - $WaitForFile $FileName; - } on-error={ - $LogPrint warning $0 ("Failed downloading certificate with CommonName '" . $CommonName . \ - "' from repository! Trying fallback to mkcert.org..."); - :do { - :if ([ $CertificateAvailable "ISRG Root X1" ] = false) do={ - $LogPrint error $0 ("Downloading required certificate failed."); - :return false; - } - /tool/fetch check-certificate=yes-without-crl http-header-field=({ [ $FetchUserAgentStr $0 ] }) \ - "https://mkcert.org/generate/" http-data=[ :serialize to=json ({ $CommonName }) ] \ - dst-path=$FileName as-value; - $WaitForFile $FileName; - :if ([ /file/get $FileName size ] = 0) do={ - $RmFile $FileName; - :error false; - } - } on-error={ - $LogPrint warning $0 ("Failed downloading certificate with CommonName '" . $CommonName . "'!"); - :return false; - } - } - - /certificate/import file-name=$FileName passphrase="" as-value; - :delay 1s; - $RmFile $FileName; - - :if ([ :len [ /certificate/find where common-name=$CommonName ] ] = 0) do={ - /certificate/remove [ find where name~("^" . $FileName . "_[0-9]+\$") ]; - $LogPrint warning $0 ("Certificate with CommonName '" . $CommonName . "' still unavailable!"); - :return false; - } - - :foreach Cert in=[ /certificate/find where name~("^" . $FileName . "_[0-9]+\$") ] do={ - $CertificateNameByCN [ /certificate/get $Cert common-name ]; - } - :return true; -} - -# name a certificate by its common-name -:set CertificateNameByCN do={ - :local CommonName [ :tostr $1 ]; - - :global CleanName; - - :local Cert [ /certificate/find where common-name=$CommonName ]; - /certificate/set $Cert name=[ $CleanName $CommonName ]; -} - -# multiply given character(s) -:set CharacterMultiply do={ - :local Return ""; - :for I from=1 to=$2 do={ - :set Return ($Return . $1); - } - :return $Return; -} - -# 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; -} - -# clean name for DNS, file and more -:set CleanName do={ - :local Input [ :tostr $1 ]; - - :local Return ""; - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :if ([ :typeof [ find "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" $Char ] ] = "nil") do={ - :do { - :if ([ :len $Return ] = 0) do={ - :error true; - } - :if ([ :pick $Return ([ :len $Return ] - 1) ] = "-") do={ - :error true; - } - :set Char "-"; - } on-error={ - :set Char ""; - } - } - :set Return ($Return . $Char); - } - :return $Return; -} - -# get readable device info -:set DeviceInfo do={ - :global CommitId; - :global CommitInfo; - :global ExpectedConfigVersion; - :global Identity; - - :global IfThenElse; - :global FormatLine; - - :local License [ /system/license/get ]; - :local Resource [ /system/resource/get ]; - :local RouterBoard; - :do { - :set RouterBoard [[ :parse "/system/routerboard/get" ]]; - } on-error={ } - :local Snmp [ /snmp/get ]; - :local Update [ /system/package/update/get ]; - - :return ( \ - [ $FormatLine "Hostname" $Identity ] . "\n" . \ - [ $IfThenElse ([ :len ($Snmp->"location") ] > 0) \ - ([ $FormatLine "Location" ($Snmp->"location") ] . "\n") ] . \ - [ $IfThenElse ([ :len ($Snmp->"contact") ] > 0) \ - ([ $FormatLine "Contact" ($Snmp->"contact") ] . "\n") ] . \ - "Hardware:\n" . \ - [ $FormatLine " Board" ($Resource->"board-name") ] . "\n" . \ - [ $FormatLine " Arch" ($Resource->"architecture-name") ] . "\n" . \ - [ $IfThenElse ($RouterBoard->"routerboard" = true) \ - ([ $FormatLine " Model" ($RouterBoard->"model") ] . \ - [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \ - (" " . $RouterBoard->"revision") ] . "\n" . \ - [ $FormatLine " Serial" ($RouterBoard->"serial-number") ] . "\n") ] . \ - [ $IfThenElse ([ :len ($License->"nlevel") ] > 0) \ - ([ $FormatLine " License" ("level " . ($License->"nlevel")) ] . "\n") ] . \ - "RouterOS:\n" . \ - [ $IfThenElse ([ :len ($License->"level") ] > 0) \ - ([ $FormatLine " License" ("level " . ($License->"level")) ] . "\n") ] . \ - [ $FormatLine " Channel" ($Update->"channel") ] . "\n" . \ - [ $FormatLine " Installed" ($Update->"installed-version") ] . "\n" . \ - [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \ - $Update->"installed-version" != $Update->"latest-version") \ - ([ $FormatLine " Available" ($Update->"latest-version") ] . "\n") ] . \ - [ $IfThenElse ($RouterBoard->"routerboard" = true && \ - $RouterBoard->"current-firmware" != $RouterBoard->"upgrade-firmware") \ - ([ $FormatLine " Firmware" ($RouterBoard->"current-firmware") ] . "\n") ] . \ - "RouterOS-Scripts:\n" . \ - [ $IfThenElse ($CommitId != "unknown") \ - ([ $FormatLine " Commit" ($CommitInfo . "/" . [ :pick $CommitId 0 8 ]) ] . "\n") ] . \ - [ $FormatLine " Version" $ExpectedConfigVersion ]); -} - -# convert line endings, DOS -> UNIX -:set Dos2Unix do={ - :return [ :tolf [ :tostr $1 ] ]; -} - -# 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 LogPrint; - :global MkDir; - :global RmFile; - :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={ - $LogPrint warning $0 ("Failed creating directory, not downloading package."); - :return false; - } - - :if ([ :len [ /file/find where name=$PkgDest type="package" ] ] > 0) do={ - $LogPrint info $0 ("Package file " . $PkgName . " already exists."); - :return true; - } - - :if ([ $CertificateAvailable "ISRG Root X1" ] = false) do={ - $LogPrint error $0 ("Downloading required certificate failed."); - :return false; - } - - :local Url ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile); - $LogPrint info $0 ("Downloading package file '" . $PkgName . "'..."); - $LogPrint debug $0 ("... from url: " . $Url); - :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={ - $LogPrint debug $0 ("Downloading package file failed."); - } - - $RmFile $PkgDest; - :set Retry ($Retry - 1); - } - - $LogPrint warning $0 ("Downloading package file '" . $PkgName . "' failed."); - :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 ]; - } - :if ([ :typeof $1 ] = "time") do={ - :return [ $IfThenElse ($1 > 0s) $1 $2 ]; - } - # this works for boolean values, literal ones with parentheses - :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; -} - -# simple macro to print error message on unintentional error -:set ExitError do={ - :local ExitOK [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - - :global IfThenElse; - :global LogPrint; - - :if ($ExitOK = "false") do={ - $LogPrint error $Name ([ $IfThenElse ([ :pick $Name 0 1 ] = "\$") \ - "Function" "Script" ] . " '" . $Name . "' exited with error."); - } -} - -# fetch huge data to file, read in chunks -:set FetchHuge do={ - :local ScriptName [ :tostr $1 ]; - :local Url [ :tostr $2 ]; - :local CheckCert [ :tostr $3 ]; - - :global CleanName; - :global FetchUserAgentStr; - :global GetRandom20CharAlNum; - :global IfThenElse; - :global LogPrint; - :global MkDir; - :global RmDir; - :global RmFile; - :global WaitForFile; - - :set CheckCert [ $IfThenElse ($CheckCert = "false") "no" "yes-without-crl" ]; - - :local DirName ("tmpfs/" . [ $CleanName $ScriptName ]); - :if ([ $MkDir $DirName ] = false) do={ - $LogPrint error $0 ("Failed creating directory!"); - :return false; - } - - :local FileName ($DirName . "/" . [ $CleanName $0 ] . "-" . [ $GetRandom20CharAlNum ]); - :do { - /tool/fetch check-certificate=$CheckCert $Url dst-path=$FileName \ - http-header-field=({ [ $FetchUserAgentStr $ScriptName ] }) as-value; - } on-error={ - :if ([ $WaitForFile $FileName 500ms ] = true) do={ - $RmFile $FileName; - } - $LogPrint debug $0 ("Failed downloading from: " . $Url); - $RmDir $DirName; - :return false; - } - $WaitForFile $FileName; - - :local FileSize [ /file/get $FileName size ]; - :local Return ""; - :local VarSize 0; - :while ($VarSize != $FileSize) do={ - :set Return ($Return . ([ /file/read offset=$VarSize chunk-size=32768 file=$FileName as-value ]->"data")); - :set FileSize [ /file/get $FileName size ]; - :set VarSize [ :len $Return ]; - :if ($VarSize > $FileSize) do={ - :delay 100ms; - } - } - $RmDir $DirName; - :return $Return; -} - -# generate user agent string for fetch -:set FetchUserAgentStr do={ - :local Caller [ :tostr $1 ]; - - :local Resource [ /system/resource/get ]; - - :return ("User-Agent: Mikrotik/" . $Resource->"version" . " " . \ - $Resource->"architecture-name" . " " . $Caller . "/Fetch (https://rsc.eworm.de/)"); -} - -# format a line for output -:set FormatLine do={ - :local Key [ :tostr $1 ]; - :local Value [ :tostr $2 ]; - :local Indent [ :tonum $3 ]; - :local Spaces; - :local Return ""; - - :global CharacterMultiply; - :global EitherOr; - - :set Indent [ $EitherOr $Indent 16 ]; - :local Spaces [ $CharacterMultiply " " $Indent ]; - - :if ([ :len $Key ] > 0) do={ :set Return ($Key . ":"); } - :if ([ :len $Key ] > ($Indent - 2)) do={ - :set Return ($Return . "\n" . [ :pick $Spaces 0 $Indent ] . $Value); - } else={ - :set Return ($Return . [ :pick $Spaces 0 ($Indent - [ :len $Return ]) ] . $Value); - } - - :return $Return; -} - -# format multiple lines for output -:set FormatMultiLines do={ - :local Key [ :tostr $1 ]; - :local Values [ :toarray $2 ]; - :local Indent [ :tonum $3 ]; - :local Return; - - :global FormatLine; - - :set Return [ $FormatLine $Key ($Values->0) $Indent ]; - :foreach Value in=[ :pick $Values 1 [ :len $Values ] ] do={ - :set Return ($Return . "\n" . [ $FormatLine "" $Value $Indent ]); - } - - :return $Return; -} - -# get MAC vendor -:set GetMacVendor do={ - :local Mac [ :tostr $1 ]; - - :global CertificateAvailable; - :global IsMacLocallyAdministered; - :global LogPrint; - - :if ([ $IsMacLocallyAdministered $Mac ] = true) do={ - :return "locally administered"; - } - - :do { - :if ([ $CertificateAvailable "GTS Root R4" ] = false) do={ - $LogPrint warning $0 ("Downloading required certificate failed."); - :error false; - } - :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; - $LogPrint debug $0 ("The mac vendor is not known in database."); - } on-error={ - $LogPrint warning $0 ("Failed getting mac vendor."); - } - :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 ]; - - :global HexToNum; - - :if ([ :pick $Input 0 ] = "*") do={ - :return [ $HexToNum [ :pick $Input 1 [ :len $Input ] ] ]; - } - - :return [ :tonum ("0x" . $Input) ]; -} - -# return human readable number -:set HumanReadableNum do={ - :local Input [ :tonum $1 ]; - :local Base [ :tonum $2 ]; - - :global EitherOr; - :global IfThenElse; - - :local Prefix "kMGTPE"; - :local Pow 1; - - :set Base [ $EitherOr $Base 1024 ]; - :local Bin [ $IfThenElse ($Base = 1024) "i" "" ]; - - :if ($Input < $Base) do={ - :return $Input; - } - - :for I from=0 to=[ :len $Prefix ] do={ - :set Pow ($Pow * $Base); - :if ($Input / $Base < $Pow) do={ - :set Prefix [ :pick $Prefix $I ]; - :local Tmp1 ($Input * 100 / $Pow); - :local Tmp2 ($Tmp1 / 100); - :if ($Tmp2 >= 100) do={ - :return ($Tmp2 . $Prefix . $Bin); - } - :return ($Tmp2 . "." . \ - [ :pick $Tmp1 [ :len $Tmp2 ] ([ :len $Tmp1 ] - [ :len $Tmp2 ] + 1) ] . \ - $Prefix . $Bin); - } - } -} - -# 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={ - :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 IsTimeSyncResetNtp; - - :global LogPrintOnce; - - :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; - } - - :local Uptime [ /system/resource/get uptime ]; - :if ([ :typeof $IsTimeSyncResetNtp ] = "nothing") do={ - :set IsTimeSyncResetNtp $Uptime; - } - :if ($Uptime - $IsTimeSyncResetNtp < 3m) do={ - :return false; - } - - $LogPrintOnce warning $0 ("The ntp client is configured, but did not sync."); - :set IsTimeSyncResetNtp $Uptime; - /system/ntp/client/set enabled=no; - :delay 20ms; - /system/ntp/client/set enabled=yes; - :return false; - } - - :if ([ /system/license/get ]->"level" = "free" || \ - [ /system/resource/get ]->"board-name" = "x86") do={ - $LogPrintOnce debug $0 ("No ntp client configured, relying on RTC for CHR free license and x86."); - :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; - } - - $LogPrintOnce debug $0 ("No time source configured! Returning gracefully..."); - :return true; -} - -# log and print with same text -:set LogPrint do={ - :local Severity [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - :local Message [ :tostr $3 ]; - - :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 "" ] . ": " . $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); - } -} - -# log and print, once until reboot -:set LogPrintOnce do={ - :local Severity [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - :local Message [ :tostr $3 ]; - - :global LogPrint; - - :global LogPrintOnceMessages; - - :if ([ :typeof $LogPrintOnceMessages ] = "nothing") do={ - :set LogPrintOnceMessages ({}); - } - - :if ($LogPrintOnceMessages->$Message = 1) do={ - :return false; - } - - :if ([ :len [ /log/find where message=($Name . ": " . $Message) ] ] > 0) do={ - $LogPrint warning $0 \ - ("The message is already in log, scripting subsystem may have crashed before!"); - } - - :set ($LogPrintOnceMessages->$Message) 1; - $LogPrint $Severity $Name $Message; - :return true; -} - -# The function $LogPrintVerbose is declared, but has no code, intentionally. -# https://rsc.eworm.de/DEBUG.md#verbose-output - -# get max value -:set MAX do={ - :if ($1 > $2) do={ :return $1; } - :return $2; -} - -# get min value -:set MIN do={ - :if ($1 < $2) do={ :return $1; } - :return $2; -} - -# create directory -:set MkDir do={ - :local Path [ :tostr $1 ]; - - :global CleanFilePath; - :global LogPrint; - :global RmDir; - :global WaitForFile; - - :local MkTmpfs do={ - :global LogPrint; - :global WaitForFile; - - :local TmpFs [ /disk/find where slot=tmpfs type=tmpfs ]; - :if ([ :len $TmpFs ] = 1) do={ - :if ([ /disk/get $TmpFs disabled ] = true) do={ - $LogPrint info $0 ("The tmpfs is disabled, enabling."); - /disk/enable $TmpFs; - } - :return true; - } - - $LogPrint info $0 ("Creating disk of type tmpfs."); - $RmDir "tmpfs"; - :do { - /disk/add slot=tmpfs type=tmpfs tmpfs-max-size=([ /system/resource/get total-memory ] / 3); - $WaitForFile "tmpfs"; - } on-error={ - $LogPrint warning $0 ("Creating disk of type tmpfs failed!"); - :return false; - } - :return true; - } - - :set Path [ $CleanFilePath $Path ]; - - :if ($Path = "") do={ - :return true; - } - - $LogPrint debug $0 ("Making directory: " . $Path); - - :if ([ :len [ /file/find where name=$Path type="directory" ] ] = 1) do={ - $LogPrint debug $0 ("... which already exists."); - :return true; - } - - :if ([ :pick $Path 0 5 ] = "tmpfs") do={ - :if ([ $MkTmpfs ] = false) do={ - :return false; - } - } - - :do { - /file/add type="directory" name=$Path; - $WaitForFile $Path; - } on-error={ - $LogPrint warning $0 ("Making directory '" . $Path . "' failed!"); - :return false; - } - - :return true; -} - -# prepare NotificationFunctions array -:if ([ :typeof $NotificationFunctions ] != "array") do={ - :set NotificationFunctions ({}); -} - -# parse the date and return a named array -:set ParseDate do={ - :local Date [ :tostr $1 ]; - - :return ({ "year"=[ :tonum [ :pick $Date 0 4 ] ]; - "month"=[ :tonum [ :pick $Date 5 7 ] ]; - "day"=[ :tonum [ :pick $Date 8 10 ] ] }); -} - -# parse key value store -:set ParseKeyValueStore do={ - :local Source $1; - - :if ([ :pick $Source 0 1 ] = "{") do={ - :do { - :return [ :deserialize from=json $Source ]; - } on-error={ } - } - - :if ([ :typeof $Source ] != "array") do={ - :set Source [ :tostr $1 ]; - } - :local Result ({}); - :foreach KeyValue in=[ :toarray $Source ] do={ - :if ([ :find $KeyValue "=" ]) do={ - :local Key [ :pick $KeyValue 0 [ :find $KeyValue "=" ] ]; - :local Value [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ]; - :if ($Value="true") do={ :set Value true; } - :if ($Value="false") do={ :set Value false; } - :set ($Result->$Key) $Value; - } else={ - :set ($Result->$KeyValue) true; - } - } - :return $Result; -} - -# print lines with trailing carriage return -:set PrettyPrint do={ - :put [ :tocrlf [ :tostr $1 ] ]; -} - -# strip protocol from from url string -:set ProtocolStrip do={ - :local Input [ :tostr $1 ]; - - :local Pos [ :find $Input "://" ]; - :if ([ :typeof $Pos ] = "nil") do={ - :return $Input; - } - :return [ :pick $Input ($Pos + 3) [ :len $Input ] ]; -} - -# delay a random amount of seconds -:set RandomDelay do={ - :local Time [ :tonum $1 ]; - :local Unit [ :tostr $2 ]; - - :global EitherOr; - :global GetRandomNumber; - :global MAX; - - :if ($Time = 0) do={ - :return false; - } - - :delay ([ $MAX 10 [ $GetRandomNumber ([ :tonsec [ :totime ($Time . [ $EitherOr $Unit "s" ]) ] ] / 1000000) ] ] . "ms"); -} - -# check for required RouterOS version -:set RequiredRouterOS do={ - :local Caller [ :tostr $1 ]; - :local Required [ :tostr $2 ]; - :local Warn [ :tostr $3 ]; - - :global IfThenElse; - :global LogPrint; - :global VersionToNum; - - :if (!($Required ~ "^\\d+\\.\\d+((alpha|beta|rc|\\.)\\d+|)\$")) do={ - $LogPrint error $0 ("No valid RouterOS version: " . $Required); - :return false; - } - - :if ([ $VersionToNum $Required ] > [ $VersionToNum [ /system/package/update/get installed-version ] ]) do={ - :if ($Warn = "true") do={ - $LogPrint warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = ("\$")) "function" "script" ] . \ - " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!"); - } - :return false; - } - :return true; -} - -# remove directory -:set RmDir do={ - :local DirName [ :tostr $1 ]; - - :global LogPrint; - - $LogPrint debug $0 ("Removing directory: ". $DirName); - - :if ([ :len [ /file/find where name=$DirName type!=directory ] ] > 0) do={ - $LogPrint error $0 ("Directory '" . $DirName . "' is not a directory."); - :return false; - } - - :local Dir [ /file/find where name=$DirName type=directory ]; - :if ([ :len $Dir ] = 0) do={ - $LogPrint debug $0 ("... which does not exist."); - :return true; - } - - :do { - /file/remove $Dir; - } on-error={ - $LogPrint error $0 ("Removing directory '" . $DirName . "' (" . $Dir . ") failed."); - :return false; - } - :return true; -} - -# remove file -:set RmFile do={ - :local FileName [ :tostr $1 ]; - - :global LogPrint; - - $LogPrint debug $0 ("Removing file: ". $FileName); - - :if ([ :len [ /file/find where name=$FileName (type=directory or type=disk) ] ] > 0) do={ - $LogPrint error $0 ("File '" . $FileName . "' is not a file."); - :return false; - } - - :local File [ /file/find where name=$FileName !(type=directory or type=disk) ]; - :if ([ :len $File ] = 0) do={ - $LogPrint debug $0 ("... which does not exist."); - :return true; - } - - :do { - /file/remove $File; - } on-error={ - $LogPrint error $0 ("Removing file '" . $FileName . "' (" . $File . ") failed."); - :return false; - } - :return true; -} - -# check if script is run from terminal -:set ScriptFromTerminal do={ - :local Script [ :tostr $1 ]; - - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $Script ] = false) do={ - :return false; - } - - :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={ - $LogPrint debug $0 ("Script " . $Script . " started from terminal."); - :return true; - } - } - - $LogPrint debug $0 ("Script " . $Script . " NOT started from terminal."); - :return false; -} - -# install new scripts, update existing scripts -:set ScriptInstallUpdate do={ :do { - :local Scripts [ :toarray $1 ]; - :local NewComment [ :tostr $2 ]; - - :global CommitId; - :global CommitInfo; - :global ExpectedConfigVersion; - :global Identity; - :global IDonate; - :global NoNewsAndChangesNotification; - :global ScriptUpdatesBaseUrl; - :global ScriptUpdatesCRLF; - :global ScriptUpdatesUrlSuffix; - - :global CertificateAvailable; - :global EitherOr; - :global FetchUserAgentStr; - :global Grep; - :global IfThenElse; - :global LogPrint; - :global LogPrintOnce; - :global ParseKeyValueStore; - :global RequiredRouterOS; - :global SendNotification2; - :global SymbolForNotification; - :global ValidateSyntax; - - :if ([ $CertificateAvailable "ISRG Root X2" ] = false) do={ - $LogPrint warning $0 ("Downloading certificate failed, trying without."); - } - - :foreach Script in=$Scripts do={ - :if ([ :len [ /system/script/find where name=$Script ] ] = 0) do={ - $LogPrint info $0 ("Adding new script: " . $Script); - /system/script/add name=$Script owner=$Script source="#!rsc by RouterOS\n" comment=$NewComment; - } - } - - :local CommitIdBefore $CommitId; - :local ExpectedConfigVersionBefore $ExpectedConfigVersion; - :local ReloadGlobalFunctions false; - :local ReloadGlobalConfig false; - :local DeviceMode [ /system/device-mode/get ]; - - :local CheckSums ({}); - :do { - :local Url ($ScriptUpdatesBaseUrl . "checksums.json" . $ScriptUpdatesUrlSuffix); - $LogPrint debug $0 ("Fetching checksums from url: " . $Url); - :set CheckSums [ :deserialize from=json ([ /tool/fetch check-certificate=yes-without-crl \ - http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ]->"data") ]; - } on-error={ } - - :foreach Script in=[ /system/script/find where source~"^#!rsc by RouterOS\r?\n" ] do={ - :local ScriptVal [ /system/script/get $Script ]; - :local ScriptInfo [ $ParseKeyValueStore ($ScriptVal->"comment") ]; - :local SourceNew; - - :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={ - $LogPrint warning $0 ("Policies differ for script '" . $ScriptVal->"name" . \ - "' and its scheduler '" . $SchedulerVal->"name" . "'!"); - } - } - - :do { - :if ($ScriptInfo->"ignore" = true) do={ - $LogPrint debug $0 ("Ignoring script '" . $ScriptVal->"name" . "', as requested."); - :error true; - } - - :local CheckSum ($CheckSums->($ScriptVal->"name")); - :if ([ :len ($ScriptInfo->"base-url") ] = 0 && [ :len ($ScriptInfo->"url-suffix") ] = 0 && \ - [ :convert transform=md5 to=hex [ :tolf ($ScriptVal->"source") ] ] = $CheckSum) do={ - $LogPrint debug $0 ("Checksum for script '" . $ScriptVal->"name" . "' matches, ignoring."); - :error true; - } - - :do { - :local BaseUrl [ $EitherOr ($ScriptInfo->"base-url") $ScriptUpdatesBaseUrl ]; - :local UrlSuffix [ $EitherOr ($ScriptInfo->"url-suffix") $ScriptUpdatesUrlSuffix ]; - :local Url ($BaseUrl . $ScriptVal->"name" . ".rsc" . $UrlSuffix); - $LogPrint debug $0 ("Fetching script '" . $ScriptVal->"name" . "' from url: " . $Url); - :local Result [ /tool/fetch check-certificate=yes-without-crl \ - http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ]; - :if ($Result->"status" = "finished") do={ - :set SourceNew [ :tolf ($Result->"data") ]; - } - } on-error={ - :if ($ScriptVal->"source" = "#!rsc by RouterOS\n") do={ - $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . \ - "', removing dummy. Typo on installation?"); - /system/script/remove $Script; - } else={ - $LogPrint warning $0 ("Failed fetching script '" . $ScriptVal->"name" . "'!"); - } - :error false; - } - - :if ([ :len $SourceNew ] = 0) do={ - $LogPrint debug $0 ("No update for script '" . $ScriptVal->"name" . "'."); - :error false; - } - - :local SourceCRLF [ :tocrlf $SourceNew ]; - :if ($SourceNew = $ScriptVal->"source" || $SourceCRLF = $ScriptVal->"source") do={ - $LogPrint debug $0 ("Script '" . $ScriptVal->"name" . "' did not change."); - :error false; - } - - :if ([ :pick $SourceNew 0 18 ] != "#!rsc by RouterOS\n") do={ - $LogPrint warning $0 ("Looks like new script '" . $ScriptVal->"name" . \ - "' is not valid (missing shebang). Ignoring!"); - :error false; - } - - :local RequiredROS ([ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires RouterOS, ") ] ]->"version"); - :if ([ $RequiredRouterOS $0 [ $EitherOr $RequiredROS "0.0" ] false ] = false) do={ - $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires RouterOS " . \ - $RequiredROS . ", which is not met by your installation. Ignoring!"); - :error false; - } - - :local RequiredDM [ $ParseKeyValueStore [ $Grep $SourceNew ("\23 requires device-mode, ") ] ]; - :local MissingDM ({}); - :foreach Feature,Value in=$RequiredDM do={ - :if ([ :typeof ($DeviceMode->$Feature) ] = "bool" && ($DeviceMode->$Feature) = false) do={ - :set MissingDM ($MissingDM, $Feature); - } - } - :if ([ :len $MissingDM ] > 0) do={ - $LogPrintOnce warning $0 ("The script '" . $ScriptVal->"name" . "' requires disabled " . \ - "device-mode features (" . [ :tostr $MissingDM ] . "). Ignoring!"); - :error false; - } - - :if ([ $ValidateSyntax $SourceNew ] = false) do={ - $LogPrint warning $0 ("Syntax validation for script '" . $ScriptVal->"name" . "' failed! Ignoring!"); - :error false; - } - - $LogPrint info $0 ("Updating script: " . $ScriptVal->"name"); - /system/script/set owner=($ScriptVal->"name") \ - source=[ $IfThenElse ($ScriptUpdatesCRLF = true) $SourceCRLF $SourceNew ] $Script; - :if ($ScriptVal->"name" = "global-config") do={ - :set ReloadGlobalConfig true; - } - :if ($ScriptVal->"name" = "global-functions" || $ScriptVal->"name" ~ ("^mod/.")) do={ - :set ReloadGlobalFunctions true; - } - } on-error={ } - } - - :if ($ReloadGlobalFunctions = true) do={ - $LogPrint info $0 ("Reloading global functions."); - :do { - /system/script/run global-functions; - } on-error={ - $LogPrint error $0 ("Reloading global functions failed!"); - } - } - - :if ($ReloadGlobalConfig = true) do={ - $LogPrint info $0 ("Reloading global configuration."); - :do { - /system/script/run global-config; - } on-error={ - $LogPrint error $0 ("Reloading global configuration failed!" . \ - " Syntax error or missing overlay?"); - } - } - - :if ($CommitId != "unknown" && $CommitIdBefore != $CommitId) do={ - $LogPrint info $0 ("Updated to commit: " . $CommitInfo . "/" . [ :pick $CommitId 0 8 ]); - } - - :if ($ExpectedConfigVersionBefore > $ExpectedConfigVersion) do={ - $LogPrint warning $0 ("The configuration version decreased from " . \ - $ExpectedConfigVersionBefore . " to " . $ExpectedConfigVersion . \ - ". Installed an older version?"); - } - - :if ($ExpectedConfigVersionBefore < $ExpectedConfigVersion) do={ - :global GlobalConfigChanges; - :global GlobalConfigMigration; - :local ChangeLogCode; - - :do { - :local Url ($ScriptUpdatesBaseUrl . "news-and-changes.rsc" . $ScriptUpdatesUrlSuffix); - $LogPrint debug $0 ("Fetching news, changes and migration: " . $Url); - :local Result [ /tool/fetch check-certificate=yes-without-crl \ - http-header-field=({ [ $FetchUserAgentStr $0 ] }) $Url output=user as-value ]; - :if ($Result->"status" = "finished") do={ - :set ChangeLogCode ($Result->"data"); - } - } on-error={ - $LogPrint warning $0 ("Failed fetching news, changes and migration!"); - } - - :if ([ :len $ChangeLogCode ] > 0) do={ - :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={ - :do { - [ :parse $ChangeLogCode ]; - } on-error={ - $LogPrint warning $0 ("The changelog failed to run!"); - } - } else={ - $LogPrint warning $0 ("The changelog failed syntax validation!"); - } - } - - :if ([ :len $GlobalConfigMigration ] > 0) do={ - :for I from=($ExpectedConfigVersionBefore + 1) to=$ExpectedConfigVersion do={ - :local Migration ($GlobalConfigMigration->[ :tostr $I ]); - :do { - :if ([ :typeof $Migration ] != "str") do={ - $LogPrint debug $0 ("Migration code for change " . $I . " is not available."); - :error false; - } - - :if ([ $ValidateSyntax $Migration ] = false) do={ - $LogPrint warning $0 ("Migration code for change " . $I . " failed syntax validation!"); - :error false; - } - - $LogPrint info $0 ("Applying migration for change " . $I . ": " . $Migration); - :do { - [ :parse $Migration ]; - } on-error={ - $LogPrint warning $0 ("Migration code for change " . $I . " failed to run!"); - } - } on-error={ } - } - } - - :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."); - $LogPrint info $0 ($NotificationMessage); - - :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 " . \ - [ $SymbolForNotification "pushpin" "*" ] . $Change); - $LogPrint info $0 ("Change " . $I . ": " . $Change); - } - } 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://rsc.eworm.de/#donate"; - } - - $SendNotification2 ({ origin=$0; \ - subject=([ $SymbolForNotification "pushpin" ] . "News and configuration changes"); \ - message=$NotificationMessage; link=$Link }); - } - - :set GlobalConfigChanges; - :set GlobalConfigMigration; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# lock script against multiple invocation -:set ScriptLock do={ - :local Script [ :tostr $1 ]; - :local WaitMax ([ :tonum $3 ] * 10); - - :global GetRandom20CharAlNum; - :global IfThenElse; - :global LogPrint; - - :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={ - $LogPrint error $0 ("A script named '" . $Script . "' does not exist!"); - :error false; - } - - :if ([ $JobCount $Script ] = 0) do={ - $LogPrint error $0 ("No script '" . $Script . "' is running!"); - :error false; - } - - :if ([ $TicketCount $Script ] >= [ $JobCount $Script ]) do={ - $LogPrint error $0 ("More tickets than running scripts '" . $Script . "', resetting!"); - :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 true; - } - - $RemoveTicket $Script $MyTicket; - $LogPrint debug $0 ("Script '" . $Script . "' started more than once" . \ - [ $IfThenElse ($WaitCount > 0) " and timed out waiting for lock" "" ] . "..."); - :return false; -} - -# send notification via NotificationFunctions - expects at least two string arguments -:set SendNotification do={ :do { - :global SendNotification2; - - $SendNotification2 ({ origin=$0; subject=$1; message=$2; link=$3; silent=$4 }); -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# 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 Name [ :tostr $1 ]; - - :global LogPrintOnce; - - :local Symbols { - "abacus"="\F0\9F\A7\AE"; - "alarm-clock"="\E2\8F\B0"; - "arrow-down"="\E2\AC\87"; - "arrow-up"="\E2\AC\86"; - "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"; - "gear"="\E2\9A\99"; - "heart"="\E2\99\A5"; - "high-voltage-sign"="\E2\9A\A1"; - "incoming-envelope"="\F0\9F\93\A8"; - "information"="\E2\84\B9"; - "large-orange-circle"="\F0\9F\9F\A0"; - "large-red-circle"="\F0\9F\94\B4"; - "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"; - "smiley-partying-face"="\F0\9F\A5\B3"; - "smiley-smiling-face"="\E2\98\BA"; - "smiley-winking-face-with-tongue"="\F0\9F\98\9C"; - "sparkles"="\E2\9C\A8"; - "speech-balloon"="\F0\9F\92\AC"; - "star"="\E2\AD\90"; - "warning-sign"="\E2\9A\A0"; - "white-heavy-check-mark"="\E2\9C\85" - } - - :if ([ :len ($Symbols->$Name) ] = 0) do={ - $LogPrintOnce warning $0 ("No symbol available for name '" . $Name . "'!"); - :return ""; - } - - :return (($Symbols->$Name) . "\EF\B8\8F"); -} - -# return symbol for notification -:set SymbolForNotification do={ - :global NotificationsWithSymbols; - :global SymbolByUnicodeName; - :global IfThenElse; - - :if ($NotificationsWithSymbols != true) do={ - :return [ $IfThenElse ([ :len $2 ] > 0) ([ :tostr $2 ] . " ") "" ]; - } - :local Return ""; - :foreach Symbol in=[ :toarray $1 ] do={ - :set Return ($Return . [ $SymbolByUnicodeName $Symbol ]); - } - :return ($Return . " "); -} - -# convert line endings, UNIX -> DOS -:set Unix2Dos do={ - :return [ :tocrlf [ :tostr $1 ] ]; -} - -# 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 $Input "." "," ]; - :foreach I in={ "zero"; "alpha"; "beta"; "rc" } do={ - :set Input [ $CharacterReplace $Input $I ("," . $I . ",") ]; - } - - :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 = "zero") do={ } - :if ($Value = "alpha") do={ :set Return ($Return + 0x3f00); } - :if ($Value = "beta") do={ :set Return ($Return + 0x5f00); } - :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; - :global MAX; - - :set FileName [ $CleanFilePath $FileName ]; - :local I 1; - :local Delay ([ $MAX [ $EitherOr $WaitTime 2s ] 100ms ] / 10); - - :while ([ :len [ /file/find where name=$FileName ] ] = 0) do={ - :if ($I >= 10) do={ - :return false; - } - :delay $Delay; - :set I ($I + 1); - } - - :while ([ :len [ /file/find where name=$FileName ] ] > 0) do={ - :do { - /file/get $FileName; - :return true; - } on-error={ } - :delay $Delay; - :set Delay ($Delay * 3 / 2); - } - - :return false; -} - -# 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={ - $LogPrint error $0 ("Module '" . $ScriptVal->"name" . "' failed to run."); - } - } else={ - $LogPrint error $0 ("Module '" . $ScriptVal->"name" . "' failed syntax validation, skipping."); - } -} - -# Log success -:local Resource [ /system/resource/get ]; -$LogPrintOnce info $ScriptName ("Loaded on " . $Resource->"board-name" . \ - " with RouterOS " . $Resource->"version" . "."); - -# signal we are ready -:set GlobalFunctionsReady true; diff --git a/global-wait b/global-wait new file mode 100644 index 0000000..43cc5cc --- /dev/null +++ b/global-wait @@ -0,0 +1,11 @@ +#!rsc by RouterOS +# RouterOS script: global-wait +# Copyright (c) 2020-2022 Christian Hesse +# 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; } diff --git a/global-wait.rsc b/global-wait.rsc deleted file mode 100644 index ca3fc0c..0000000 --- a/global-wait.rsc +++ /dev/null @@ -1,12 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: global-wait -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# wait for global-functions to finish -# https://rsc.eworm.de/doc/global-wait.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } diff --git a/gps-track b/gps-track new file mode 100644 index 0000000..7c582e2 --- /dev/null +++ b/gps-track @@ -0,0 +1,34 @@ +#!rsc by RouterOS +# RouterOS script: gps-track +# Copyright (c) 2018-2022 Christian Hesse +# 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/gps-track.rsc b/gps-track.rsc deleted file mode 100644 index dea56d2..0000000 --- a/gps-track.rsc +++ /dev/null @@ -1,53 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: gps-track -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch -# -# track gps data by sending json data to http server -# https://rsc.eworm.de/doc/gps-track.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global GpsTrackUrl; - :global Identity; - - :global FetchUserAgentStr; - :global LogPrint; - :global ScriptLock; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - $WaitFullyConnected; - - :local CoordinateFormat [ /system/gps/get coordinate-format ]; - :local Gps [ /system/gps/monitor once as-value ]; - - :if ($Gps->"valid" = true) do={ - :do { - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - http-header-field=({ [ $FetchUserAgentStr $ScriptName ]; "Content-Type: application/json" }) \ - http-data=[ :serialize to=json { "identity"=$Identity; \ - "lat"=($Gps->"latitude"); "lon"=($Gps->"longitude") } ] $GpsTrackUrl as-value; - $LogPrint debug $ScriptName ("Sending GPS data in " . $CoordinateFormat . " format: " . \ - "lat: " . ($Gps->"latitude") . " " . \ - "lon: " . ($Gps->"longitude")); - } on-error={ - $LogPrint warning $ScriptName ("Failed sending GPS data!"); - } - } else={ - $LogPrint debug $ScriptName ("GPS data not valid."); - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/hotspot-to-wpa b/hotspot-to-wpa new file mode 100644 index 0000000..628d748 --- /dev/null +++ b/hotspot-to-wpa @@ -0,0 +1,72 @@ +#!rsc by RouterOS +# RouterOS script: hotspot-to-wpa +# Copyright (c) 2019-2022 Christian Hesse +# 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 accesslist 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/hotspot-to-wpa-cleanup b/hotspot-to-wpa-cleanup new file mode 100644 index 0000000..f54edc6 --- /dev/null +++ b/hotspot-to-wpa-cleanup @@ -0,0 +1,51 @@ +#!rsc by RouterOS +# RouterOS script: hotspot-to-wpa-cleanup +# Copyright (c) 2021-2022 Christian Hesse +# 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-cleanup.capsman.rsc b/hotspot-to-wpa-cleanup.capsman.rsc deleted file mode 100644 index 033d0e7..0000000 --- a/hotspot-to-wpa-cleanup.capsman.rsc +++ /dev/null @@ -1,80 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa-cleanup.capsman -# Copyright (c) 2021-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=80 -# requires RouterOS, version=7.15 -# requires device-mode, hotspot -# -# manage and clean up private WPA passphrase after hotspot login -# https://rsc.eworm.de/doc/hotspot-to-wpa.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global EitherOr; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :local DHCPServers ({}); - :foreach Server in=[ /ip/dhcp-server/find where comment~"hotspot-to-wpa" ] do={ - :local ServerVal [ /ip/dhcp-server/get $Server ] - :local ServerInfo [ $ParseKeyValueStore ($ServerVal->"comment") ]; - :if (($ServerInfo->"hotspot-to-wpa") = "wpa") do={ - :set ($DHCPServers->($ServerVal->"name")) \ - [ :totime [ $EitherOr ($ServerInfo->"timeout") 4w ] ]; - } - } - - :foreach Client in=[ /caps-man/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ - :local ClientVal [ /caps-man/registration-table/get $Client ]; - :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic \ - mac-address=($ClientVal->"mac-address") ] do={ - :if (($DHCPServers->[ /ip/dhcp-server/lease/get $Lease server ]) > 0s) do={ - $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \ - " connected to WPA, making lease static."); - /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:" \ - !(comment~[ /system/clock/get date ]) mac-address ] do={ - :local ClientVal [ /caps-man/access-list/get $Client ]; - :if ([ :len [ /ip/dhcp-server/lease/find where !dynamic comment~"^hotspot-to-wpa:" \ - mac-address=($ClientVal->"mac-address") ] ] = 0) do={ - $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \ - " did not connect to WPA, removing from access list."); - /caps-man/access-list/remove $Client; - } - } - - :foreach Server,Timeout in=$DHCPServers do={ - :local TimeoutExtra ($Timeout + [ /system/clock/get time ]); - :foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status="waiting" \ - server=$Server last-seen>$TimeoutExtra comment~"^hotspot-to-wpa:" ] do={ - :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - $LogPrint info $ScriptName ("Client with mac address " . ($LeaseVal->"mac-address") . \ - " was not seen for " . ($LeaseVal->"last-seen") . ", removing."); - /caps-man/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - mac-address=($LeaseVal->"mac-address") ]; - /ip/dhcp-server/lease/remove $Lease; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/hotspot-to-wpa-cleanup.template.rsc b/hotspot-to-wpa-cleanup.template.rsc deleted file mode 100644 index 0f8c490..0000000 --- a/hotspot-to-wpa-cleanup.template.rsc +++ /dev/null @@ -1,87 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa-cleanup%TEMPL% -# Copyright (c) 2021-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=80 -# requires RouterOS, version=7.15 -# requires device-mode, hotspot -# -# manage and clean up private WPA passphrase after hotspot login -# https://rsc.eworm.de/doc/hotspot-to-wpa.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global EitherOr; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :local DHCPServers ({}); - :foreach Server in=[ /ip/dhcp-server/find where comment~"hotspot-to-wpa" ] do={ - :local ServerVal [ /ip/dhcp-server/get $Server ] - :local ServerInfo [ $ParseKeyValueStore ($ServerVal->"comment") ]; - :if (($ServerInfo->"hotspot-to-wpa") = "wpa") do={ - :set ($DHCPServers->($ServerVal->"name")) \ - [ :totime [ $EitherOr ($ServerInfo->"timeout") 4w ] ]; - } - } - - :foreach Client in=[ /caps-man/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ - :foreach Client in=[ /interface/wifi/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ - :local ClientVal [ /caps-man/registration-table/get $Client ]; - :local ClientVal [ /interface/wifi/registration-table/get $Client ]; - :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic \ - mac-address=($ClientVal->"mac-address") ] do={ - :if (($DHCPServers->[ /ip/dhcp-server/lease/get $Lease server ]) > 0s) do={ - $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \ - " connected to WPA, making lease static."); - /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:" \ - :foreach Client in=[ /interface/wifi/access-list/find where comment~"^hotspot-to-wpa:" \ - !(comment~[ /system/clock/get date ]) mac-address ] do={ - :local ClientVal [ /caps-man/access-list/get $Client ]; - :local ClientVal [ /interface/wifi/access-list/get $Client ]; - :if ([ :len [ /ip/dhcp-server/lease/find where !dynamic comment~"^hotspot-to-wpa:" \ - mac-address=($ClientVal->"mac-address") ] ] = 0) do={ - $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \ - " did not connect to WPA, removing from access list."); - /caps-man/access-list/remove $Client; - /interface/wifi/access-list/remove $Client; - } - } - - :foreach Server,Timeout in=$DHCPServers do={ - :local TimeoutExtra ($Timeout + [ /system/clock/get time ]); - :foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status="waiting" \ - server=$Server last-seen>$TimeoutExtra comment~"^hotspot-to-wpa:" ] do={ - :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - $LogPrint info $ScriptName ("Client with mac address " . ($LeaseVal->"mac-address") . \ - " was not seen for " . ($LeaseVal->"last-seen") . ", removing."); - /caps-man/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - /interface/wifi/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - mac-address=($LeaseVal->"mac-address") ]; - /ip/dhcp-server/lease/remove $Lease; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/hotspot-to-wpa-cleanup.wifi.rsc b/hotspot-to-wpa-cleanup.wifi.rsc deleted file mode 100644 index dfec697..0000000 --- a/hotspot-to-wpa-cleanup.wifi.rsc +++ /dev/null @@ -1,80 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa-cleanup.wifi -# Copyright (c) 2021-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# provides: lease-script, order=80 -# requires RouterOS, version=7.15 -# requires device-mode, hotspot -# -# manage and clean up private WPA passphrase after hotspot login -# https://rsc.eworm.de/doc/hotspot-to-wpa.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global EitherOr; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :local DHCPServers ({}); - :foreach Server in=[ /ip/dhcp-server/find where comment~"hotspot-to-wpa" ] do={ - :local ServerVal [ /ip/dhcp-server/get $Server ] - :local ServerInfo [ $ParseKeyValueStore ($ServerVal->"comment") ]; - :if (($ServerInfo->"hotspot-to-wpa") = "wpa") do={ - :set ($DHCPServers->($ServerVal->"name")) \ - [ :totime [ $EitherOr ($ServerInfo->"timeout") 4w ] ]; - } - } - - :foreach Client in=[ /interface/wifi/registration-table/find where comment~"^hotspot-to-wpa:" ] do={ - :local ClientVal [ /interface/wifi/registration-table/get $Client ]; - :foreach Lease in=[ /ip/dhcp-server/lease/find where dynamic \ - mac-address=($ClientVal->"mac-address") ] do={ - :if (($DHCPServers->[ /ip/dhcp-server/lease/get $Lease server ]) > 0s) do={ - $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \ - " connected to WPA, making lease static."); - /ip/dhcp-server/lease/make-static $Lease; - /ip/dhcp-server/lease/set comment=($ClientVal->"comment") $Lease; - } - } - } - - :foreach Client in=[ /interface/wifi/access-list/find where comment~"^hotspot-to-wpa:" \ - !(comment~[ /system/clock/get date ]) mac-address ] do={ - :local ClientVal [ /interface/wifi/access-list/get $Client ]; - :if ([ :len [ /ip/dhcp-server/lease/find where !dynamic comment~"^hotspot-to-wpa:" \ - mac-address=($ClientVal->"mac-address") ] ] = 0) do={ - $LogPrint info $ScriptName ("Client with mac address " . ($ClientVal->"mac-address") . \ - " did not connect to WPA, removing from access list."); - /interface/wifi/access-list/remove $Client; - } - } - - :foreach Server,Timeout in=$DHCPServers do={ - :local TimeoutExtra ($Timeout + [ /system/clock/get time ]); - :foreach Lease in=[ /ip/dhcp-server/lease/find where !dynamic status="waiting" \ - server=$Server last-seen>$TimeoutExtra comment~"^hotspot-to-wpa:" ] do={ - :local LeaseVal [ /ip/dhcp-server/lease/get $Lease ]; - $LogPrint info $ScriptName ("Client with mac address " . ($LeaseVal->"mac-address") . \ - " was not seen for " . ($LeaseVal->"last-seen") . ", removing."); - /interface/wifi/access-list/remove [ find where comment~"^hotspot-to-wpa:" \ - mac-address=($LeaseVal->"mac-address") ]; - /ip/dhcp-server/lease/remove $Lease; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/hotspot-to-wpa.capsman.rsc b/hotspot-to-wpa.capsman.rsc deleted file mode 100644 index 3f51475..0000000 --- a/hotspot-to-wpa.capsman.rsc +++ /dev/null @@ -1,105 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa.capsman -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, hotspot -# -# add private WPA passphrase after hotspot login -# https://rsc.eworm.de/doc/hotspot-to-wpa.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global EitherOr; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :local MacAddress $"mac-address"; - :local UserName $username; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={ - $LogPrint error $ScriptName ("This script is supposed to run from hotspot on login."); - :set ExitOK true; - :error false; - } - - :local Date [ /system/clock/get date ]; - :local UserVal ({}); - :if ([ :len [ /ip/hotspot/user/find where name=$UserName ] ] > 0) do={ - :set 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; - $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- hotspot-to-wpa above ---'."); - } - :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; - $LogPrint warning $ScriptName ("Added template in access-list for hotspot '" . $Hotspot . "'."); - } - :local Template [ /caps-man/access-list/get ([ find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ]; - - :if ($Template->"action" = "reject") do={ - $LogPrint info $ScriptName ("Ignoring login for hotspot '" . $Hotspot . "'."); - :set ExitOK true; - :error true; - } - - # allow login page to load - :delay 1s; - - $LogPrint info $ScriptName ("Adding/updating access-list entry for mac address " . $MacAddress . \ - " (user " . $UserName . ")."); - /caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; - /caps-man/access-list/add private-passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \ - mac-address=$MacAddress comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \ - action=reject 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; - } - - :delay 2s; - /caps-man/access-list/set $Entry action=accept; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/hotspot-to-wpa.template.rsc b/hotspot-to-wpa.template.rsc deleted file mode 100644 index 068241d..0000000 --- a/hotspot-to-wpa.template.rsc +++ /dev/null @@ -1,125 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa%TEMPL% -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, hotspot -# -# add private WPA passphrase after hotspot login -# https://rsc.eworm.de/doc/hotspot-to-wpa.md -# -# !! This is just a template to generate the real script! -# !! Pattern '%TEMPL%' is replaced, paths are filtered. - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global EitherOr; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :local MacAddress $"mac-address"; - :local UserName $username; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={ - $LogPrint error $ScriptName ("This script is supposed to run from hotspot on login."); - :set ExitOK true; - :error false; - } - - :local Date [ /system/clock/get date ]; - :local UserVal ({}); - :if ([ :len [ /ip/hotspot/user/find where name=$UserName ] ] > 0) do={ - :set 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={ - :if ([ :len [ /interface/wifi/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={ - /caps-man/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes; - /interface/wifi/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes; - $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- hotspot-to-wpa above ---'."); - } - :local PlaceBefore ([ /caps-man/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0); - :local PlaceBefore ([ /interface/wifi/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0); - - :if ([ :len [ /caps-man/access-list/find where \ - :if ([ :len [ /interface/wifi/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; - /interface/wifi/access-list/add comment=("hotspot-to-wpa template " . $Hotspot) disabled=yes place-before=$PlaceBefore; - $LogPrint warning $ScriptName ("Added template in access-list for hotspot '" . $Hotspot . "'."); - } - :local Template [ /caps-man/access-list/get ([ find where \ - :local Template [ /interface/wifi/access-list/get ([ find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ]; - - :if ($Template->"action" = "reject") do={ - $LogPrint info $ScriptName ("Ignoring login for hotspot '" . $Hotspot . "'."); - :set ExitOK true; - :error true; - } - - # allow login page to load - :delay 1s; - - $LogPrint info $ScriptName ("Adding/updating access-list entry for mac address " . $MacAddress . \ - " (user " . $UserName . ")."); - /caps-man/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; - /interface/wifi/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; - /caps-man/access-list/add private-passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \ - /interface/wifi/access-list/add passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \ - mac-address=$MacAddress comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \ - action=reject place-before=$PlaceBefore; - - :local Entry [ /caps-man/access-list/find where mac-address=$MacAddress \ - :local Entry [ /interface/wifi/access-list/find where mac-address=$MacAddress \ - comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) ]; -# NOT /caps-man/ # - :set ($Template->"private-passphrase") ($Template->"passphrase"); -# NOT /caps-man/ # - :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; - /interface/wifi/access-list/set $Entry !passphrase; - } else={ - /caps-man/access-list/set $Entry private-passphrase=$PrivatePassphrase; - /interface/wifi/access-list/set $Entry 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; - /interface/wifi/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; - /interface/wifi/access-list/set $Entry vlan-id=$VlanId; - } -# NOT /interface/wifi/ # - :local VlanMode [ $EitherOr ($UserInfo->"vlan-mode") ($Template->"vlan-mode") ]; - :if ([ :len $VlanMode] > 0) do={ - /caps-man/access-list/set $Entry vlan-mode=$VlanMode; - } -# NOT /interface/wifi/ # - - :delay 2s; - /caps-man/access-list/set $Entry action=accept; - /interface/wifi/access-list/set $Entry action=accept; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/hotspot-to-wpa.wifi.rsc b/hotspot-to-wpa.wifi.rsc deleted file mode 100644 index cc5e2fc..0000000 --- a/hotspot-to-wpa.wifi.rsc +++ /dev/null @@ -1,102 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: hotspot-to-wpa.wifi -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, hotspot -# -# add private WPA passphrase after hotspot login -# https://rsc.eworm.de/doc/hotspot-to-wpa.md -# -# !! Do not edit this file, it is generated from template! - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global EitherOr; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :local MacAddress $"mac-address"; - :local UserName $username; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :typeof $MacAddress ] = "nothing" || [ :typeof $UserName ] = "nothing") do={ - $LogPrint error $ScriptName ("This script is supposed to run from hotspot on login."); - :set ExitOK true; - :error false; - } - - :local Date [ /system/clock/get date ]; - :local UserVal ({}); - :if ([ :len [ /ip/hotspot/user/find where name=$UserName ] ] > 0) do={ - :set 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 [ /interface/wifi/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ] ] = 0) do={ - /interface/wifi/access-list/add comment="--- hotspot-to-wpa above ---" disabled=yes; - $LogPrint warning $ScriptName ("Added disabled access-list entry with comment '--- hotspot-to-wpa above ---'."); - } - :local PlaceBefore ([ /interface/wifi/access-list/find where comment="--- hotspot-to-wpa above ---" disabled ]->0); - - :if ([ :len [ /interface/wifi/access-list/find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ] ] = 0) do={ - /interface/wifi/access-list/add comment=("hotspot-to-wpa template " . $Hotspot) disabled=yes place-before=$PlaceBefore; - $LogPrint warning $ScriptName ("Added template in access-list for hotspot '" . $Hotspot . "'."); - } - :local Template [ /interface/wifi/access-list/get ([ find where \ - comment=("hotspot-to-wpa template " . $Hotspot) disabled ]->0) ]; - - :if ($Template->"action" = "reject") do={ - $LogPrint info $ScriptName ("Ignoring login for hotspot '" . $Hotspot . "'."); - :set ExitOK true; - :error true; - } - - # allow login page to load - :delay 1s; - - $LogPrint info $ScriptName ("Adding/updating access-list entry for mac address " . $MacAddress . \ - " (user " . $UserName . ")."); - /interface/wifi/access-list/remove [ find where mac-address=$MacAddress comment~"^hotspot-to-wpa: " ]; - /interface/wifi/access-list/add passphrase=($UserVal->"password") ssid-regexp="-wpa\$" \ - mac-address=$MacAddress comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) \ - action=reject place-before=$PlaceBefore; - - :local Entry [ /interface/wifi/access-list/find where mac-address=$MacAddress \ - comment=("hotspot-to-wpa: " . $UserName . ", " . $MacAddress . ", " . $Date) ]; - :set ($Template->"private-passphrase") ($Template->"passphrase"); - :local PrivatePassphrase [ $EitherOr ($UserInfo->"private-passphrase") ($Template->"private-passphrase") ]; - :if ([ :len $PrivatePassphrase ] > 0) do={ - :if ($PrivatePassphrase = "ignore") do={ - /interface/wifi/access-list/set $Entry !passphrase; - } else={ - /interface/wifi/access-list/set $Entry passphrase=$PrivatePassphrase; - } - } - :local SsidRegexp [ $EitherOr ($UserInfo->"ssid-regexp") ($Template->"ssid-regexp") ]; - :if ([ :len $SsidRegexp ] > 0) do={ - /interface/wifi/access-list/set $Entry ssid-regexp=$SsidRegexp; - } - :local VlanId [ $EitherOr ($UserInfo->"vlan-id") ($Template->"vlan-id") ]; - :if ([ :len $VlanId ] > 0) do={ - /interface/wifi/access-list/set $Entry vlan-id=$VlanId; - } - - :delay 2s; - /interface/wifi/access-list/set $Entry action=accept; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/ip-addr-bridge b/ip-addr-bridge new file mode 100644 index 0000000..783ff2f --- /dev/null +++ b/ip-addr-bridge @@ -0,0 +1,18 @@ +#!rsc by RouterOS +# RouterOS script: ip-addr-bridge +# Copyright (c) 2018-2022 Christian Hesse +# 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/ip-addr-bridge.rsc b/ip-addr-bridge.rsc deleted file mode 100644 index 68ff4a4..0000000 --- a/ip-addr-bridge.rsc +++ /dev/null @@ -1,18 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: ip-addr-bridge -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# enable or disable ip addresses based on bridge port state -# https://rsc.eworm.de/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 new file mode 100644 index 0000000..0131f62 --- /dev/null +++ b/ipsec-to-dns @@ -0,0 +1,68 @@ +#!rsc by RouterOS +# RouterOS script: ipsec-to-dns +# Copyright (c) 2021-2022 Christian Hesse +# 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 LogPrintExit2; +:global IfThenElse; + +: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=$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 Comment ($CommentPrefix . $PeerVal->"id"); +:put ($PeerVal->"id"); + :local HostName [ :pick ($PeerVal->"id") 0 [ :find ($PeerVal->"id" . ".") "." ] ]; +:put $HostName; + + :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/ipsec-to-dns.rsc b/ipsec-to-dns.rsc deleted file mode 100644 index 26dab0a..0000000 --- a/ipsec-to-dns.rsc +++ /dev/null @@ -1,84 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: ipsec-to-dns -# Copyright (c) 2021-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, ipsec -# -# and add/remove/update DNS entries from IPSec mode-config -# https://rsc.eworm.de/doc/ipsec-to-dns.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Domain; - :global HostNameInZone; - :global Identity; - :global PrefixInZone; - - :global CharacterReplace; - :global EscapeForRegEx; - :global IfThenElse; - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :local Zone \ - ([ $IfThenElse ($PrefixInZone = true) "ipsec." ] . \ - [ $IfThenElse ($HostNameInZone = true) ($Identity . ".") ] . $Domain); - :local Ttl 5m; - :local CommentPrefix ("managed by " . $ScriptName . " for "); - :local CommentString ("--- " . $ScriptName . " above ---"); - - :if ([ :len [ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and name=-)) type=NXDOMAIN disabled ] ] = 0) do={ - /ip/dns/static/add name=$CommentString type=NXDOMAIN disabled=yes; - $LogPrint warning $ScriptName ("Added disabled static dns record with name '" . $CommentString . "'."); - } - :local PlaceBefore ([ /ip/dns/static/find where (name=$CommentString or (comment=$CommentString and 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={ - $LogPrint debug $ScriptName ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") still exists. Not deleting DNS entry."); - } else={ - :local Found false; - $LogPrint info $ScriptName ("Peer " . $PeerId . " (" . $DnsRecordVal->"name" . ") has gone, deleting DNS entry."); - /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={ - $LogPrint debug $ScriptName ("DNS entry for " . $Fqdn . " does not need updating."); - } else={ - $LogPrint info $ScriptName ("Replacing DNS entry for " . $Fqdn . ", new address is " . $PeerVal->"dynamic-address" . "."); - /ip/dns/static/set name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment $DnsRecord; - } - } else={ - $LogPrint info $ScriptName ("Adding new DNS entry for " . $Fqdn . ", address is " . $PeerVal->"dynamic-address" . "."); - /ip/dns/static/add name=$Fqdn address=($PeerVal->"dynamic-address") ttl=$Ttl comment=$Comment place-before=$PlaceBefore; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/ipv6-update b/ipv6-update new file mode 100644 index 0000000..742a7cf --- /dev/null +++ b/ipv6-update @@ -0,0 +1,62 @@ +#!rsc by RouterOS +# RouterOS script: ipv6-update +# Copyright (c) 2013-2022 Christian Hesse +# 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 Address [ / ipv6 address find where from-pool=$Pool interface=($Comment->"interface") ]; + :if ([ :len $Address ] = 1) do={ + :set Address [ / ipv6 address get $Address address ]; + $LogPrintExit2 info $0 ("Updating IPv6 address list with new IPv6 prefix " . $Address . \ + " from interface " . ($Comment->"interface")) false; + / ipv6 firewall address-list set address=$Address $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 get [ find where interface=($Comment->"interface") from-pool=$Pool global ] 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/ipv6-update.rsc b/ipv6-update.rsc deleted file mode 100644 index 94bd1bc..0000000 --- a/ipv6-update.rsc +++ /dev/null @@ -1,107 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: ipv6-update -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# update firewall and dns settings on IPv6 prefix change -# https://rsc.eworm.de/doc/ipv6-update.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :local NaAddress $"na-address"; - :local NaValid $"na-valid"; - :local PdPrefix $"pd-prefix"; - :local PdValid $"pd-valid"; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :typeof $NaAddress ] = "str") do={ - $LogPrint info $ScriptName ("An address (" . $NaAddress . ") was acquired, not a prefix. Ignoring."); - :set ExitOK true; - :error false; - } - - :if ([ :typeof $PdPrefix ] = "nothing" || [ :typeof $PdValid ] = "nothing") do={ - $LogPrint error $ScriptName ("This script is supposed to run from ipv6 dhcp-client."); - :set ExitOK true; - :error false; - } - - :if ($PdValid != 1) do={ - $LogPrint info $ScriptName ("The prefix " . $PdPrefix . " is no longer valid. Ignoring."); - :set ExitOK true; - :error false; - } - - :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) dynamic=yes; - $LogPrint warning $ScriptName ("Added dynamic ipv6 address list entry for ipv6-pool-" . $Pool); - } - :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={ - $LogPrint info $ScriptName ("Updating IPv6 address list with new IPv6 prefix " . $PdPrefix); - /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)); - - $LogPrint info $ScriptName ("Updating IPv6 address list with new IPv6 host address " . $Address . \ - " from interface " . ($Comment->"interface")); - /ipv6/firewall/address-list/set address=$Address $ListEntry; - } else={ - $LogPrint info $ScriptName ("Updating IPv6 address list with new IPv6 prefix " . $Prefix . \ - " from interface " . ($Comment->"interface")); - /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)); - - $LogPrint info $ScriptName ("Updating DNS record for " . ($RecordVal->"name") . \ - ($RecordVal->"regexp") . " to " . $Address); - /ip/dns/static/set address=$Address $Record; - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/learn-mac-based-vlan b/learn-mac-based-vlan new file mode 100644 index 0000000..e05fb7e --- /dev/null +++ b/learn-mac-based-vlan @@ -0,0 +1,13 @@ +#!rsc by RouterOS +# RouterOS script: learn-mac-based-vlan +# Copyright (c) 2013-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# learn MAC address for MAC-based-VLAN + +:local NewVlanId 33; + +:if ([ :len [ / interface ethernet switch mac-based-vlan find where src-mac-address=$leaseActMAC ] ] = 0 ) do={ + :log info ("MAC-based-VLAN: learning MAC address " . $leaseActMAC . " for VLAN " . $NewVlanId . "."); + / interface ethernet switch mac-based-vlan add src-mac-address=$leaseActMAC new-customer-vid=$NewVlanId; +} diff --git a/lease-script b/lease-script new file mode 100644 index 0000000..2b873c8 --- /dev/null +++ b/lease-script @@ -0,0 +1,54 @@ +#!rsc by RouterOS +# RouterOS script: lease-script +# Copyright (c) 2013-2022 Christian Hesse +# 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 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 [ :toarray "" ]; + +:foreach Script in=[ / system script find where source~("\n# provides: lease-script, ") ] do={ + :local Name [ / system script get $Script name ]; + :local Store [ / system script get $Script source ]; + + :set Store [ :pick $Store ([ :find $Store "\n# provides: lease-script, " ] + 27) [ :len $Store ] ]; + :set Store [ :pick $Store 0 [ :find $Store "\n" ] ]; + :set Store [ $ParseKeyValueStore $Store ]; + + :set ($RunOrder->($Store->"order")) $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/lease-script.rsc b/lease-script.rsc deleted file mode 100644 index bf27fda..0000000 --- a/lease-script.rsc +++ /dev/null @@ -1,65 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: lease-script -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# run scripts on DHCP lease -# https://rsc.eworm.de/doc/lease-script.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Grep; - :global IfThenElse; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ :typeof $leaseActIP ] = "nothing" || \ - [ :typeof $leaseActMAC ] = "nothing" || \ - [ :typeof $leaseServerName ] = "nothing" || \ - [ :typeof $leaseBound ] = "nothing") do={ - $LogPrint error $ScriptName ("This script is supposed to run from ip dhcp-server."); - :set ExitOK true; - :error false; - } - - $LogPrint debug $ScriptName ("DHCP Server " . $leaseServerName . " " . [ $IfThenElse ($leaseBound = 0) \ - "de" "" ] . "assigned lease " . $leaseActIP . " to " . $leaseActMAC); - - :if ([ $ScriptLock $ScriptName 10 ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :len [ /system/script/job/find where script=$ScriptName ] ] > 1) do={ - $LogPrint debug $ScriptName ("More invocations are waiting, exiting early."); - :set ExitOK true; - :error true; - } - - :local RunOrder ({}); - :foreach Script in=[ /system/script/find where source~("\n# provides: lease-script\\b") ] do={ - :local ScriptVal [ /system/script/get $Script ]; - :local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") ("\23 provides: lease-script, ") ] ]; - - :set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name"); - } - - :foreach Order,Script in=$RunOrder do={ - :do { - $LogPrint debug $ScriptName ("Running script with order " . $Order . ": " . $Script); - /system/script/run $Script; - } on-error={ - $LogPrint warning $ScriptName ("Running script '" . $Script . "' failed!"); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/leds-day-mode b/leds-day-mode new file mode 100644 index 0000000..ed6f68d --- /dev/null +++ b/leds-day-mode @@ -0,0 +1,9 @@ +#!rsc by RouterOS +# RouterOS script: leds-day-mode +# Copyright (c) 2013-2022 Christian Hesse +# 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-day-mode.rsc b/leds-day-mode.rsc deleted file mode 100644 index 7344fde..0000000 --- a/leds-day-mode.rsc +++ /dev/null @@ -1,9 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: leds-day-mode -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# enable LEDs -# https://rsc.eworm.de/doc/leds-mode.md - -/system/leds/settings/set all-leds-off=never; diff --git a/leds-night-mode b/leds-night-mode new file mode 100644 index 0000000..af2388c --- /dev/null +++ b/leds-night-mode @@ -0,0 +1,9 @@ +#!rsc by RouterOS +# RouterOS script: leds-night-mode +# Copyright (c) 2013-2022 Christian Hesse +# 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-night-mode.rsc b/leds-night-mode.rsc deleted file mode 100644 index 8bd028e..0000000 --- a/leds-night-mode.rsc +++ /dev/null @@ -1,9 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: leds-night-mode -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# disable LEDs -# https://rsc.eworm.de/doc/leds-mode.md - -/system/leds/settings/set all-leds-off=immediate; diff --git a/leds-toggle-mode b/leds-toggle-mode new file mode 100644 index 0000000..8ec66e3 --- /dev/null +++ b/leds-toggle-mode @@ -0,0 +1,13 @@ +#!rsc by RouterOS +# RouterOS script: leds-toggle-mode +# Copyright (c) 2018-2022 Christian Hesse +# 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/leds-toggle-mode.rsc b/leds-toggle-mode.rsc deleted file mode 100644 index b55e351..0000000 --- a/leds-toggle-mode.rsc +++ /dev/null @@ -1,9 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: leds-toggle-mode -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# toggle LEDs mode -# https://rsc.eworm.de/doc/leds-mode.md - -/system/leds/settings/set all-leds-off=(({ "never"="immediate"; "immediate"="never" })->[ get all-leds-off ]); diff --git a/log-forward b/log-forward new file mode 100644 index 0000000..3cd83d5 --- /dev/null +++ b/log-forward @@ -0,0 +1,87 @@ +#!rsc by RouterOS +# RouterOS script: log-forward +# Copyright (c) 2020-2022 Christian Hesse +# 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 EscapeForRegEx; +:global HexToNum; +:global IfThenElse; +:global LogPrintExit2; +:global QuotedPrintable; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; +:global WaitFullyConnected; + +$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; +} + +$WaitFullyConnected; + +:local Count 0; +:local Duplicates false; +:local Last [ $IfThenElse ([ :len $LogForwardLast ] > 0) [ $HexToNum $LogForwardLast ] -1 ]; +:local Messages ""; +:local MessageVal; +:local MessageDups [ :toarray "" ]; + +:local LogForwardFilterLogForwarding ("^" . [ $EscapeForRegEx ("Error sending e-mail <" . \ + [ $QuotedPrintable ("[" . $Identity . "] " . [ $SymbolForNotification "warning-sign" ] . \ + "Log Forwarding") ] . ">:") ]); +:foreach Message in=[ / log find where (!(message="") and !(message~$LogForwardFilterLogForwarding) 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 ($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={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "warning-sign" ] . "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.") ] . "\n" . $Messages) }); + + :set LogForwardRateLimit ($LogForwardRateLimit + 10); + :set LogForwardLast ($MessageVal->".id"); +} else={ + :if ($LogForwardRateLimit > 0) do={ + :set LogForwardRateLimit ($LogForwardRateLimit - 1); + } +} diff --git a/log-forward.rsc b/log-forward.rsc deleted file mode 100644 index afeb3f2..0000000 --- a/log-forward.rsc +++ /dev/null @@ -1,113 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: log-forward -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# forward log messages via notification -# https://rsc.eworm.de/doc/log-forward.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - :global LogForwardFilter; - :global LogForwardFilterMessage; - :global LogForwardInclude; - :global LogForwardIncludeMessage; - :global LogForwardLast; - :global LogForwardRateLimit; - - :global EitherOr; - :global HexToNum; - :global IfThenElse; - :global LogForwardFilterLogForwarding; - :global LogPrint; - :global MAX; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :typeof $LogForwardRateLimit ] = "nothing") do={ - :set LogForwardRateLimit 0; - } - - :if ($LogForwardRateLimit > 30) do={ - :set LogForwardRateLimit ($LogForwardRateLimit - 1); - $LogPrint info $ScriptName ("Rate limit in action, not forwarding logs, if any!"); - :set ExitOK true; - :error false; - } - - :local Count 0; - :local Duplicates false; - :local Last [ $IfThenElse ([ :len $LogForwardLast ] > 0) [ $HexToNum $LogForwardLast ] -1 ]; - :local Messages ""; - :local Warning false; - :local MessageVal; - :local MessageDups ({}); - - :set LogForwardFilter [ $EitherOr $LogForwardFilter [] ]; - :set LogForwardFilterMessage [ $EitherOr $LogForwardFilterMessage [] ]; - :set LogForwardInclude [ $EitherOr $LogForwardInclude [] ]; - :set LogForwardIncludeMessage [ $EitherOr $LogForwardIncludeMessage [] ]; - - :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 ]; - :local Bullet "information"; - - :if ($Last < [ $HexToNum ($MessageVal->".id") ]) do={ - :local DupCount ($MessageDups->($MessageVal->"message")); - :if ($MessageVal->"topics" ~ "(warning)") do={ - :set Warning true; - :set Bullet "large-orange-circle"; - } - :if ($MessageVal->"topics" ~ "(emergency|alert|critical|error)") do={ - :set Warning true; - :set Bullet "large-red-circle"; - } - :if ($DupCount < 3) do={ - :set Messages ($Messages . "\n" . [ $SymbolForNotification $Bullet ] . \ - $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=$ScriptName; \ - 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) }); - } else={ - :set LogForwardRateLimit [ $MAX 0 ($LogForwardRateLimit - 1) ]; - } - - :local LogAll [ /log/find ]; - :set LogForwardLast ($LogAll->([ :len $LogAll ] - 1) ); -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/manage-umts b/manage-umts new file mode 100644 index 0000000..8f782ec --- /dev/null +++ b/manage-umts @@ -0,0 +1,29 @@ +#!rsc by RouterOS +# RouterOS script: manage-umts +# Copyright (c) 2013-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# manage UMTS interface based on ethernet and wireless status + +:local EtherInt "en1"; +:local WlanInt "wl-station"; +:local UmtsInt "t-mobile"; + +:local EtherStatus [ / interface ethernet get $EtherInt running ]; +:local WlanStatus [ / interface wireless get $WlanInt running ]; + +:if ($EtherStatus = true || $WlanStatus = true) do={ + :if ([ / interface get $UmtsInt disabled ] = false) do={ + :log info ("Ethernet (" . $EtherInt . " / " . $EtherStatus . ") or " . \ + "wireless (" . $WlanInt . " / " . $WlanStatus . ") is running, " . \ + "UMTS interface " . $UmtsInt . " is enabled. Disabling..."); + / interface set disabled=yes $UmtsInt; + } +} else={ + :if ([ / interface get $UmtsInt disabled ] = true) do={ + :log info ("Neither ethernet (" . $EtherInt . ") nor wireless (" . \ + $WlanInt . ") interface is running, UMTS interface " . $UmtsInt . \ + " is disabled. Enabling..."); + / interface set disabled=no $UmtsInt; + } +} diff --git a/mod/bridge-port-to b/mod/bridge-port-to new file mode 100644 index 0000000..d88b1cd --- /dev/null +++ b/mod/bridge-port-to @@ -0,0 +1,54 @@ +#!rsc by RouterOS +# RouterOS script: mod/bridge-port-to +# Copyright (c) 2013-2022 Christian Hesse +# 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; + + :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; + } + / interface bridge port set disabled=no bridge=$BridgeDefault $BridgePort; + } else={ + $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \ + " bridge " . $BridgeDefault . ".") false; + } + } + } + } + } +} diff --git a/mod/bridge-port-to.rsc b/mod/bridge-port-to.rsc deleted file mode 100644 index 39a036e..0000000 --- a/mod/bridge-port-to.rsc +++ /dev/null @@ -1,70 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/bridge-port-to -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# reset bridge ports to default bridge -# https://rsc.eworm.de/doc/mod/bridge-port-to.md - -:global BridgePortTo; - -:set BridgePortTo do={ :do { - :local BridgePortTo [ :tostr $1 ]; - - :global IfThenElse; - :global LogPrint; - :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={ - $LogPrint warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \ - " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!"); - :return false; - } - :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ]; - - :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={ - $LogPrint info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client."); - /interface/bridge/port/disable $BridgePort; - :delay 200ms; - /ip/dhcp-client/enable $DHCPClient; - } - } else={ - :if ($BridgePortVal->"disabled" = true || $BridgeDefault != $BridgePortVal->"bridge") do={ - $LogPrint info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $BridgePortTo . \ - " bridge " . $BridgeDefault . ", disabling dhcp client."); - :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={ - $LogPrint debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $BridgePortTo . \ - " bridge " . $BridgeDefault . "."); - } - } - } - } - } - :if ([ :len $InterfaceReEnable ] > 0) do={ - :delay 5s; - $LogPrint info $0 ("Re-enabling interfaces..."); - /interface/ethernet/enable $InterfaceReEnable; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } diff --git a/mod/bridge-port-vlan b/mod/bridge-port-vlan new file mode 100644 index 0000000..db9cbfd --- /dev/null +++ b/mod/bridge-port-vlan @@ -0,0 +1,62 @@ +#!rsc by RouterOS +# RouterOS script: mod/bridge-port-vlan +# Copyright (c) 2013-2022 Christian Hesse +# 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; + +# TODO +:global BridgePortVlan do={ + :local ConfigTo [ :tostr $1 ]; + + :global IfThenElse; + :global LogPrintExit2; + :global ParseKeyValueStore; + + :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={ + :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 . ", disabling dhcp client.") false; + :if ([ :len $DHCPClient ] = 1) do={ + / ip dhcp-client disable $DHCPClient; + :delay 200ms; + } + / interface bridge port set disabled=no pvid=$Vlan $BridgePort; + } else={ + $LogPrintExit2 debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $ConfigTo . \ + " vlan " . $Vlan . ".") false; + } + } + } + } + } +} diff --git a/mod/bridge-port-vlan.rsc b/mod/bridge-port-vlan.rsc deleted file mode 100644 index 0eeb9b5..0000000 --- a/mod/bridge-port-vlan.rsc +++ /dev/null @@ -1,79 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/bridge-port-vlan -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# manage VLANs on bridge ports -# https://rsc.eworm.de/doc/mod/bridge-port-vlan.md - -:global BridgePortVlan; - -:global BridgePortVlan do={ :do { - :local ConfigTo [ :tostr $1 ]; - - :global IfThenElse; - :global LogPrint; - :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={ - $LogPrint warning $0 ([ $IfThenElse ([ :len $DHCPClient ] = 0) "Missing" "Duplicate" ] . \ - " dhcp client configuration for interface " . $BridgePortVal->"interface" . "!"); - :return false; - } - :local DHCPClientDisabled [ /ip/dhcp-client/get $DHCPClient disabled ]; - - :if ($BridgePortVal->"disabled" = false || $DHCPClientDisabled = true) do={ - $LogPrint info $0 ("Disabling bridge port for interface " . $BridgePortVal->"interface" . ", enabling dhcp client."); - /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={ - $LogPrint warning $0 ("Could not find VLAN '" . $Vlan . "' for interface " . $BridgePortVal->"interface" . "!"); - :return false; - } - } - :if ($BridgePortVal->"disabled" = true || $Vlan != $BridgePortVal->"pvid") do={ - $LogPrint info $0 ("Enabling bridge port for interface " . $BridgePortVal->"interface" . ", changing to " . $ConfigTo . \ - " vlan " . $Vlan . [ $IfThenElse ($Vlan != $VlanName) (" (" . $VlanName . ")") ] . ", disabling dhcp client."); - :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={ - $LogPrint debug $0 ("Interface " . $BridgePortVal->"interface" . " already connected to " . $ConfigTo . \ - " vlan " . $Vlan . "."); - } - } - } - } - } - :if ([ :len $InterfaceReEnable ] > 0) do={ - :delay 5s; - $LogPrint info $0 ("Re-enabling interfaces..."); - /interface/ethernet/enable $InterfaceReEnable; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } diff --git a/mod/inspectvar.rsc b/mod/inspectvar similarity index 78% rename from mod/inspectvar.rsc rename to mod/inspectvar index c861557..2130bb1 100644 --- a/mod/inspectvar.rsc +++ b/mod/inspectvar @@ -1,24 +1,18 @@ #!rsc by RouterOS # RouterOS script: mod/inspectvar -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# inspect variables -# https://rsc.eworm.de/doc/mod/inspectvar.md +# Copyright (c) 2020-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md :global InspectVar; :global InspectVarReturn; # inspect variable and print on terminal -:set InspectVar do={ :do { +:set InspectVar do={ + :global CharacterReplace; :global InspectVarReturn; - :put [ :tocrlf [ $InspectVarReturn $1 ] ]; -} on-error={ - :global ExitError; $ExitError false $0; -} } + :put [ $CharacterReplace [ $InspectVarReturn $1 ] ("\n") ("\n\r") ]; +} # inspect variable and return formatted string :set InspectVarReturn do={ @@ -42,7 +36,7 @@ :local TypeOf [ :typeof $Input ]; :local Return [ $IndentReturn "type" $TypeOf $Level ]; - + :if ($TypeOf = "array") do={ :foreach Key,Value in=$Input do={ :set $Return ($Return . "\n" . \ diff --git a/mod/ipcalc.rsc b/mod/ipcalc similarity index 57% rename from mod/ipcalc.rsc rename to mod/ipcalc index 477cf4a..a3e1e00 100644 --- a/mod/ipcalc.rsc +++ b/mod/ipcalc @@ -1,35 +1,27 @@ #!rsc by RouterOS # RouterOS script: mod/ipcalc -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# ip address calculation -# https://rsc.eworm.de/doc/mod/ipcalc.md +# Copyright (c) 2020-2022 Christian Hesse +# 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={ :do { +:set IPCalc do={ :local Input [ :tostr $1 ]; - :global FormatLine; :global IPCalcReturn; :local Values [ $IPCalcReturn $1 ]; - :put [ :tocrlf ( \ - [ $FormatLine "Address" ($Values->"address") ] . "\n" . \ - [ $FormatLine "Netmask" ($Values->"netmask") ] . "\n" . \ - [ $FormatLine "Network" ($Values->"network") ] . "\n" . \ - [ $FormatLine "HostMin" ($Values->"hostmin") ] . "\n" . \ - [ $FormatLine "HostMax" ($Values->"hostmax") ] . "\n" . \ - [ $FormatLine "Broadcast" ($Values->"broadcast") ]) ]; -} on-error={ - :global ExitError; $ExitError false $0; -} } + :put ( \ + "Address: " . $Values->"address" . "\n\r" . \ + "Netmask: " . $Values->"netmask" . "\n\r" . \ + "Network: " . $Values->"network" . "\n\r" . \ + "HostMin: " . $Values->"hostmin" . "\n\r" . \ + "HostMax: " . $Values->"hostmax" . "\n\r" . \ + "Broadcast: " . $Values->"broadcast"); +} # calculate and return netmask, network, min host, max host and broadcast :set IPCalcReturn do={ @@ -51,3 +43,4 @@ :return $Return; } + diff --git a/mod/notification-email.rsc b/mod/notification-email.rsc deleted file mode 100644 index 7b89d98..0000000 --- a/mod/notification-email.rsc +++ /dev/null @@ -1,266 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/notification-email -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, email, scheduler -# -# send notifications via e-mail -# https://rsc.eworm.de/doc/mod/notification-email.md - -:global EMailGenerateFrom; -:global FlushEmailQueue; -:global LogForwardFilterLogForwarding; -:global NotificationEMailSubject; -:global NotificationFunctions; -:global PurgeEMailQueue; -:global QuotedPrintable; -:global SendEMail; -:global SendEMail2; - -# generate from-property with display name -:set EMailGenerateFrom do={ - :global Identity; - - :global CleanName; - - :local From [ /tool/e-mail/get from ]; - - :if ($From ~ "<.*>\$") do={ - :return $From; - } - - :return ([ $CleanName $Identity ] . " via routeros-scripts <" . $From . ">"); -} - -# flush e-mail queue -:set FlushEmailQueue do={ :do { - :global EmailQueue; - - :global EitherOr; - :global EMailGenerateFrom; - :global IsDNSResolving; - :global IsTimeSync; - :global LogPrint; - - :local AllDone true; - :local QueueLen [ :len $EmailQueue ]; - :local Scheduler [ /system/scheduler/find where name="_FlushEmailQueue" ]; - - :if ([ :len $Scheduler ] > 0 && $QueueLen = 0) do={ - $LogPrint warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty."); - /system/scheduler/remove $Scheduler; - :return false; - } - - :if ($QueueLen = 0) do={ - :return true; - } - - :if ([ :len $Scheduler ] < 0) do={ - /system/scheduler/add name="_FlushEmailQueue" interval=1m start-time=startup \ - comment="Doing initial checks..." on-event=(":global FlushEmailQueue; \$FlushEmailQueue;"); - :set Scheduler [ /system/scheduler/find where name="_FlushEmailQueue" ]; - } - - :local SchedVal [ /system/scheduler/get $Scheduler ]; - :if (($SchedVal->"interval") < 1m) do={ - /system/scheduler/set interval=1m comment="Doing initial checks..." $Scheduler; - } - - :if ([ /tool/e-mail/get last-status ] = "in-progress") do={ - $LogPrint debug $0 ("Sending mail is currently in progress, not flushing."); - :return false; - } - - :if ([ $IsTimeSync ] = false) do={ - $LogPrint debug $0 ("Time is not synced, not flushing."); - :return false; - } - - :local EMailSettings [ /tool/e-mail/get ]; - :if ([ :typeof [ :toip ($EMailSettings->"server") ] ] != "ip" && [ $IsDNSResolving ] = false) do={ - $LogPrint debug $0 ("Server address is a DNS name and resolving fails, not flushing."); - :return false; - } - - /system/scheduler/set interval=($QueueLen . "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={ - $LogPrint warning $0 ("File '" . $File . "' does not exist, can not attach."); - } - } - /tool/e-mail/send from=[ $EMailGenerateFrom ] 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; - :return true; - } - - :if ([ :len [ /system/scheduler/find where name="_FlushEmailQueue" ] ] = 0 && \ - [ :typeof $EmailQueue ] = "nothing") do={ - $LogPrint info $0 ("Queue was purged? Exiting."); - :return false; - } - - /system/scheduler/set interval=(($SchedVal->"run-count") . "m") \ - comment="Waiting for retry..." $Scheduler; -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# 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 NotificationEMailSignature; - :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->"server") = "0.0.0.0" || ($EMailSettings->"from") = "<>") do={ - :return false; - } - - :if ([ :typeof $EmailQueue ] = "nothing") do={ - :set EmailQueue ({}); - } - :local Signature [ $EitherOr [ $NotificationEMailSignature ] [ /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;"); - } -} - -# purge the e-mail queue -:set PurgeEMailQueue do={ - :global EmailQueue; - - /system/scheduler/remove [ find where name="_FlushEmailQueue" ]; - :set EmailQueue; -} - -# convert string to quoted-printable -:global QuotedPrintable do={ - :local Input [ :tostr $1 ]; - - :global CharacterMultiply; - - :if ([ :len $Input ] = 0) do={ - :return $Input; - } - - :local Return ""; - :local Chars ( \ - "\00\01\02\03\04\05\06\07\08\09\0A\0B\0C\0D\0E\0F\10\11\12\13\14\15\16\17\18\19\1A\1B\1C\1D\1E\1F" . \ - [ $CharacterMultiply ("\00") 29 ] . "=\00?" . [ $CharacterMultiply ("\00") 63 ] . "\7F" . \ - "\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 "0123456789ABCDEF"; - - :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 ("=" . [ :pick $Hex ($Replace / 16)] . [ :pick $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={ :do { - :global SendEMail2; - - $SendEMail2 ({ origin=$0; subject=$1; message=$2; link=$3 }); -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# 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 new file mode 100644 index 0000000..4ec5f6c --- /dev/null +++ b/mod/notification-matrix @@ -0,0 +1,157 @@ +#!rsc by RouterOS +# RouterOS script: mod/notification-matrix +# Copyright (c) 2013-2022 Michael Gisbers +# Christian Hesse +# 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 LogPrintExit2; + + :local AllDone true; + :local QueueLen [ :len $MatrixQueue ]; + + :if ([ :len [ / system scheduler find where name="FlushMatrixQueue" ] ] > 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="FlushMatrixQueue" ]; + :set MatrixQueue; + } +} + +# send notification via Matrix - expects one array argument +:set ($NotificationFunctions->"matrix") do={ + :local Notification $1; + + :global Identity; + :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"={ "\\\\"; """; "
"; "&"; "<"; ">" }; + } + + :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 ("## [" . $Identity . "] " . ($Notification->"subject") . "\n```\n" . \ + ($Notification->"message") . "\n```") "plain" ]; + :local Formatted ("

" . [ $PrepareText ("[" . $Identity . "] " . ($Notification->"subject")) "format" ] . "

" . \ + "
" . [ $PrepareText ($Notification->"message") "format" ] . "
"); + :if ([ :len ($Notification->"link") ] > 0) do={ + :set Plain ($Plain . "\\n" . [ $SymbolForNotification "link" ] . \ + [ $PrepareText ("[" . $Notification->"link" . "](" . $Notification->"link" . ")") "plain" ]); + :set Formatted ($Formatted . "
" . [ $SymbolForNotification "link" ] . \ + ""link") "format" ] . "\\\">" . \ + [ $PrepareText ($Notification->"link") "format" ] . ""); + } + + :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 [ :toarray "" ]; + } + :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 . "
" . $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 lease 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-matrix.rsc b/mod/notification-matrix.rsc deleted file mode 100644 index e989ee0..0000000 --- a/mod/notification-matrix.rsc +++ /dev/null @@ -1,271 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/notification-matrix -# Copyright (c) 2013-2025 Michael Gisbers -# Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch, scheduler -# -# send notifications via Matrix -# https://rsc.eworm.de/doc/mod/notification-matrix.md - -:global FlushMatrixQueue; -:global NotificationFunctions; -:global PurgeMatrixQueue; -:global SendMatrix; -:global SendMatrix2; -:global SetupMatrixAuthenticate; -:global SetupMatrixJoinRoom; - -# flush Matrix queue -:set FlushMatrixQueue do={ :do { - :global MatrixQueue; - - :global IsFullyConnected; - :global LogPrint; - - :if ([ $IsFullyConnected ] = false) do={ - $LogPrint debug $0 ("System is not fully connected, not flushing."); - :return false; - } - - :local AllDone true; - :local QueueLen [ :len $MatrixQueue ]; - - :if ([ :len [ /system/scheduler/find where name="_FlushMatrixQueue" ] ] > 0 && $QueueLen = 0) do={ - $LogPrint warning $0 ("Flushing Matrix messages from scheduler, but queue is empty."); - } - - :foreach Id,Message in=$MatrixQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - :do { - /tool/fetch check-certificate=yes-without-crl output=none \ - http-header-field=($Message->"headers") http-method=post \ - http-data=[ :serialize to=json { "msgtype"="m.text"; "body"=($Message->"plain"); - "format"="org.matrix.custom.html"; "formatted_body"=($Message->"formatted") } ] \ - ("https://" . $Message->"homeserver" . "/_matrix/client/r0/rooms/" . $Message->"room" . \ - "/send/m.room.message?access_token=" . $Message->"accesstoken") as-value; - :set ($MatrixQueue->$Id); - } on-error={ - $LogPrint debug $0 ("Sending queued Matrix message failed."); - :set AllDone false; - } - } - } - - :if ($AllDone = true && $QueueLen = [ :len $MatrixQueue ]) do={ - /system/scheduler/remove [ find where name="_FlushMatrixQueue" ]; - :set MatrixQueue; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# 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 FetchUserAgentStr; - :global LogPrint; - :global ProtocolStrip; - :global SymbolForNotification; - - :local PrepareText do={ - :local Input [ :tostr $1 ]; - - :if ([ :len $Input ] = 0) do={ - :return ""; - } - - :local Return ""; - :local Chars { "\""; "\n"; "&"; "<"; ">" }; - :local Subs { """; "
"; "&"; "<"; ">" }; - - :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; - } - - :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 Headers ({ [ $FetchUserAgentStr ($Notification->"origin") ] }); - :local Plain ("## [" . $IdentityExtra . $Identity . "] " . \ - ($Notification->"subject") . "\n```\n" . ($Notification->"message") . "\n```"); - :local Formatted ("

" . [ $PrepareText ("[" . $IdentityExtra . $Identity . "] " . \ - ($Notification->"subject")) ] . "

" . "
" . \
-    [ $PrepareText ($Notification->"message") ] . "
"); - :if ([ :len ($Notification->"link") ] > 0) do={ - :local Label [ $ProtocolStrip ($Notification->"link") ]; - :set Plain ($Plain . "\n" . [ $SymbolForNotification "link" ] . \ - "[" . $Label . "](" . $Notification->"link" . ")"); - :set Formatted ($Formatted . "
" . [ $SymbolForNotification "link" ] . \ - ""link") ] . "\">" . \ - [ $PrepareText $Label ] . ""); - } - - :do { - /tool/fetch check-certificate=yes-without-crl output=none \ - http-header-field=$Headers http-method=post \ - http-data=[ :serialize to=json { "msgtype"="m.text"; "body"=$Plain; - "format"="org.matrix.custom.html"; "formatted_body"=$Formatted } ] \ - ("https://" . $HomeServer . "/_matrix/client/r0/rooms/" . $Room . \ - "/send/m.room.message?access_token=" . $AccessToken) as-value; - } on-error={ - $LogPrint info $0 ("Failed sending Matrix notification! Queuing..."); - - :if ([ :typeof $MatrixQueue ] = "nothing") do={ - :set MatrixQueue ({}); - } - :local Symbol [ $SymbolForNotification "alarm-clock" ]; - :local DateTime ([ /system/clock/get date ] . " " . [ /system/clock/get time ]); - :set Plain ($Plain . "\n" . $Symbol . "This message was queued since *" . \ - $DateTime . "* and may be obsolete."); - :set Formatted ($Formatted . "
" . $Symbol . "This message was queued since " . \ - $DateTime . " and may be obsolete."); - :set ($MatrixQueue->[ :len $MatrixQueue ]) { headers=$Headers; \ - accesstoken=$AccessToken; homeserver=$HomeServer; room=$Room; \ - 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;"); - } - } -} - -# purge the Matrix queue -:set PurgeMatrixQueue do={ - :global MatrixQueue; - - /system/scheduler/remove [ find where name="_FlushMatrixQueue" ]; - :set MatrixQueue; -} - -# send notification via Matrix - expects at least two string arguments -:set SendMatrix do={ :do { - :global SendMatrix2; - - $SendMatrix2 ({ origin=$0; subject=$1; message=$2; link=$3 }); -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# send notification via Matrix - expects one array argument -:set SendMatrix2 do={ - :local Notification $1; - - :global NotificationFunctions; - - ($NotificationFunctions->"matrix") ("\$NotificationFunctions->\"matrix\"") $Notification; -} - -# setup - get home server and access token -:set SetupMatrixAuthenticate do={ - :local User [ :tostr $1 ]; - :local Pass [ :tostr $2 ]; - - :global FetchUserAgentStr; - :global LogPrint; - - :global MatrixAccessToken; - :global MatrixHomeServer; - - :local Domain [ :pick $User ([ :find $User ":" ] + 1) [ :len $User] ]; - :do { - :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ - http-header-field=({ [ $FetchUserAgentStr $0 ] }) \ - ("https://" . $Domain . "/.well-known/matrix/client") as-value ]->"data"); - :set MatrixHomeServer ([ :deserialize from=json value=$Data ]->"m.homeserver"->"base_url"); - $LogPrint debug $0 ("Home server is: " . $MatrixHomeServer); - } on-error={ - $LogPrint error $0 ("Failed getting home server!"); - :return false; - } - - :if ([ :pick $MatrixHomeServer 0 8 ] = "https://") do={ - :set MatrixHomeServer [ :pick $MatrixHomeServer 8 [ :len $MatrixHomeServer ] ]; - } - - :do { - :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ - http-header-field=({ [ $FetchUserAgentStr $0 ] }) http-method=post \ - http-data=[ :serialize to=json { "type"="m.login.password"; "user"=$User; "password"=$Pass } ] \ - ("https://" . $MatrixHomeServer . "/_matrix/client/r0/login") as-value ]->"data"); - :set MatrixAccessToken ([ :deserialize from=json value=$Data ]->"access_token"); - $LogPrint debug $0 ("Access token is: " . $MatrixAccessToken); - } on-error={ - $LogPrint error $0 ("Failed logging in (and getting access token)!"); - :return false; - } - - :do { - /system/script/remove [ find where name="global-config-overlay.d/mod/notification-matrix" ]; - /system/script/add name="global-config-overlay.d/mod/notification-matrix" source=( \ - "# configuration snippet: mod/notification-matrix\n\n" . \ - ":global MatrixHomeServer \"" . $MatrixHomeServer . "\";\n" . \ - ":global MatrixAccessToken \"" . $MatrixAccessToken . "\";\n"); - $LogPrint info $0 ("Added configuration snippet. Now create and join a room, please!"); - } on-error={ - $LogPrint error $0 ("Failed adding configuration snippet!"); - :return false; - } -} - -# setup - join a room -:set SetupMatrixJoinRoom do={ - :global MatrixRoom [ :tostr $1 ]; - - :global FetchUserAgentStr; - :global LogPrint; - :global UrlEncode; - - :global MatrixAccessToken; - :global MatrixHomeServer; - :global MatrixRoom; - - :do { - /tool/fetch check-certificate=yes-without-crl output=none \ - http-header-field=({ [ $FetchUserAgentStr $0 ] }) http-method=post http-data="" \ - ("https://" . $MatrixHomeServer . "/_matrix/client/r0/rooms/" . [ $UrlEncode $MatrixRoom ] . \ - "/join?access_token=" . [ $UrlEncode $MatrixAccessToken ]) as-value; - $LogPrint debug $0 ("Joined the room."); - } on-error={ - $LogPrint error $0 ("Failed joining the room!"); - :return false; - } - - :do { - :local Snippet [ /system/script/find where name="global-config-overlay.d/mod/notification-matrix" ]; - /system/script/set $Snippet source=([ get $Snippet source ] . \ - ":global MatrixRoom \"" . $MatrixRoom . "\";\n"); - $LogPrint info $0 ("Appended configuration to configuration snippet. Please review!"); - } on-error={ - $LogPrint error $0 ("Failed appending configuration to snippet!"); - :return false; - } -} diff --git a/mod/notification-ntfy.rsc b/mod/notification-ntfy.rsc deleted file mode 100644 index aac6d6c..0000000 --- a/mod/notification-ntfy.rsc +++ /dev/null @@ -1,162 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/notification-ntfy -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch, scheduler -# -# send notifications via Ntfy (ntfy.sh) -# https://rsc.eworm.de/doc/mod/notification-ntfy.md - -:global FlushNtfyQueue; -:global NotificationFunctions; -:global PurgeNtfyQueue; -:global SendNtfy; -:global SendNtfy2; - -# flush ntfy queue -:set FlushNtfyQueue do={ :do { - :global NtfyQueue; - :global NtfyMessageIDs; - - :global IsFullyConnected; - :global LogPrint; - - :if ([ $IsFullyConnected ] = false) do={ - $LogPrint debug $0 ("System is not fully connected, not flushing."); - :return false; - } - - :local AllDone true; - :local QueueLen [ :len $NtfyQueue ]; - - :if ([ :len [ /system/scheduler/find where name="_FlushNtfyQueue" ] ] > 0 && $QueueLen = 0) do={ - $LogPrint warning $0 ("Flushing Ntfy messages from scheduler, but queue is empty."); - } - - :foreach Id,Message in=$NtfyQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - :do { - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - http-header-field=($Message->"headers") http-data=($Message->"text") \ - ($Message->"url") as-value; - :set ($NtfyQueue->$Id); - } on-error={ - $LogPrint debug $0 ("Sending queued Ntfy message failed."); - :set AllDone false; - } - } - } - - :if ($AllDone = true && $QueueLen = [ :len $NtfyQueue ]) do={ - /system/scheduler/remove [ find where name="_FlushNtfyQueue" ]; - :set NtfyQueue; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# send notification via ntfy - expects one array argument -:set ($NotificationFunctions->"ntfy") do={ - :local Notification $1; - - :global Identity; - :global IdentityExtra; - :global NtfyQueue; - :global NtfyServer; - :global NtfyServerOverride; - :global NtfyServerPass; - :global NtfyServerPassOverride; - :global NtfyServerToken; - :global NtfyServerTokenOverride; - :global NtfyServerUser; - :global NtfyServerUserOverride; - :global NtfyTopic; - :global NtfyTopicOverride; - - :global CertificateAvailable; - :global EitherOr; - :global FetchUserAgentStr; - :global IfThenElse; - :global LogPrint; - :global SymbolForNotification; - :global UrlEncode; - - :local Server [ $EitherOr ($NtfyServerOverride->($Notification->"origin")) $NtfyServer ]; - :local User [ $EitherOr ($NtfyServerUserOverride->($Notification->"origin")) $NtfyServerUser ]; - :local Pass [ $EitherOr ($NtfyServerPassOverride->($Notification->"origin")) $NtfyServerPass ]; - :local Token [ $EitherOr ($NtfyServerTokenOverride->($Notification->"origin")) $NtfyServerToken ]; - :local Topic [ $EitherOr ($NtfyTopicOverride->($Notification->"origin")) $NtfyTopic ]; - - :if ([ :len $Topic ] = 0) do={ - :return false; - } - - :local Url ("https://" . $Server . "/" . [ $UrlEncode $Topic ]); - :local Headers ({ [ $FetchUserAgentStr ($Notification->"origin") ]; \ - ("Priority: " . [ $IfThenElse ($Notification->"silent") "low" "default" ]); \ - ("Title: " . "[" . $IdentityExtra . $Identity . "] " . ($Notification->"subject")) }); - :if ([ :len $User ] > 0 || [ :len $Pass ] > 0) do={ - :set Headers ($Headers, ("Authorization: Basic " . [ :convert to=base64 ($User . ":" . $Pass) ])); - } - :if ([ :len $Token ] > 0) do={ - :set Headers ($Headers, ("Authorization: Bearer " . $Token)); - } - :local Text (($Notification->"message") . "\n"); - :if ([ :len ($Notification->"link") ] > 0) do={ - :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . ($Notification->"link")); - } - - :do { - :if ($Server = "ntfy.sh") do={ - :if ([ $CertificateAvailable "ISRG Root X1" ] = false) do={ - $LogPrint warning $0 ("Downloading required certificate failed."); - :error false; - } - } - /tool/fetch check-certificate=yes-without-crl output=none http-method=post \ - http-header-field=$Headers http-data=$Text $Url as-value; - } on-error={ - $LogPrint info $0 ("Failed sending ntfy notification! Queuing..."); - - :if ([ :typeof $NtfyQueue ] = "nothing") do={ - :set NtfyQueue ({}); - } - :set Text ($Text . "\n" . [ $SymbolForNotification "alarm-clock" ] . \ - "This message was queued since " . [ /system/clock/get date ] . " " . \ - [ /system/clock/get time ] . " and may be obsolete."); - :set ($NtfyQueue->[ :len $NtfyQueue ]) \ - { url=$Url; headers=$Headers; text=$Text }; - :if ([ :len [ /system/scheduler/find where name="_FlushNtfyQueue" ] ] = 0) do={ - /system/scheduler/add name="_FlushNtfyQueue" interval=1m start-time=startup \ - on-event=(":global FlushNtfyQueue; \$FlushNtfyQueue;"); - } - } -} - -# purge the Ntfy queue -:set PurgeNtfyQueue do={ - :global NtfyQueue; - - /system/scheduler/remove [ find where name="_FlushNtfyQueue" ]; - :set NtfyQueue; -} - -# send notification via ntfy - expects at least two string arguments -:set SendNtfy do={ :do { - :global SendNtfy2; - - $SendNtfy2 ({ origin=$0; subject=$1; message=$2; link=$3; silent=$4 }); -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# send notification via ntfy - expects one array argument -:set SendNtfy2 do={ - :local Notification $1; - - :global NotificationFunctions; - - ($NotificationFunctions->"ntfy") ("\$NotificationFunctions->\"ntfy\"") $Notification; -} diff --git a/mod/notification-telegram b/mod/notification-telegram new file mode 100644 index 0000000..d50f6d7 --- /dev/null +++ b/mod/notification-telegram @@ -0,0 +1,164 @@ +#!rsc by RouterOS +# RouterOS script: mod/notification-telegram +# Copyright (c) 2013-2022 Christian Hesse +# 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 LogPrintExit2; + + :local AllDone true; + :local QueueLen [ :len $TelegramQueue ]; + + :if ([ :len [ / system scheduler find where name="FlushTelegramQueue" ] ] > 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") . \ + "&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="FlushTelegramQueue" ]; + :set TelegramQueue; + } +} + +# send notification via telegram - expects one array argument +:set ($NotificationFunctions->"telegram") do={ + :local Notification $1; + + :global Identity; + :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 ($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 ("[" . $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 Telegram 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 Root 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") . \ + "&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 [ :toarray "" ]; + } + :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") }; + :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 lease 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/notification-telegram.rsc b/mod/notification-telegram.rsc deleted file mode 100644 index 68e913f..0000000 --- a/mod/notification-telegram.rsc +++ /dev/null @@ -1,243 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/notification-telegram -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch, scheduler -# -# send notifications via Telegram -# https://rsc.eworm.de/doc/mod/notification-telegram.md - -:global FlushTelegramQueue; -:global GetTelegramChatId; -:global NotificationFunctions; -:global PurgeTelegramQueue; -:global SendTelegram; -:global SendTelegram2; - -# flush telegram queue -:set FlushTelegramQueue do={ :do { - :global TelegramQueue; - :global TelegramMessageIDs; - - :global IsFullyConnected; - :global LogPrint; - - :if ([ $IsFullyConnected ] = false) do={ - $LogPrint debug $0 ("System is not fully connected, not flushing."); - :return false; - } - - :local AllDone true; - :local QueueLen [ :len $TelegramQueue ]; - - :if ([ :len [ /system/scheduler/find where name="_FlushTelegramQueue" ] ] > 0 && $QueueLen = 0) do={ - $LogPrint warning $0 ("Flushing Telegram messages from scheduler, but queue is empty."); - } - - :foreach Id,Message in=$TelegramQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - :do { - :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user http-method=post \ - ("https://api.telegram.org/bot" . ($Message->"tokenid") . "/sendMessage") \ - http-data=($Message->"http-data") as-value ]->"data"); - :set ($TelegramQueue->$Id); - :set ($TelegramMessageIDs->[ :tostr ([ :deserialize from=json value=$Data ]->"result"->"message_id") ]) 1; - } on-error={ - $LogPrint debug $0 ("Sending queued Telegram message failed."); - :set AllDone false; - } - } - } - - :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={ - /system/scheduler/remove [ find where name="_FlushTelegramQueue" ]; - :set TelegramQueue; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# get the chat id -:set GetTelegramChatId do={ :do { - :global TelegramTokenId; - - :global CertificateAvailable; - :global LogPrint; - - :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" ] = false) do={ - $LogPrint warning $0 ("Downloading required certificate failed."); - :return false; - } - - :local Data; - :do { - :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ - ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=0" . \ - "&allowed_updates=%5B%22message%22%5D") as-value ]->"data"); - } on-error={ - $LogPrint warning $0 ("Fetching data failed!"); - :return false; - } - - :local JSON [ :deserialize from=json value=$Data ]; - :local Count [ :len ($JSON->"result") ]; - - :if ($Count = 0) do={ - $LogPrint info $0 ("No message received."); - :return false; - } - - :local Message ($JSON->"result"->($Count - 1)->"message"); - $LogPrint info $0 ("The chat id is: " . ($Message->"chat"->"id")); - :if (($Message->"is_topic_message") = true) do={ - $LogPrint info $0 ("The thread id is: " . ($Message->"message_thread_id")); - } -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# send notification via telegram - expects one array argument -:set ($NotificationFunctions->"telegram") do={ - :local Notification $1; - - :global Identity; - :global IdentityExtra; - :global TelegramChatId; - :global TelegramChatIdOverride; - :global TelegramMessageIDs; - :global TelegramQueue; - :global TelegramThreadId; - :global TelegramThreadIdOverride; - :global TelegramTokenId; - :global TelegramTokenIdOverride; - - :global CertificateAvailable; - :global CharacterReplace; - :global EitherOr; - :global IfThenElse; - :global LogPrint; - :global ProtocolStrip; - :global SymbolForNotification; - :global UrlEncode; - - :local EscapeMD do={ - :local Text [ :tostr $1 ]; - :local Mode [ :tostr $2 ]; - :local Excl [ :tostr $3 ]; - - :global CharacterReplace; - :global IfThenElse; - - :local Chars { - "body"={ "\\"; "`" }; - "plain"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">"; - "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" }; - } - :foreach Char in=($Chars->$Mode) do={ - :if ([ :typeof [ :find $Excl $Char ] ] = "nil") do={ - :set Text [ $CharacterReplace $Text $Char ("\\" . $Char) ]; - } - } - - :if ($Mode = "body") do={ - :return ("```\n" . $Text . "\n```"); - } - - :return $Text; - } - - :local ChatId [ $EitherOr ($Notification->"chatid") \ - [ $EitherOr ($TelegramChatIdOverride->($Notification->"origin")) $TelegramChatId ] ]; - :local ThreadId [ $EitherOr ($Notification->"threadid") \ - [ $EitherOr ($TelegramThreadIdOverride->($Notification->"origin")) $TelegramThreadId ] ]; - :local TokenId [ $EitherOr ($TelegramTokenIdOverride->($Notification->"origin")) $TelegramTokenId ]; - - :if ([ :len $TokenId ] = 0 || [ :len $ChatId ] = 0) do={ - :return false; - } - - :if ([ :typeof $TelegramMessageIDs ] = "nothing") do={ - :set TelegramMessageIDs ({}); - } - - :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") ] * 2); - :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 [ $ProtocolStrip ($Notification->"link") ] "plain" ] . "]" . \ - "(" . [ $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" "_" ]); - } - - :local HTTPData ("chat_id=" . $ChatId . "&disable_notification=" . ($Notification->"silent") . \ - "&reply_to_message_id=" . ($Notification->"replyto") . "&message_thread_id=" . $ThreadId . \ - "&disable_web_page_preview=true&parse_mode=MarkdownV2"); - :do { - :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" ] = false) do={ - $LogPrint warning $0 ("Downloading required certificate failed."); - :error false; - } - :local Data ([ /tool/fetch check-certificate=yes-without-crl output=user http-method=post \ - ("https://api.telegram.org/bot" . $TokenId . "/sendMessage") \ - http-data=($HTTPData . "&text=" . [ $UrlEncode $Text ]) as-value ]->"data"); - :set ($TelegramMessageIDs->[ :tostr ([ :deserialize from=json value=$Data ]->"result"->"message_id") ]) 1; - } on-error={ - $LogPrint info $0 ("Failed sending Telegram notification! Queuing..."); - - :if ([ :typeof $TelegramQueue ] = "nothing") do={ - :set TelegramQueue ({}); - } - :set Text ($Text . "\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 ]) { tokenid=$TokenId; - http-data=($HTTPData . "&text=" . [ $UrlEncode $Text ]) }; - :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;"); - } - } -} - -# purge the Telegram queue -:set PurgeTelegramQueue do={ - :global TelegramQueue; - - /system/scheduler/remove [ find where name="_FlushTelegramQueue" ]; - :set TelegramQueue; -} - -# send notification via telegram - expects at least two string arguments -:set SendTelegram do={ :do { - :global SendTelegram2; - - $SendTelegram2 ({ origin=$0; subject=$1; message=$2; link=$3; silent=$4 }); -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# 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 new file mode 100644 index 0000000..3e02236 --- /dev/null +++ b/mod/scriptrunonce @@ -0,0 +1,46 @@ +#!rsc by RouterOS +# RouterOS script: mod/scriptrunonece +# Copyright (c) 2020-2022 Christian Hesse +# 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; + } + } + } +} diff --git a/mod/scriptrunonce.rsc b/mod/scriptrunonce.rsc deleted file mode 100644 index 7fcd5b5..0000000 --- a/mod/scriptrunonce.rsc +++ /dev/null @@ -1,56 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/scriptrunonece -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# download script and run it once -# https://rsc.eworm.de/doc/mod/scriptrunonce.md - -:global ScriptRunOnce; - -# fetch and run script(s) once -:set ScriptRunOnce do={ :do { - :local Scripts [ :toarray $1 ]; - - :global ScriptRunOnceBaseUrl; - :global ScriptRunOnceUrlSuffix; - - :global FetchHuge; - :global LogPrint; - :global ValidateSyntax; - - :foreach Script in=$Scripts do={ - :if (!($Script ~ "^(ftp|https?|sftp)://")) do={ - :if ([ :len $ScriptRunOnceBaseUrl ] = 0) do={ - $LogPrint warning $0 ("Script '" . $Script . "' is not an url and base url is not available."); - :return false; - } - :set Script ($ScriptRunOnceBaseUrl . $Script . ".rsc" . $ScriptRunOnceUrlSuffix); - } - - :local Source [ $FetchHuge $0 $Script true ]; - :if ($Source = false) do={ - $LogPrint warning $0 ("Failed fetching script '" . $Script . "'!"); - :return false; - } - - :if ([ $ValidateSyntax $Source ] = false) do={ - $LogPrint warning $0 ("The script '" . $Script . "' failed syntax validation!"); - :return false; - } - - :do { - $LogPrint info $0 ("Running script '" . $Script . "' now."); - [ :parse $Source ]; - } on-error={ - $LogPrint warning $0 ("The script '" . $Script . "' failed to run!"); - :return false; - } - - :return true; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } diff --git a/mod/ssh-keys-import.rsc b/mod/ssh-keys-import.rsc deleted file mode 100644 index 2fae4b1..0000000 --- a/mod/ssh-keys-import.rsc +++ /dev/null @@ -1,114 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mod/ssh-keys-import -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.16 -# -# import ssh keys for public key authentication -# https://rsc.eworm.de/doc/mod/ssh-keys-import.md - -:global SSHKeysImport; -:global SSHKeysImportFile; - -# import single key passed as string -:set SSHKeysImport do={ :do { - :local Key [ :tostr $1 ]; - :local User [ :tostr $2 ]; - - :global GetRandom20CharAlNum; - :global LogPrint; - :global MkDir; - :global RmDir; - :global WaitForFile; - - :if ([ :len $Key ] = 0 || [ :len $User ] = 0) do={ - $LogPrint warning $0 ("Missing argument(s), please pass key and user!"); - :return false; - } - - :if ([ :len [ /user/find where name=$User ] ] = 0) do={ - $LogPrint warning $0 ("User '" . $User . "' does not exist."); - :return false; - } - - :local KeyVal ([ :deserialize $Key delimiter=" " from=dsv options=dsv.plain ]->0); - :if (!($KeyVal->0 = "ssh-ed25519" || $KeyVal->0 = "ssh-rsa")) do={ - $LogPrint warning $0 ("SSH key of type '" . $KeyVal->0 . "' is not supported."); - :return false; - } - - :local FingerPrintMD5 [ :convert from=base64 transform=md5 to=hex ($KeyVal->1) ]; - - :if ([ :len [ /user/ssh-keys/find where user=$User key-owner~("\\bmd5=" . $FingerPrintMD5 . "\\b") ] ] > 0) do={ - $LogPrint warning $0 ("The ssh public key (MD5:" . $FingerPrintMD5 . \ - ") is already available for user '" . $User . "'."); - :return false; - } - - :if ([ $MkDir "tmpfs/ssh-keys-import" ] = false) do={ - $LogPrint warning $0 ("Creating directory 'tmpfs/ssh-keys-import' failed!"); - :return false; - } - - :local FileName ("tmpfs/ssh-keys-import/key-" . [ $GetRandom20CharAlNum 6 ] . ".pub"); - /file/add name=$FileName contents=($Key . ", md5=" . $FingerPrintMD5); - $WaitForFile $FileName; - - :do { - /user/ssh-keys/import public-key-file=$FileName user=$User; - $LogPrint info $0 ("Imported ssh public key (" . $KeyVal->2 . ", " . $KeyVal->0 . ", " . \ - "MD5:" . $FingerPrintMD5 . ") for user '" . $User . "'."); - $RmDir "tmpfs/ssh-keys-import"; - } on-error={ - $LogPrint warning $0 ("Failed importing key."); - $RmDir "tmpfs/ssh-keys-import"; - :return false; - } -} on-error={ - :global ExitError; $ExitError false $0; -} } - -# import keys from a file -:set SSHKeysImportFile do={ :do { - :local FileName [ :tostr $1 ]; - :local User [ :tostr $2 ]; - - :global EitherOr; - :global LogPrint; - :global ParseKeyValueStore; - :global SSHKeysImport; - - :if ([ :len $FileName ] = 0 || [ :len $User ] = 0) do={ - $LogPrint warning $0 ("Missing argument(s), please pass file name and user!"); - :return false; - } - - :local File [ /file/find where name=$FileName ]; - :if ([ :len $File ] = 0) do={ - $LogPrint warning $0 ("File '" . $FileName . "' does not exist."); - :return false; - } - :local Keys [ :tolf [ /file/get $FileName contents ] ]; - - :foreach KeyVal in=[ :deserialize $Keys delimiter=" " from=dsv options=dsv.plain ] do={ - :local Continue false; - :if ($KeyVal->0 = "ssh-ed25519" || $KeyVal->0 = "ssh-rsa") do={ - :do { - $SSHKeysImport ($KeyVal->0 . " " . $KeyVal->1 . " " . $KeyVal->2) $User; - } on-error={ - $LogPrint warning $0 ("Failed importing key for user '" . $User . "'."); - } - :set Continue true; - } - :if ($Continue = false && $KeyVal->0 = "#") do={ - :set User [ $EitherOr ([ $ParseKeyValueStore ($KeyVal->1) ]->"user") $User ]; - :set Continue true; - } - :if ($Continue = false && [ :len ($KeyVal->0) ] > 0) do={ - $LogPrint warning $0 ("SSH key of type '" . $KeyVal->0 . "' is not supported."); - } - } -} on-error={ - :global ExitError; $ExitError false $0; -} } diff --git a/mode-button b/mode-button new file mode 100644 index 0000000..e1b2a23 --- /dev/null +++ b/mode-button @@ -0,0 +1,76 @@ +#!rsc by RouterOS +# RouterOS script: mode-button +# Copyright (c) 2018-2022 Christian Hesse +# 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/mode-button.rsc b/mode-button.rsc deleted file mode 100644 index edc5f40..0000000 --- a/mode-button.rsc +++ /dev/null @@ -1,96 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: mode-button -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, scheduler -# -# act on multiple mode and reset button presses -# https://rsc.eworm.de/doc/mode-button.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global ModeButton; - - :global LogPrint; - - :set ($ModeButton->"count") ($ModeButton->"count" + 1); - - :local Scheduler [ /system/scheduler/find where name="_ModeButtonScheduler" ]; - - :if ([ :len $Scheduler ] = 0) do={ - $LogPrint info $ScriptName ("Creating scheduler _ModeButtonScheduler, counting presses..."); - :global ModeButtonScheduler do={ :do { - :local FuncName $0; - - :global ModeButton; - - :global LogPrint; - :global ModeButtonScheduler; - :global ValidateSyntax; - - :local LEDInvert do={ - :global ModeButtonLED; - - :global IfThenElse; - - :local LED [ /system/leds/find where leds=$ModeButtonLED \ - !disabled 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 [ find where name="_ModeButtonScheduler" ]; - - :if ([ :len $Code ] > 0) do={ - :if ([ $ValidateSyntax $Code ] = true) do={ - $LogPrint info $FuncName ("Acting on " . $Count . " mode-button presses: " . $Code); - - :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; - } - - :do { - [ :parse $Code ]; - } on-error={ - $LogPrint warning $FuncName \ - ("The code for " . $Count . " mode-button presses failed with runtime error!"); - } - } else={ - $LogPrint warning $FuncName \ - ("The code for " . $Count . " mode-button presses failed syntax validation!"); - } - } else={ - $LogPrint info $FuncName ("No action defined for " . $Count . " mode-button presses."); - } - } on-error={ - :global ExitError; $ExitError false $0; - } } - /system/scheduler/add name="_ModeButtonScheduler" \ - on-event=":global ModeButtonScheduler; \$ModeButtonScheduler;" interval=3s; - } else={ - $LogPrint debug $ScriptName ("Updating scheduler _ModeButtonScheduler..."); - /system/scheduler/set $Scheduler start-time=[ /system/clock/get time ]; - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/netwatch-dns b/netwatch-dns new file mode 100644 index 0000000..e2fcfa3 --- /dev/null +++ b/netwatch-dns @@ -0,0 +1,82 @@ +#!rsc by RouterOS +# RouterOS script: netwatch-dns +# Copyright (c) 2022 Christian Hesse +# 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 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 [ :toarray "" ]; +:local DnsFallback [ :toarray "" ]; +:local DnsCurrent [ / ip dns get servers ]; + +:foreach Host in=[ / tool netwatch find where comment~"dns" disabled=no ] 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 ]; + +:foreach Host in=[ / tool netwatch find where comment~"doh" disabled=no ] 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") ]; + } +} + +:if ($DohServer != "") do={ + :if ($DohServer != $DohCurrent) do={ + $LogPrintExit2 info $0 ("Updating DoH server: " . $DohServer) 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-dns.rsc b/netwatch-dns.rsc deleted file mode 100644 index 467d636..0000000 --- a/netwatch-dns.rsc +++ /dev/null @@ -1,150 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: netwatch-dns -# Copyright (c) 2022-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.16 -# requires device-mode, fetch -# -# monitor and manage dns/doh with netwatch -# https://rsc.eworm.de/doc/netwatch-dns.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CertificateAvailable; - :global EitherOr; - :global IsDNSResolving; - :global IsTimeSync; - :global LogPrint; - :global LogPrintOnce; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :local SettleTime (5m30s - [ /system/resource/get uptime ]); - :if ($SettleTime > 0s) do={ - $LogPrint info $ScriptName ("System just booted, giving netwatch " . $SettleTime . " to settle."); - :set ExitOK true; - :error true; - } - - :local DnsServers ({}); - :local DnsFallback ({}); - :local DnsCurrent [ /ip/dns/get servers ]; - - :foreach Host in=[ /tool/netwatch/find where comment~"\\bdns\\b" status="up" ] do={ - :local HostVal [ /tool/netwatch/get $Host ]; - :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; - - :if ($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={ - $LogPrint info $ScriptName ("Updating DNS servers: " . [ :tostr $DnsServers ]); - /ip/dns/set servers=$DnsServers; - /ip/dns/cache/flush; - } - } else={ - :if ([ :len $DnsFallback ] > 0) do={ - :if ($DnsFallback != $DnsCurrent) do={ - $LogPrint info $ScriptName ("Updating DNS servers to fallback: " . [ :tostr $DnsFallback ]); - /ip/dns/set servers=$DnsFallback; - /ip/dns/cache/flush; - } - } - } - - :local DohCurrent [ /ip/dns/get use-doh-server ]; - :local DohServers ({}); - - :if ([ :len $DohCurrent ] > 0 && [ $IsDNSResolving ] = false && [ $IsTimeSync ] = false) do={ - $LogPrint info $ScriptName ("Time is not sync, disabling DoH: " . $DohCurrent); - /ip/dns/set use-doh-server=""; - :set DohCurrent ""; - } - - :foreach Host in=[ /tool/netwatch/find where comment~"\\bdoh\\b" status="up" ] do={ - :local HostVal [ /tool/netwatch/get $Host ]; - :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; - :local HostName [ /ip/dns/static/find where name address=($HostVal->"host") \ - (type="A" or type="AAAA") !disabled !dynamic ]; - :if ([ :len $HostName ] > 0) do={ - :set HostName [ /ip/dns/static/get ($HostName->0) name ]; - } - - :if ($HostInfo->"doh" = true && $HostInfo->"disabled" != true) do={ - :if ([ :len ($HostInfo->"doh-url") ] = 0) do={ - :set ($HostInfo->"doh-url") ("https://" . [ $EitherOr $HostName ($HostVal->"host") ] . "/dns-query"); - } - - :if ($DohCurrent = $HostInfo->"doh-url") do={ - $LogPrint debug $ScriptName ("Current DoH server is still up: " . $DohCurrent); - :set ExitOK true; - :error true; - } - - :set ($DohServers->[ :len $DohServers ]) $HostInfo; - } - } - - :if ([ :len $DohCurrent ] > 0) do={ - $LogPrint info $ScriptName ("Current DoH server is down, disabling: " . $DohCurrent); - /ip/dns/set use-doh-server=""; - /ip/dns/cache/flush; - } - - :foreach DohServer in=$DohServers do={ - :if ([ :len ($DohServer->"doh-cert") ] > 0) do={ - :if ([ $CertificateAvailable ($DohServer->"doh-cert") ] = false) do={ - $LogPrint warning $ScriptName ("Downloading certificate failed, trying without."); - } - } - - :local Data false; - :do { - :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ - http-header-field=({ "accept: application/dns-message" }) \ - url=(($DohServer->"doh-url") . "?dns=" . [ :convert to=base64 ([ :rndstr length=2 ] . \ - "\01\00" . "\00\01" . "\00\00" . "\00\00" . "\00\00" . "\09doh-check\05eworm\02de\00" . \ - "\00\10" . "\00\01") ]) as-value ]->"data"); - } on-error={ - $LogPrint warning $ScriptName ("Request to DoH server failed (network or certificate issue): " . \ - ($DohServer->"doh-url")); - } - - :if ($Data != false) do={ - :if ([ :typeof [ :find $Data "doh-check-OK" ] ] = "num") do={ - /ip/dns/set use-doh-server=($DohServer->"doh-url") verify-doh-cert=yes; - :if ([ /certificate/settings/get crl-use ] = true) do={ - $LogPrintOnce warning $ScriptName ("Configured to use CRL, that can cause severe issue!"); - } - /ip/dns/cache/flush; - $LogPrint info $ScriptName ("Setting DoH server: " . ($DohServer->"doh-url")); - :set ExitOK true; - :error true; - } else={ - $LogPrint warning $ScriptName ("Received unexpected response from DoH server: " . \ - ($DohServer->"doh-url")); - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/netwatch-notify b/netwatch-notify new file mode 100644 index 0000000..34a9e8f --- /dev/null +++ b/netwatch-notify @@ -0,0 +1,154 @@ +#!rsc by RouterOS +# RouterOS script: netwatch-notify +# Copyright (c) 2020-2022 Christian Hesse +# 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 DNSIsResolving; +:global IfThenElse; +:global LogPrintExit2; +:global ParseKeyValueStore; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; + +:local NetwatchNotifyHook do={ + :local Name [ :tostr $1 ]; + :local Type [ :tostr $2 ]; + :local Hook [ :tostr $3 ]; + + :global LogPrintExit2; + :global ValidateSyntax; + + :if ([ $ValidateSyntax $Hook ] = true) do={ + :do { + [ :parse $Hook ]; + } on-error={ + $LogPrintExit2 warning $0 ("The " . $Type . "-hook for host " . $Name . " failed to run.") false; + :return ("The hook failed to run."); + } + } else={ + $LogPrintExit2 warning $0 ("The " . $Type . "-hook for host " . $Name . " failed syntax validation.") false; + :return ("The hook failed syntax validation."); + } + + $LogPrintExit2 info $0 ("Ran hook on host " . $Name . " " . $Type . ": " . $Hook) false; + :return ("Ran hook:\n" . $Hook); +} + +$ScriptLock $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 [ :toarray "" ]; +} + +:foreach Host in=[ / tool netwatch find where comment~"notify" disabled=no ] do={ + :local HostVal [ / tool netwatch get $Host ]; + :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ]; + + :if ($HostInfo->"notify" = true && $HostInfo->"disabled" != true) do={ + :local HostName ($HostInfo->"hostname"); + + :local Metric { "count"=0; "notified"=false }; + :if ([ :typeof ($NetwatchNotify->$HostName) ] = "array") do={ + :set $Metric ($NetwatchNotify->$HostName); + } + + :if ([ :typeof ($HostInfo->"resolve") ] = "str") do={ + :if ([ $DNSIsResolving ] = true) do={ + :do { + :local Resolve [ :resolve ($HostInfo->"resolve") ]; + :if ($Resolve != $HostVal->"host") do={ + $LogPrintExit2 info $0 ("Name '" . $HostInfo->"resolve" . [ $IfThenElse ($HostInfo->"resolve" != \ + $HostInfo->"hostname") ("' for host '" . $HostInfo->"hostname") "" ] . \ + "' resolves to different address " . $Resolve . ", updating.") false; + / tool netwatch set host=$Resolve $Host; + :set ($Metric->"resolve-failed") false; + } + } on-error={ + :if ($Metric->"resolve-failed" != true) do={ + $LogPrintExit2 warning $0 ("Resolving name '" . $HostInfo->"resolve" . [ $IfThenElse ($HostInfo->"resolve" != \ + $HostInfo->"hostname") ("' for host '" . $HostInfo->"hostname") "" ] . "' failed.") false; + :set ($Metric->"resolve-failed") true; + } + } + } + } + + :if ($HostVal->"status" = "up") do={ + :local Count ($Metric->"count"); + :if ($Count > 0) do={ + $LogPrintExit2 info $0 ("Host " . $HostName . " (" . $HostVal->"host" . ") is up.") false; + :set ($Metric->"count") 0; + } + :if ($Metric->"notified" = true) do={ + :local Message ("Host " . $HostName . " (" . $HostVal->"host" . ") is up since " . $HostVal->"since" . ".\n" . \ + "It was down for " . $Count . " checks since " . ($Metric->"since") . "."); + :if ([ :typeof ($HostInfo->"up-hook") ] = "str") do={ + :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $HostName "up" ($HostInfo->"up-hook") ]); + } + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Netwatch Notify: " . $HostName . " up"); \ + message=$Message }); + } + :set ($Metric->"notified") false; + :set ($Metric->"parent") ($HostInfo->"parent"); + :set ($Metric->"since"); + } else={ + :set ($Metric->"count") ($Metric->"count" + 1); + :set ($Metric->"parent") ($HostInfo->"parent"); + :set ($Metric->"since") ($HostVal->"since"); + :local Count [ $IfThenElse ([ :tonum ($HostInfo->"count") ] > 0) ($HostInfo->"count") 5 ]; + :local Parent ($HostInfo->"parent"); + :while ([ :len $Parent ] > 0) do={ + :set Count ($Count + 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 ]; + :if ($ParentNotified = false) do={ + :set Parent ($NetwatchNotify->$Parent->"parent"); + } + } + $LogPrintExit2 [ $IfThenElse ($HostInfo->"no-down-notification" != true) info debug ] $0 \ + ("Host " . $HostName . " (" . $HostVal->"host" . ") is down for " . $Metric->"count" . " checks, " . \ + [ $IfThenElse ($ParentNotified = false) [ $IfThenElse ($Metric->"notified" = true) ("already notified.") \ + ($Count - $Metric->"count" . " to go.") ] ("parent host " . $Parent . " is down.") ]) false; + :if ((($Count * 2) - ($Metric->"count" * 3)) / 2 = 0 && [ :typeof ($HostInfo->"pre-down-hook") ] = "str") do={ + $NetwatchNotifyHook $HostName "pre-down" ($HostInfo->"pre-down-hook"); + } + :if ($ParentNotified = false && $Metric->"count" >= $Count && $Metric->"notified" != true) do={ + :local Message ("Host " . $HostName . " (" . $HostVal->"host" . ") is down since " . $HostVal->"since" . "."); + :if ([ :typeof ($HostInfo->"down-hook") ] = "str") do={ + :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $HostName "down" ($HostInfo->"down-hook") ]); + } + :if ($HostInfo->"no-down-notification" != true) do={ + $SendNotification2 ({ origin=$0; \ + subject=([ $SymbolForNotification "cross-mark" ] . "Netwatch Notify: " . $HostName . " down"); \ + message=$Message }); + } + :set ($Metric->"notified") true; + } + } + :set ($NetwatchNotify->$HostName) { + "count"=($Metric->"count"); + "notified"=($Metric->"notified"); + "parent"=($Metric->"parent"); + "resolve-failed"=($Metric->"resolve-failed"); + "since"=($Metric->"since") }; + } +} diff --git a/netwatch-notify.rsc b/netwatch-notify.rsc deleted file mode 100644 index 0b8a8dc..0000000 --- a/netwatch-notify.rsc +++ /dev/null @@ -1,229 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: netwatch-notify -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# monitor netwatch and send notifications -# https://rsc.eworm.de/doc/netwatch-notify.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global NetwatchNotify; - - :global EitherOr; - :global IfThenElse; - :global IsDNSResolving; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptFromTerminal; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - - :local NetwatchNotifyHook do={ - :local ScriptName [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - :local Type [ :tostr $3 ]; - :local State [ :tostr $4 ]; - :local Hook [ :tostr $5 ]; - - :global LogPrint; - :global ValidateSyntax; - - :if ([ $ValidateSyntax $Hook ] = true) do={ - :do { - [ :parse $Hook ]; - } on-error={ - $LogPrint warning $ScriptName ("The " . $State . "-hook for " . $Type . " '" . $Name . "' failed to run."); - :return ("The hook failed to run."); - } - } else={ - $LogPrint warning $ScriptName ("The " . $State . "-hook for " . $Type . " '" . $Name . "' failed syntax validation."); - :return ("The hook failed syntax validation."); - } - - $LogPrint info $ScriptName ("Ran hook on " . $Type . " '" . $Name . "' " . $State . ": " . $Hook); - :return ("Ran hook:\n" . $Hook); - } - - :local ResolveExpected do={ - :local ScriptName [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - :local Expected [ :tostr $3 ]; - - :global GetRandom20CharAlNum; - - :local FwAddrList ($ScriptName . "-" . [ $GetRandom20CharAlNum ]); - :if ([ :typeof [ :toip $Expected ] ] = "ip") do={ - /ip/firewall/address-list/add address=$Name list=$FwAddrList dynamic=yes timeout=10s; - :delay 20ms; - :if ([ :len [ /ip/firewall/address-list/find where list=$FwAddrList address=$Expected ] ] > 0) do={ - :return true; - } - } - :if ([ :typeof [ :toip6 $Expected ] ] = "ip6") do={ - /ipv6/firewall/address-list/add address=$Name list=$FwAddrList dynamic=yes timeout=10s; - :delay 20ms; - :if ([ :len [ /ipv6/firewall/address-list/find where list=$FwAddrList address=$Expected ] ] > 0) do={ - :return true; - } - } - - :return false; - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :local ScriptFromTerminalCached [ $ScriptFromTerminal $ScriptName ]; - - :if ([ :typeof $NetwatchNotify ] = "nothing") do={ - :set NetwatchNotify ({}); - } - - :foreach Host in=[ /tool/netwatch/find where comment~"\\bnotify\\b" !disabled status!="unknown" ] 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 type=[ $IfThenElse ([ :typeof ($HostVal->"host") ] = "ip") \ - "ipv4" "ipv6" ] ($HostInfo->"resolve") ]; - :if ($Resolve != $HostVal->"host") do={ - :if ([ $ResolveExpected $ScriptName ($HostInfo->"resolve") ($HostVal->"host") ] = false) do={ - $LogPrint info $ScriptName ("Name '" . $HostInfo->"resolve" . [ $IfThenElse \ - ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ - $HostInfo->"name") "" ] . "' resolves to different address " . $Resolve . \ - ", updating."); - /tool/netwatch/set host=$Resolve $Host; - :set ($Metric->"resolve-failcnt") 0; - :set ($HostVal->"status") "unknown"; - } - } - } on-error={ - :set ($Metric->"resolve-failcnt") ($Metric->"resolve-failcnt" + 1); - :if ($Metric->"resolve-failcnt" = 3) do={ - $LogPrint [ $IfThenElse ($HostInfo->"no-resolve-fail" != true) warning debug ] \ - $ScriptName ("Resolving name '" . $HostInfo->"resolve" . [ $IfThenElse \ - ($HostInfo->"resolve" != $HostInfo->"name") ("' for " . $Type . " '" . \ - $HostInfo->"name") "" ] . "' failed."); - } - } - } - } - - :if ($HostVal->"status" = "up") do={ - :local CountDown ($Metric->"count-down"); - :if ($CountDown > 0) do={ - $LogPrint info $ScriptName \ - ("The " . $Type . " '" . $Name . "' (" . $HostDetails . ") is up."); - :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->"note") ] = "str") do={ - :set Message ($Message . "\n\nNote:\n" . ($HostInfo->"note")); - } - :if ([ :typeof ($HostInfo->"up-hook") ] = "str") do={ - :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $ScriptName $Name $Type "up" \ - ($HostInfo->"up-hook") ]); - } - $SendNotification2 ({ origin=[ $EitherOr ($HostInfo->"origin") $ScriptName ]; silent=($HostInfo->"silent"); \ - subject=([ $SymbolForNotification "white-heavy-check-mark" ] . "Netwatch Notify: " . $Name . " up"); \ - message=$Message; link=($HostInfo->"link") }); - } - :set ($Metric->"notified") false; - :set ($Metric->"parent") ($HostInfo->"parent"); - :set ($Metric->"since"); - } - - :if ($HostVal->"status" = "down") do={ - :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={ - $LogPrint [ $IfThenElse ($HostInfo->"no-down-notification" != true) info debug ] $ScriptName \ - ("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.") ]); - } - :if ((($CountDown * 2) - ($Metric->"count-down" * 3)) / 2 = 0 && \ - [ :typeof ($HostInfo->"pre-down-hook") ] = "str") do={ - $NetwatchNotifyHook $ScriptName $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->"note") ] = "str") do={ - :set Message ($Message . "\n\nNote:\n" . ($HostInfo->"note")); - } - :if ([ :typeof ($HostInfo->"down-hook") ] = "str") do={ - :set Message ($Message . "\n\n" . [ $NetwatchNotifyHook $ScriptName $Name $Type "down" \ - ($HostInfo->"down-hook") ]); - } - :if ($HostInfo->"no-down-notification" != true) do={ - $SendNotification2 ({ origin=[ $EitherOr ($HostInfo->"origin") $ScriptName ]; silent=($HostInfo->"silent"); \ - subject=([ $SymbolForNotification "cross-mark" ] . "Netwatch Notify: " . $Name . " down"); \ - message=$Message; link=($HostInfo->"link") }); - } - :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") }; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/netwatch-syslog b/netwatch-syslog new file mode 100644 index 0000000..42e5c52 --- /dev/null +++ b/netwatch-syslog @@ -0,0 +1,17 @@ +#!rsc by RouterOS +# RouterOS script: netwatch-syslog +# Copyright (c) 2013-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# requires: dont-require-permissions=yes +# +# manage remote logging facilities +# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-syslog.md + +:local Remote [ /system logging action get ([ find where target=remote ]->0) remote ]; + +if ([ / tool netwatch get [ find where host=$Remote up-script="netwatch-syslog" down-script="netwatch-syslog" ] status ] = "up") do={ + / system logging set disabled=no [ find where action=remote disabled=yes ]; +} else={ + / system logging set disabled=yes [ find where action=remote disabled=no ]; +} diff --git a/news-and-changes.rsc b/news-and-changes.rsc deleted file mode 100644 index 459326f..0000000 --- a/news-and-changes.rsc +++ /dev/null @@ -1,72 +0,0 @@ -# News, changes and migration by RouterOS Scripts -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md - -:global IDonate; - -:global IfThenElse; -:global RequiredRouterOS; -:global SymbolForNotification; - -:local Resource [ /system/resource/get ]; - -# News, changes and migration up to change 95: -# https://git.eworm.de/cgit/routeros-scripts/plain/global-config.changes?h=change-95 - -# Changes for global-config to be added to notification on script updates -:global GlobalConfigChanges { - 96="Added support for notes in 'netwatch-notify', these are included verbatim into the notification."; - 97="Modified 'dhcp-to-dns' to always add A records for names with mac address, and optionally add CNAME records if the host name is available."; - 98="Extended 'check-certificates' to download new certificate by SubjectAltNames if download by CommonName fails."; - 99="Modified 'dhcp-to-dns', which dropped global configuration. Settings moved to dhcp server's network definitions."; - 100="The script 'ssh-keys-import' became a module 'mod/ssh-keys-import' with enhanced functionality."; - 101="Introduced new script 'fw-addr-lists' to download, import and update firewall address-lists."; - 102="Modified 'hotspot-to-wpa' to support non-local (radius) users."; - 103="Dropped hard-coded name and timeout from 'hotspot-to-wpa-cleanup', instead a comment is required for dhcp server now."; - 104="All relevant scripts were ported to new wifiwave2 and are available for AX devices now!"; - 105="Extended 'check-routeros-update' to support automatic update from specific neighbor(s)."; - 106="Modified 'telegram-chat' to make it act on message replies, without activation. Also made it answer a single question mark with a short notice."; - 107="Dropped support for non-fixed width font in Telegram notifications."; - 108="Enhanced 'log-forward' to list log messages with colorful bullets to indicate severity."; - 109="Added support to send notifications via Ntfy (ntfy.sh)."; - 110="Dropped support for loading scripts from local storage."; - 111="Modified 'dhcp-to-dns' to allow multiple records for one mac address."; - 112="Enhanced 'mod/ssh-keys-import' to record the fingerprint of keys."; - 113="Added helper functions for easier setup to Matrix notification module."; - 114="All relevant scripts were ported to new wifi package for RouterOS 7.13 and later. Migration is complex and thus not done automatically!"; - 115=("Celebrating " . [ $SymbolForNotification "sparkles,star" ] . "1.000 stars " . [ $SymbolForNotification "star,sparkles" ] . "on Github! Please continue starring..."); - 116=("... and also please keep in mind that it takes a huge amount of time maintaining these scripts. " . [ $IfThenElse ($IDonate != true) \ - ("Following the donation hint " . [ $SymbolForNotification "arrow-down" "below" ] . "to keep me motivated is much appreciated. Thanks!") \ - ("Looks like you did donate already. " . [ $SymbolForNotification "heart" "<3" ] . "Much appreciated, thanks!") ]); - 117="Enhanced 'packages-update' to support deferred reboot on automatically installed updates."; - 118=("RouterOS packages increase in size with each release. This becomes a problem for devices with 16MB storage and below. " . \ - [ $IfThenElse ($Resource->"total-hdd-space" < 16000000) ("Your " . $Resource->"board-name" . " is specifically affected! ") \ - [ $IfThenElse ($Resource->"free-hdd-space" > 4000000) ("(Your " . $Resource->"board-name" . " does not suffer this issue.) ") ] ] . \ - "Huge configuration and lots of scripts give an extra risk. Take care!"); - 119="Added support for IPv6 to script 'fw-addr-lists'."; - 120="Implemented a workaround in 'backup-cloud'. Now script should no longer just crash, but send notification with error."; - 121="The 'wifiwave2' scripts are finally gone. Development continues with 'wifi' in RouterOS 7.13 and later."; - 122="The global configuration was enhanced to support loading snippets. Configuration can be split off to scripts where name starts with 'global-config-overlay.d/'."; - 123="Introduced new function '\$LogPrint', and deprecated '\$LogPrintExit2'. Please update custom scripts if you use it."; - 124="Added support for links in 'netwatch-notify', these are added below the formatted notification text."; - 125=("April's Fool! " . [ $SymbolForNotification "smiley-partying-face" ] . "Well, you missed it... - no charge nor fees. (Anyway... Donations are much appreciated, " . [ $SymbolForNotification "smiley-smiling-face" ] . "thanks!)"); - 126="Made 'telegram-chat' capable of handling large command output. Telegram messages still limit the size, so it is truncated now."; - 127="Added support for authentication to Ntfy notification module."; - 128="Added another list from blocklist.de to default configuration for 'fw-addr-lists'."; - 129="Extended 'backup-partition' to support RouterOS copy-over - interactively or before feature update."; - 130="Dropped intermediate certificates, depending on just root certificates now."; - 131="Enhanced certificate download to fallback to mkcert.org, so all (commonly trusted) root certificates are available now."; - 132="Split off plugins from 'check-health', so the script works on all devices to monitor CPU and RAM. The supported plugins for sensors in hardware are installed automatically."; - 133="Updated the default configuration for 'fw-addr-lists', deprecated lists were removed, a collective list was added."; - 134="Enhanced 'mod/notification-telegram' and 'telegram-chat' to support topics in groups."; - 135="Introduced helper function '\$GetTelegramChatId' for 'mod/notification-telegram' which helps retrieve information."; -}; - -# Migration steps to be applied on script updates -:global GlobalConfigMigration { - 97=":local Rec [ /ip/dns/static/find where comment~\"^managed by dhcp-to-dns for \" ]; :if ([ :len \$Rec ] > 0) do={ /ip/dns/static/remove \$Rec; /system/script/run dhcp-to-dns; }"; - 100=":global ScriptInstallUpdate; :if ([ :len [ /system/script/find where name=\"ssh-keys-import\" source~\"^#!rsc by RouterOS\\r?\\n\" ] ] > 0) do={ /system/script/set name=\"mod/ssh-keys-import\" ssh-keys-import; \$ScriptInstallUpdate; }"; - 104=":global CharacterReplace; :global ScriptInstallUpdate; :foreach Script in={ \"capsman-download-packages\"; \"capsman-rolling-upgrade\"; \"hotspot-to-wpa\"; \"hotspot-to-wpa-cleanup\" } do={ /system/script/set name=(\$Script . \".capsman\") [ find where name=\$Script ]; :foreach Scheduler in=[ /system/scheduler/find where on-event~(\$Script . \"([^-.]|\\\$)\") ] do={ /system/scheduler/set \$Scheduler on-event=[ \$CharacterReplace [ get \$Scheduler on-event ] \$Script (\$Script . \".capsman\") ]; }; }; /ip/hotspot/user/profile/set on-login=\"hotspot-to-wpa.capsman\" [ find where on-login=\"hotspot-to-wpa\" ]; \$ScriptInstallUpdate;"; - 111=":local Rec [ /ip/dns/static/find where comment~\"^managed by dhcp-to-dns for \" ]; :if ([ :len \$Rec ] > 0) do={ /ip/dns/static/remove \$Rec; /system/script/run dhcp-to-dns; }"; - 132=":if ([ :len [ /system/script/find where name=\"check-health\" ] ] > 0) do={ :local Code \":local Install \\\"check-health\\\"; :if ([ :len [ /system/health/find where type=\\\"\\\" name~\\\"-state\\\\\\\$\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/state\\\"); }; :if ([ :len [ /system/health/find where type=\\\"C\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/temperature\\\"); }; :if ([ :len [ /system/health/find where type=\\\"V\\\" ] ] > 0) do={ :set Install (\\\$Install . \\\",check-health.d/voltage\\\"); }; :global ScriptInstallUpdate; \\\$ScriptInstallUpdate \\\$Install;\"; :global ValidateSyntax; :if ([ \$ValidateSyntax \$Code ] = true) do={ :do { [ :parse \$Code ]; } on-error={ }; }; }"; -}; diff --git a/ospf-to-leds b/ospf-to-leds new file mode 100644 index 0000000..448cc6b --- /dev/null +++ b/ospf-to-leds @@ -0,0 +1,29 @@ +#!rsc by RouterOS +# RouterOS script: ospf-to-leds +# Copyright (c) 2020-2022 Christian Hesse +# 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 ]; + + :if ($InstanceVal->"state" = "running" && $LEDType = "off") do={ + $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " is running, led on!") false; + / system leds set type=on [ find where leds=$LED ]; + } + :if ($InstanceVal->"state" = "down" && $LEDType = "on") do={ + $LogPrintExit2 info $0 ("OSPF instance " . $InstanceVal->"name" . " is down, led off!") false; + / system leds set type=off [ find where leds=$LED ]; + } +} diff --git a/ospf-to-leds.rsc b/ospf-to-leds.rsc deleted file mode 100644 index a8662b3..0000000 --- a/ospf-to-leds.rsc +++ /dev/null @@ -1,49 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: ospf-to-leds -# Copyright (c) 2020-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# visualize ospf instance state via leds -# https://rsc.eworm.de/doc/ospf-to-leds.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :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={ - $LogPrint info $ScriptName ("OSPF instance " . $InstanceVal->"name" . " has " . $NeighborCount . " neighbors, led on!"); - /system/leds/set type=on [ find where leds=$LED ]; - } - :if ($NeighborCount = 0 && $LEDType = "on") do={ - $LogPrint info $ScriptName ("OSPF instance " . $InstanceVal->"name" . " has no neighbors, led off!"); - /system/leds/set type=off [ find where leds=$LED ]; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/packages-update b/packages-update new file mode 100644 index 0000000..2e6d8be --- /dev/null +++ b/packages-update @@ -0,0 +1,93 @@ +#!rsc by RouterOS +# RouterOS script: packages-update +# Copyright (c) 2019-2022 Christian Hesse +# 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={ + / system scheduler add name="reboot-for-update" start-time=03:00:00 interval=1d \ + on-event=(":global RandomDelay; \$RandomDelay 3600; " . \ + "/ system scheduler remove reboot-for-update; / system reboot;"); + $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; diff --git a/packages-update.rsc b/packages-update.rsc deleted file mode 100644 index b11596e..0000000 --- a/packages-update.rsc +++ /dev/null @@ -1,168 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: packages-update -# Copyright (c) 2019-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, scheduler -# -# download packages and reboot for installation -# https://rsc.eworm.de/doc/packages-update.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global BackupRandomDelay; - :global PackagesUpdateDeferReboot; - :global PackagesUpdateBackupFailure; - - :global DownloadPackage; - :global Grep; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptFromTerminal; - :global ScriptLock; - :global VersionToNum; - - :local Schedule do={ - :local ScriptName [ :tostr $1 ]; - - :global GetRandomNumber; - :global LogPrint; - - :global RebootForUpdate do={ - /system/reboot; - } - - :local StartTime [ :tostr [ :totime (10800 + [ $GetRandomNumber 7200 ]) ] ]; - /system/scheduler/add name="_RebootForUpdate" start-time=$StartTime interval=1d \ - on-event=("/system/scheduler/remove \"_RebootForUpdate\"; " . \ - ":global RebootForUpdate; \$RebootForUpdate;"); - $LogPrint info $ScriptName ("Scheduled reboot for update at " . $StartTime . \ - " local time (" . [ /system/clock/get time-zone-name ] . ")."); - :return true; - } - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ :len [ /system/scheduler/find where name="running-from-backup-partition" ] ] > 0) do={ - $LogPrint warning $ScriptName ("Running from backup partition, refusing to act."); - :set ExitOK true; - :error false; - } - - :local Update [ /system/package/update/get ]; - - :if ([ :typeof ($Update->"latest-version") ] = "nothing") do={ - $LogPrint warning $ScriptName ("Latest version is not known."); - :set ExitOK true; - :error false; - } - - :if ($Update->"installed-version" = $Update->"latest-version") do={ - $LogPrint info $ScriptName ("Version " . $Update->"latest-version" . " is already installed."); - :set ExitOK true; - :error true; - } - - :local RunOrder ({}); - :foreach Script in=[ /system/script/find where source~("\n# provides: backup-script\\b") ] do={ - :local ScriptVal [ /system/script/get $Script ]; - :local Store [ $ParseKeyValueStore [ $Grep ($ScriptVal->"source") ("\23 provides: backup-script, ") ] ]; - - :set ($RunOrder->($Store->"order" . "-" . $ScriptVal->"name")) ($ScriptVal->"name"); - } - - :local BackupRandomDelayBefore $BackupRandomDelay; - :foreach Order,Script in=$RunOrder do={ - :set BackupRandomDelay 0; - :set PackagesUpdateBackupFailure false; - :do { - $LogPrint info $ScriptName ("Running backup script " . $Script . " before update."); - /system/script/run $Script; - } on-error={ - :set PackagesUpdateBackupFailure true; - } - :set BackupRandomDelay $BackupRandomDelayBefore; - - :if ($PackagesUpdateBackupFailure = true) do={ - $LogPrint warning $ScriptName ("Running backup script " . $Script . " before update failed!"); - :if ([ $ScriptFromTerminal $ScriptName ] = true) do={ - :put "Do you want to continue anyway? [y/N]"; - :if (([ /terminal/inkey timeout=60 ] % 32) = 25) do={ - $LogPrint info $ScriptName ("User requested to continue anyway."); - } else={ - $LogPrint info $ScriptName ("Canceled update..."); - :set ExitOK true; - :error false; - } - } else={ - $LogPrint warning $ScriptName ("Canceled non-interactive update."); - :set ExitOK true; - :error false; - } - } - } - - :local NumInstalled [ $VersionToNum ($Update->"installed-version") ]; - :local NumLatest [ $VersionToNum ($Update->"latest-version") ]; - - :local DoDowngrade false; - :if ($NumInstalled > $NumLatest) do={ - :if ([ $ScriptFromTerminal $ScriptName ] = 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={ - $LogPrint warning $ScriptName ("Not installing downgrade automatically."); - :set ExitOK true; - :error false; - } - } - - :foreach Package in=[ /system/package/find where !bundle !available ] do={ - :local PkgName [ /system/package/get $Package name ]; - :if ([ $DownloadPackage $PkgName ($Update->"latest-version") ] = false) do={ - $LogPrint error $ScriptName ("Download for package " . $PkgName . " failed, update aborted."); - :set ExitOK true; - :error false; - } - } - - :if ($DoDowngrade = true) do={ - $LogPrint info $ScriptName ("Rebooting for downgrade."); - :delay 1s; - /system/package/downgrade; - } - - :if ([ $ScriptFromTerminal $ScriptName ] = true) do={ - :put "Do you want to (s)chedule reboot or (r)eboot now? [s/R]"; - :if (([ /terminal/inkey timeout=60 ] % 32) = 19) do={ - $Schedule $ScriptName; - :set ExitOK true; - :error true; - } - } else={ - :if ($PackagesUpdateDeferReboot = true) do={ - $Schedule $ScriptName; - :set ExitOK true; - :error true; - } - } - - $LogPrint info $ScriptName ("Rebooting for update."); - :delay 1s; - /system/reboot; -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/ppp-on-up b/ppp-on-up new file mode 100644 index 0000000..eebe81c --- /dev/null +++ b/ppp-on-up @@ -0,0 +1,34 @@ +#!rsc by RouterOS +# RouterOS script: ppp-on-up +# Copyright (c) 2013-2022 Christian Hesse +# 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; + } +} diff --git a/ppp-on-up.rsc b/ppp-on-up.rsc deleted file mode 100644 index e09bd9d..0000000 --- a/ppp-on-up.rsc +++ /dev/null @@ -1,44 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: ppp-on-up -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# run scripts on ppp up -# https://rsc.eworm.de/doc/ppp-on-up.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global LogPrint; - - :local Interface $interface; - - :if ([ :typeof $Interface ] = "nothing") do={ - $LogPrint error $ScriptName ("This script is supposed to run from ppp on-up script hook."); - :set ExitOK true; - :error false; - } - - :local IntName [ /interface/get $Interface name ]; - $LogPrint info $ScriptName ("PPP interface " . $IntName . " is up."); - - /ipv6/dhcp-client/release [ find where interface=$IntName !disabled bound ]; - - :foreach Script in=[ /system/script/find where source~("\n# provides: ppp-on-up\r?\n") ] do={ - :local ScriptName [ /system/script/get $Script name ]; - :do { - $LogPrint debug $ScriptName ("Running script: " . $ScriptName); - /system/script/run $Script; - } on-error={ - $LogPrint warning $ScriptName ("Running script '" . $ScriptName . "' failed!"); - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/rotate-ntp b/rotate-ntp new file mode 100644 index 0000000..83bf90a --- /dev/null +++ b/rotate-ntp @@ -0,0 +1,32 @@ +#!rsc by RouterOS +# RouterOS script: rotate-ntp +# Copyright (c) 2013-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# rotate the ntp servers +# https://git.eworm.de/cgit/routeros-scripts/about/doc/rotate-ntp.md + +:local 0 "rotate-ntp"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global NtpPool; + +:global LogPrintExit2; + +:local Ntp1; +:local Ntp2; + +:if ([ / system ntp client get enabled ] != true) do={ + $LogPrintExit2 warning $0 ("NTP client is not enabled!") true; +} + +:do { + :set Ntp1 [ :resolve ("0." . $NtpPool) ]; + :set Ntp2 [ :resolve ("1." . $NtpPool) ]; +} on-error={ + $LogPrintExit2 warning $0 ("Resolving NTP server failed.") true; +} + +$LogPrintExit2 info $0 ("Updating NTP servers to " . $Ntp1 . " and " . $Ntp2) false; +/ system ntp client set primary-ntp=$Ntp1 secondary-ntp=$Ntp2; diff --git a/sms-action b/sms-action new file mode 100644 index 0000000..a1fa4e9 --- /dev/null +++ b/sms-action @@ -0,0 +1,31 @@ +#!rsc by RouterOS +# RouterOS script: sms-action +# Copyright (c) 2018-2022 Christian Hesse +# 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-action.rsc b/sms-action.rsc deleted file mode 100644 index 3c8307a..0000000 --- a/sms-action.rsc +++ /dev/null @@ -1,41 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: sms-action -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# run action on received SMS -# https://rsc.eworm.de/doc/sms-action.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global SmsAction; - - :global LogPrint; - :global ValidateSyntax; - - :local Action $action; - - :if ([ :typeof $Action ] = "nothing") do={ - $LogPrint error $ScriptName ("This script is supposed to run from SMS hook with action=..."); - :set ExitOK true; - :error false; - } - - :local Code ($SmsAction->$Action); - :if ([ $ValidateSyntax $Code ] = true) do={ - :log info ("Acting on SMS action '" . $Action . "': " . $Code); - :delay 1s; - [ :parse $Code ]; - } else={ - $LogPrint warning $ScriptName ("The code for action '" . $Action . "' failed syntax validation!"); - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/sms-forward b/sms-forward new file mode 100644 index 0000000..436985d --- /dev/null +++ b/sms-forward @@ -0,0 +1,62 @@ +#!rsc by RouterOS +# RouterOS script: sms-forward +# Copyright (c) 2013-2022 Christian Hesse +# 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 IfThenElse; +:global LogPrintExit2; +:global ScriptLock; +:global SendNotification2; +:global SymbolForNotification; +: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 [ :toarray "" ]; + + :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"); + :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/sms-forward.rsc b/sms-forward.rsc deleted file mode 100644 index 8169022..0000000 --- a/sms-forward.rsc +++ /dev/null @@ -1,101 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: sms-forward -# Copyright (c) 2013-2025 Christian Hesse -# Anatoly Bubenkov -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# forward SMS to e-mail -# https://rsc.eworm.de/doc/sms-forward.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - :global SmsForwardHooks; - - :global IfThenElse; - :global LogPrint; - :global LogPrintOnce; - :global ScriptLock; - :global SendNotification2; - :global SymbolForNotification; - :global ValidateSyntax; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ /tool/sms/get receive-enabled ] = false) do={ - $LogPrintOnce warning $ScriptName ("Receiving of SMS is not enabled."); - :set ExitOK true; - :error false; - } - - $WaitFullyConnected; - - :local Settings [ /tool/sms/get ]; - - :if ([ /interface/lte/get ($Settings->"port") running ] != true) do={ - $LogPrint info $ScriptName ("The LTE interface is not in running state, skipping."); - :set ExitOK true; - :error true; - } - - # 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={ - $LogPrint debug $ScriptName ("Removing SMS, which started a script."); - /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={ - $LogPrint info $ScriptName ("Running hook '" . $Hook->"match" . "': " . $Hook->"command"); - :do { - :local Command [ :parse ($Hook->"command") ]; - $Command Phone=$Phone Message=($SmsVal->"message"); - :set Messages ($Messages . "\n\nRan hook '" . $Hook->"match" . "':\n" . $Hook->"command"); - } on-error={ - $LogPrint warning $ScriptName ("The code for hook '" . $Hook->"match" . "' failed to run!"); - } - } else={ - $LogPrint warning $ScriptName ("The code for hook '" . $Hook->"match" . "' failed syntax validation!"); - } - } - } - :set Delete ($Delete, $Sms); - } - } - - :if ([ :len $Messages ] > 0) do={ - :local Count [ :len $Delete ]; - $SendNotification2 ({ origin=$ScriptName; \ - 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; - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/ssh-keys-import b/ssh-keys-import new file mode 100644 index 0000000..abf0350 --- /dev/null +++ b/ssh-keys-import @@ -0,0 +1,11 @@ +#!rsc by RouterOS +# RouterOS script: ssh-keys-import +# Copyright (c) 2013-2022 Christian Hesse +# 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.rsc b/super-mario-theme similarity index 94% rename from super-mario-theme.rsc rename to super-mario-theme index 726c526..ae52fd1 100644 --- a/super-mario-theme.rsc +++ b/super-mario-theme @@ -1,10 +1,10 @@ #!rsc by RouterOS # RouterOS script: super-mario-theme -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md +# Copyright (c) 2013-2022 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md # # play Super Mario theme -# https://rsc.eworm.de/doc/super-mario-theme.md +# https://git.eworm.de/cgit/routeros-scripts/about/doc/super-mario-theme.md :local Beeps { { 660; 100 }; 150; { 660; 100 }; 300; { 660; 100 }; 300; diff --git a/telegram-chat.rsc b/telegram-chat.rsc deleted file mode 100644 index 5db4860..0000000 --- a/telegram-chat.rsc +++ /dev/null @@ -1,195 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: telegram-chat -# Copyright (c) 2023-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, fetch -# -# use Telegram to chat with your Router and send commands -# https://rsc.eworm.de/doc/telegram-chat.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global Identity; - :global TelegramChatActive; - :global TelegramChatGroups; - :global TelegramChatId; - :global TelegramChatIdsTrusted; - :global TelegramChatOffset; - :global TelegramChatRunTime; - :global TelegramMessageIDs; - :global TelegramRandomDelay; - :global TelegramTokenId; - - :global CertificateAvailable; - :global EitherOr; - :global EscapeForRegEx; - :global GetRandom20CharAlNum; - :global IfThenElse; - :global LogPrint; - :global MAX; - :global MIN; - :global MkDir; - :global RandomDelay; - :global RmDir; - :global ScriptLock; - :global SendTelegram2; - :global SymbolForNotification; - :global ValidateSyntax; - :global WaitForFile; - :global WaitFullyConnected; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - $WaitFullyConnected; - - :if ([ :typeof $TelegramChatOffset ] != "array") do={ - :set TelegramChatOffset { 0; 0; 0 }; - } - :if ([ :typeof $TelegramRandomDelay ] != "num") do={ - :set TelegramRandomDelay 0; - } - - :if ([ $CertificateAvailable "Go Daddy Root Certificate Authority - G2" ] = false) do={ - $LogPrint warning $ScriptName ("Downloading required certificate failed."); - :set ExitOK true; - :error false; - } - - $RandomDelay $TelegramRandomDelay; - - :local Data false; - :for I from=1 to=4 do={ - :if ($Data = false) do={ - :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 TelegramRandomDelay [ $MAX 0 ($TelegramRandomDelay - 1) ]; - } on-error={ - :if ($I < 4) do={ - $LogPrint debug $ScriptName ("Fetch failed, " . $I . ". try."); - :set TelegramRandomDelay [ $MIN 15 ($TelegramRandomDelay + 5) ]; - :delay (($I * $I) . "s"); - } - } - } - } - - :if ($Data = false) do={ - $LogPrint warning $ScriptName ("Failed getting updates."); - :set ExitOK true; - :error false; - } - - :local JSON [ :deserialize from=json value=$Data ]; - :local UpdateID 0; - :local Uptime [ /system/resource/get uptime ]; - :foreach Update in=($JSON->"result") do={ - :set UpdateID ($Update->"update_id"); - :local Message ($Update->"message"); - :local IsReply ([ :typeof ($Message->"reply_to_message") ] = "string"); - :local IsMyReply ($TelegramMessageIDs->[ :tostr ($Message->"reply_to_message"->"message_id") ]); - :if (($IsMyReply = 1 || $TelegramChatOffset->0 > 0 || $Uptime > 5m) && $UpdateID >= $TelegramChatOffset->2) do={ - :local Trusted false; - :local Chat ($Message->"chat"); - :local From ($Message->"from"); - :local Command ($Message->"text"); - :local ThreadId [ $IfThenElse ($Message->"is_topic_message") ($Message->"message_thread_id") "" ]; - - :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={ - :if ($From->"id" = $IdsTrusted || $From->"username" = $IdsTrusted) do={ - :set Trusted true; - } - } - - :if ($Trusted = true) do={ - :local Done false; - :if ($Command = "?") do={ - $LogPrint info $ScriptName ("Sending notice for update " . $UpdateID . "."); - $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=true; \ - replyto=($Message->"message_id"); threadid=$ThreadId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=([ $IfThenElse ([ :len ($From->"first_name") ] > 0) ("Hello " . ($From->"first_name") . "!\n\n") ] . \ - "Online" . [ $IfThenElse $TelegramChatActive " (and active!)" ] . ", awaiting your commands!") }); - :set Done true; - } - :if ($Done = false && [ :pick $Command 0 1 ] = "!") do={ - :if ($Command ~ ("^! *(" . [ $EscapeForRegEx $Identity ] . "|@" . $TelegramChatGroups . ")\$")) do={ - :set TelegramChatActive true; - } else={ - :set TelegramChatActive false; - } - $LogPrint info $ScriptName ("Now " . [ $IfThenElse $TelegramChatActive "active" "passive" ] . \ - " from update " . $UpdateID . "!"); - :set Done true; - } - :if ($Done = false && ($IsMyReply = 1 || ($IsReply = false && \ - $TelegramChatActive = true)) && [ :len $Command ] > 0) do={ - :if ([ $ValidateSyntax $Command ] = true) do={ - :local State ""; - :local File ("tmpfs/telegram-chat/" . [ $GetRandom20CharAlNum 6 ]); - :if ([ $MkDir "tmpfs/telegram-chat" ] = false) do={ - $LogPrint error $ScriptName ("Failed creating directory!"); - :set ExitOK true; - :error false; - } - $LogPrint info $ScriptName ("Running command from update " . $UpdateID . ": " . $Command); - :execute script=(":do {\n" . $Command . "\n} on-error={ /file/add name=\"" . $File . ".failed\" };" . \ - "/file/add name=\"" . $File . ".done\"") file=($File . "\00"); - :if ([ $WaitForFile ($File . ".done") [ $EitherOr $TelegramChatRunTime 20s ] ] = false) do={ - :set State ([ $SymbolForNotification "warning-sign" ] . "The command did not finish, still running in background.\n\n"); - } - :if ([ :len [ /file/find where name=($File . ".failed") ] ] > 0) do={ - :set State ([ $SymbolForNotification "cross-mark" ] . "The command failed with an error!\n\n"); - } - :local Content ([ /file/read chunk-size=32768 file=$File as-value ]->"data"); - $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=true; \ - replyto=($Message->"message_id"); threadid=$ThreadId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=([ $SymbolForNotification "gear" ] . "Command:\n" . $Command . "\n\n" . \ - $State . [ $IfThenElse ([ :len $Content ] > 0) \ - ([ $SymbolForNotification "memo" ] . "Output:\n" . $Content) \ - ([ $SymbolForNotification "memo" ] . "No output.") ]) }); - $RmDir "tmpfs/telegram-chat"; - } else={ - $LogPrint info $ScriptName ("The command from update " . $UpdateID . " failed syntax validation!"); - $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=false; \ - replyto=($Message->"message_id"); threadid=$ThreadId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=([ $SymbolForNotification "gear" ] . "Command:\n" . $Command . "\n\n" . \ - [ $SymbolForNotification "cross-mark" ] . "The command failed syntax validation!") }); - } - } - } else={ - :local MessageText ("Received a message from untrusted contact " . \ - [ $IfThenElse ([ :len ($From->"username") ] = 0) "without username" ("'" . $From->"username" . "'") ] . \ - " (ID " . $From->"id" . ") in update " . $UpdateID . "!"); - :if ($Command ~ ("^! *" . [ $EscapeForRegEx $Identity ] . "\$")) do={ - $LogPrint warning $ScriptName $MessageText; - $SendTelegram2 ({ origin=$ScriptName; chatid=($Chat->"id"); silent=false; \ - replyto=($Message->"message_id"); threadid=$ThreadId; \ - subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ - message=("You are not trusted.") }); - } else={ - $LogPrint info $ScriptName $MessageText; - } - } - } else={ - $LogPrint debug $ScriptName ("Already handled update " . $UpdateID . "."); - } - } - :set TelegramChatOffset ([ :pick $TelegramChatOffset 1 3 ], \ - [ $IfThenElse ($UpdateID >= $TelegramChatOffset->2) ($UpdateID + 1) ($TelegramChatOffset->2) ]); -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/unattended-lte-firmware-upgrade b/unattended-lte-firmware-upgrade new file mode 100644 index 0000000..14b03e5 --- /dev/null +++ b/unattended-lte-firmware-upgrade @@ -0,0 +1,39 @@ +#!rsc by RouterOS +# RouterOS script: unattended-lte-firmware-upgrade +# Copyright (c) 2018-2022 Christian Hesse +# 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 finished, waiting for installation before reset."); + :delay 150s; + / interface lte at-chat $1 input="AT+RESET"; + :log info ("Reset device, waiting to finish and reconnect."); + } + / 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/unattended-lte-firmware-upgrade.rsc b/unattended-lte-firmware-upgrade.rsc deleted file mode 100644 index 83925fd..0000000 --- a/unattended-lte-firmware-upgrade.rsc +++ /dev/null @@ -1,52 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: unattended-lte-firmware-upgrade -# Copyright (c) 2018-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# requires device-mode, scheduler -# -# schedule unattended lte firmware upgrade -# https://rsc.eworm.de/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 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"); - :do { - /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 as-value ]; - :if ([ :len ($Firmware->"latest") ] > 0 && \ - ($Firmware->"installed") != ($Firmware->"latest")) do={ - :log warning ("LTE firmware versions still differ. Upgrade failed anyway?"); - } - } on-error={ - :log error ("LTE firmware upgrade on '" . $1 . "' failed."); - } - } - - /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 new file mode 100644 index 0000000..475b30c --- /dev/null +++ b/update-gre-address @@ -0,0 +1,31 @@ +#!rsc by RouterOS +# RouterOS script: update-gre-address +# Copyright (c) 2013-2022 Christian Hesse +# 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 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" ]; + :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-gre-address.rsc b/update-gre-address.rsc deleted file mode 100644 index cddfa92..0000000 --- a/update-gre-address.rsc +++ /dev/null @@ -1,46 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: update-gre-address -# Copyright (c) 2013-2025 Christian Hesse -# https://rsc.eworm.de/COPYING.md -# -# requires RouterOS, version=7.15 -# -# update gre interface remote address with dynamic address from -# ipsec remote peer -# https://rsc.eworm.de/doc/update-gre-address.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CharacterReplace; - :global LogPrint; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - /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={ - $LogPrint info $ScriptName ("Updating remote address for interface " . $GreIntVal->"name" . " to " . $PeerVal->"dynamic-address"); - /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; - } - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -} diff --git a/update-tunnelbroker b/update-tunnelbroker new file mode 100644 index 0000000..53b0edd --- /dev/null +++ b/update-tunnelbroker @@ -0,0 +1,48 @@ +#!rsc by RouterOS +# RouterOS script: update-tunnelbroker +# Copyright (c) 2013-2022 Christian Hesse +# Michael Gisbers +# 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 ([ / ip cloud get ddns-enabled ] != true) do={ + $LogPrintExit2 error $0 ("IP cloud DDNS is not enabled.") true; +} + +# Get the current ip address from cloud +/ ip cloud force-update; +:while ([ / ip cloud get status ] != "updated") do={ + :delay 1s; +} +:local PublicAddress [ / ip cloud get public-address ]; + +:foreach Interface in=[ / interface 6to4 find where comment~"^tunnelbroker" !disabled ] do={ + :local InterfaceVal [ / interface 6to4 get $Interface ]; + + :if ($PublicAddress != $InterfaceVal->"local-address") do={ + :local Comment [ $ParseKeyValueStore ($InterfaceVal->"comment") ]; + + :if ([ $CertificateAvailable "Starfield Root Certificate Authority - G2" ] = false) do={ + $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; + } + $LogPrintExit2 info $0 ("Local address changed, sending UPDATE to tunnelbroker! New address: " . $PublicAddress) false; + / tool fetch check-certificate=yes-without-crl \ + ("https://ipv4.tunnelbroker.net/nic/update\?hostname=" . $Comment->"id") \ + user=($Comment->"user") password=($Comment->"pass") output=none as-value; + / interface 6to4 set $Interface local-address=$PublicAddress; + } else={ + $LogPrintExit2 debug $0 ("All tunnelbroker configuration is up to date for interface " . $InterfaceVal->"name" . ".") false; + } +} diff --git a/update-tunnelbroker.rsc b/update-tunnelbroker.rsc deleted file mode 100644 index 45afa6f..0000000 --- a/update-tunnelbroker.rsc +++ /dev/null @@ -1,74 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: update-tunnelbroker -# Copyright (c) 2013-2025 Christian Hesse -# Michael Gisbers -# https://rsc.eworm.de/COPYING.md -# -# provides: ppp-on-up -# requires RouterOS, version=7.15 -# requires device-mode, fetch -# -# update local address of tunnelbroker interface -# https://rsc.eworm.de/doc/update-tunnelbroker.md - -:global GlobalFunctionsReady; -:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } - -:local ExitOK false; -:do { - :local ScriptName [ :jobname ]; - - :global CertificateAvailable; - :global LogPrint; - :global ParseKeyValueStore; - :global ScriptLock; - - :if ([ $ScriptLock $ScriptName ] = false) do={ - :set ExitOK true; - :error false; - } - - :if ([ $CertificateAvailable "Starfield Root Certificate Authority - G2" ] = false) do={ - $LogPrint error $ScriptName ("Downloading required certificate failed."); - :set ExitOK true; - :error false; - } - - :foreach Interface in=[ /interface/6to4/find where comment~"^tunnelbroker" !disabled ] do={ - :local Data false; - :local InterfaceVal [ /interface/6to4/get $Interface ]; - :local Comment [ $ParseKeyValueStore ($InterfaceVal->"comment") ]; - - :for I from=2 to=0 do={ - :if ($Data = false) do={ - :do { - :set Data ([ /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={ - $LogPrint debug $ScriptName ("Failed downloading, " . $I . " retries pending."); - :delay 2s; - } - } - } - - :if (!($Data ~ "^(good|nochg) ")) do={ - $LogPrint error $ScriptName ("Failed sending the local address to tunnelbroker or unexpected response!"); - :set ExitOK true; - :error false; - } - - :local PublicAddress [ :pick $Data ([ :find $Data " " ] + 1) [ :find $Data "\n" ] ]; - - :if ($PublicAddress != $InterfaceVal->"local-address") do={ - :if ([ :len [ /ip/address find where address~("^" . $PublicAddress . "/") ] ] < 1) do={ - $LogPrint warning $ScriptName ("The address " . $PublicAddress . " is not configured on your device. NAT by ISP?"); - } - - $LogPrint info $ScriptName ("Local address changed, updating tunnel configuration with address: " . $PublicAddress); - /interface/6to4/set $Interface local-address=$PublicAddress; - } - } -} on-error={ - :global ExitError; $ExitError $ExitOK [ :jobname ]; -}